Fix
Getting Started With Python Excel AutomationDeep dive

Fix the win32com gen_py AttributeError (CLSIDToClassMap)

Excel automation with pywin32 suddenly fails with an AttributeError saying a win32com.gen_py module has no attribute CLSIDToClassMap. The generated type-library cache is broken. Find the gen_py folder, delete just the Excel entry, let EnsureDispatch rebuild it, and stop it happening again on shared and scheduled machines.

A script that has driven Excel through pywin32 for months stops at the first line:

Text
AttributeError: module 'win32com.gen_py.00020813-0000-0000-C000-000000000046x0x1x9' has no attribute 'CLSIDToClassMap'

Variants end in 'CLSIDToPackageMap', or appear as "This COM object can not automate the makepy process". Nothing is wrong with Excel or with your code. win32com.client.gencache.EnsureDispatch keeps a cache of Python modules generated from Excel's type library, and one of those modules is broken: half-written because a run was interrupted, left over from a different Excel version, or written by a different Python. Deleting the broken entry fixes it; this page shows how to find it, how to do that safely in a script, and how to keep it from coming back. It is part of Troubleshooting Common Python Excel Errors.

Prerequisites

pywin32 and Excel on Windows:

Bash
pip install pywin32

The code that talks to Excel only runs on Windows. The cache-cleaning function further down is plain Python and is demonstrated here against a sample folder, so its behaviour can be checked anywhere.

What gen_py is

EnsureDispatch("Excel.Application") gives early binding: pywin32 reads Excel's type library once, generates a Python module describing every object, method and constant, and imports that module on later runs. That is what makes win32com.client.constants.xlUp and fast, case-correct method calls work. The generated module lives in a folder named after the type library:

  • 00020813-…-000000000046 is the ID of Excel's type library, the long name in the error message;
  • x0x1x9 is the locale (0) and the library's version (1.9 for current Excel).

The folder sits inside the gen_py directory. Unless a gen_py folder exists inside the installed win32com package, pywin32 puts it in the user's temporary directory, in a subfolder per Python version, such as %LOCALAPPDATA%\Temp\gen_py\3.12. Different users, and the same user under different Python versions, each have their own cache.

How EnsureDispatch uses the cache EnsureDispatch looks up Excel's type library ID in the cache index. If a generated module exists it imports it; if not it runs makepy to generate one. The error appears when the module exists but is incomplete, so importing it succeeds and reading CLSIDToClassMap fails. EnsureDispatch() Excel.Application Cache index dicts.dat Generated module 00020813-…x0x1x9 Import module loads Class map missing if broken

Step 1 — Find the cache folder

Ask pywin32 where it keeps the cache rather than guessing; it prints the path it will use:

Python
import win32com

print(win32com.__gen_path__)

Run it with the same interpreter the failing script uses: the path includes the Python version, and a scheduled job running under a service account has its own temporary directory, so its cache is somewhere else entirely. On the command line, python -m win32com.client.gencache -d lists the cached type libraries, and it fails with the same AttributeError when the Excel entry is the broken one, which confirms the diagnosis.

Step 2 — Delete the Excel entry, not everything

The cache holds entries for every COM library used on the machine, Outlook and Word included. Removing only the Excel entry is enough, and leaves the others alone. The entry can be a folder (for large libraries such as Excel) or a single .py file, and the index file dicts.dat should be removed with it so pywin32 does not look for the deleted entry:

Python
import shutil
from pathlib import Path

EXCEL_TYPELIB = "00020813-0000-0000-C000-000000000046"

def clear_excel_cache(gen_path):
    """Remove generated Excel wrappers and the cache index from a gen_py folder."""
    gen = Path(gen_path)
    removed = []
    for entry in gen.glob(f"{EXCEL_TYPELIB}x*"):
        if entry.is_dir():
            shutil.rmtree(entry)
        else:
            entry.unlink()
        removed.append(entry.name)
    index = gen / "dicts.dat"
    if index.exists():
        index.unlink()
        removed.append(index.name)
    return removed

Here it runs against a sample cache laid out the way pywin32 lays it out, with Excel's broken folder, an Outlook entry and the index:

Python
sample = Path("gen_py/3.12")
(sample / f"{EXCEL_TYPELIB}x0x1x9").mkdir(parents=True, exist_ok=True)
(sample / f"{EXCEL_TYPELIB}x0x1x9" / "__init__.py").write_text("")       # the truncated module
(sample / "00062FFF-0000-0000-C000-000000000046x0x9x6.py").write_text("# Outlook")
(sample / "dicts.dat").write_bytes(b"index")
(sample / "__init__.py").write_text("")

print("removed:", clear_excel_cache(sample))
print("left:", sorted(p.name for p in sample.iterdir()))

On Windows, call it with win32com.__gen_path__, with no Excel automation running in the meantime, then run the script again. EnsureDispatch regenerates the module on first use, which takes a few seconds once.

The gen_py folder before and after A table of the entries in the gen_py folder. The Excel entry and dicts.dat are removed; the Outlook entry and __init__.py stay. On the next EnsureDispatch call the Excel entry and the index are rebuilt. Entry Action Next run 00020813-…x0x1x9 (Excel) deleted rebuilt on next run 00062FFF-…x0x9x6.py (Outlook) kept dicts.dat deleted rebuilt on next run __init__.py kept

Step 3 — Rebuild and verify

With the entry gone, the next EnsureDispatch regenerates it. A short check confirms early binding works again, because constants are only available with a generated module:

Python
import win32com.client as win32

excel = win32.gencache.EnsureDispatch("Excel.Application")
print(type(excel))                                  # <class 'win32com.gen_py....Application'>
print(win32.constants.xlUp)                         # -4162, from the regenerated module
excel.Quit()

The regenerated folder appears under the same name as the one you deleted, and dicts.dat is written again next to it. If the error returns immediately, the new module is being generated by a Python other than the one you checked, or another process is still holding the old module in memory; close every Python process that talks to Excel and try again.

If a script only needs to get running and does not use constants, win32com.client.dynamic.Dispatch("Excel.Application") uses late binding and never touches the cache. It is slower per call and method names are resolved at run time, so it is a workaround, not the fix.

Why it breaks, and how to prevent it

The common causes, in the order they turn up:

  • An interrupted first run. Generation takes a few seconds; a script killed during it, or two scripts starting at once, leaves a partial module. Warm the cache once, interactively, after installing Python or pywin32.
  • Excel updates. A new Excel version can change the type library version, and a cache built against the old one no longer matches. Clearing it after major Office updates avoids surprises.
  • Several Python installations. Each Python version writes its own subfolder, but a copied virtual environment or a gen_py folder inside a packaged application can carry a cache from another machine. Delete it from the package.
  • Shared service accounts. Scheduled jobs running as one account share one cache; a job that crashes mid-generation breaks the others. Run Excel COM Automation from Task Scheduler covers running these jobs reliably.
Where broken caches come from Three panels. Interrupted generation leaves a partial module, so warm the cache once and avoid concurrent first runs. Office updates change the type library, so clear the Excel entry after upgrades. Copied environments and packaged apps carry someone else's cache, so exclude gen_py when copying or freezing. Interrupted generation killed or concurrent first run warm the cache once Office update type library changed clear after upgrades Copied environments cache from another machine exclude gen_py

When a job fails on a server and works on your laptop, the first question is which Python and which cache the job actually uses. Log both at the start of every run; it costs nothing and answers the question from the job's own output:

Python
import logging
import sys
from importlib.metadata import version
import win32com

logging.info("python %s at %s", sys.version.split()[0], sys.executable)
logging.info("pywin32 %s, gen_py cache at %s", version("pywin32"), win32com.__gen_path__)

Two jobs that print different cache paths do not share a cache, so clearing one does not fix the other. A service account's temporary directory is also not the one you see when logged in, which is why clearing "the" gen_py folder by hand sometimes appears to do nothing.

A scheduled job can recover by itself: catch the AttributeError from EnsureDispatch, clear the Excel entry, and try once more:

Python
import win32com
import win32com.client as win32

def excel_app():
    try:
        return win32.gencache.EnsureDispatch("Excel.Application")
    except AttributeError:
        clear_excel_cache(win32com.__gen_path__)
        return win32.gencache.EnsureDispatch("Excel.Application")

The retry has to happen in a fresh process if the broken module was already imported; if the second attempt still fails, exit with an error and let the scheduler rerun the job.

Common pitfalls

  • Deleting all of gen_py while Excel scripts run. Clear it between runs; a script generating a module while another deletes the folder recreates the problem.
  • Clearing the wrong cache. win32com.__gen_path__ depends on the Python that runs it; check it with the same interpreter the job uses.
  • Reaching for dynamic.Dispatch permanently. It avoids the cache but also loses constants and early binding; fix the cache instead.
  • Packaging the cache. Tools that freeze a Python app can include a gen_py folder built on the developer's machine; exclude it.

Conclusion

The CLSIDToClassMap AttributeError means pywin32's generated module for Excel's type library is incomplete or stale. Find the cache with win32com.__gen_path__, delete the entries starting with Excel's type-library ID, 00020813-…, together with dicts.dat, and let EnsureDispatch rebuild them on the next run. Warm the cache once after installs and Office updates, avoid concurrent first runs, and let scheduled jobs clear the entry and retry.

Frequently asked questions

Where is the win32com gen_py folder? Print win32com.gen_path. By default it is a gen_py folder inside the user's temporary directory, with a subfolder per Python version, unless a gen_py folder exists inside the installed win32com package.

Is it safe to delete the gen_py folder? Yes. It only holds generated wrappers, which pywin32 recreates on demand. Deleting only the Excel entry and dicts.dat is enough and leaves wrappers for other applications in place.

What does the folder name 00020813-…x0x1x9 mean? It is the ID of Excel's type library followed by the locale and version of the library. The generated Python module for Excel is stored under that name.

Should I use Dispatch instead of EnsureDispatch to avoid the error? dynamic.Dispatch avoids the cache but loses constants and early binding. Use it as a temporary workaround and clear the cache to fix the real problem.

More on errors & fixes

More on errors & fixes →