{"id":34073467,"url":"https://github.com/thepoetcoder/safexl","last_synced_at":"2025-12-14T08:54:48.002Z","repository":{"id":57463589,"uuid":"279621391","full_name":"ThePoetCoder/safexl","owner":"ThePoetCoder","description":"The Safe Way to Excel","archived":false,"fork":false,"pushed_at":"2020-09-02T16:34:32.000Z","size":50,"stargazers_count":14,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-09-22T21:36:26.916Z","etag":null,"topics":["python","pywin32","vba","vba-excel","wrapper"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ThePoetCoder.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.rst","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-07-14T15:20:06.000Z","updated_at":"2023-05-09T22:19:33.000Z","dependencies_parsed_at":"2022-09-14T16:40:11.104Z","dependency_job_id":null,"html_url":"https://github.com/ThePoetCoder/safexl","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ThePoetCoder/safexl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ThePoetCoder%2Fsafexl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ThePoetCoder%2Fsafexl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ThePoetCoder%2Fsafexl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ThePoetCoder%2Fsafexl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ThePoetCoder","download_url":"https://codeload.github.com/ThePoetCoder/safexl/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ThePoetCoder%2Fsafexl/sbom","scorecard":{"id":140353,"data":{"date":"2025-08-04","repo":{"name":"github.com/ThePoetCoder/safexl","commit":"d2fb91ad45d33b6f51946e99c78e7fcf7564e82e"},"scorecard":{"version":"v5.2.1-28-gc1d103a9","commit":"c1d103a9bb9f635ec7260bf9aa0699466fa4be0e"},"score":2.7,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/11 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#binary-artifacts"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#sast"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#pinned-dependencies"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#dangerous-workflow"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#signed-releases"}},{"name":"License","score":9,"reason":"license file detected","details":["Info: project has a license file: LICENSE.rst:0","Warn: project license file does not contain an FSF or OSI license."],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#license"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":8,"reason":"2 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: PYSEC-2019-41 / GHSA-qfc5-mcwq-26q8","Warn: Project is vulnerable to: PYSEC-2021-112 / GHSA-hwfp-hg2m-9vr2"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-16T07:53:41.593Z","repository_id":57463589,"created_at":"2025-08-16T07:53:41.593Z","updated_at":"2025-08-16T07:53:41.593Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27723823,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-14T02:00:11.348Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["python","pywin32","vba","vba-excel","wrapper"],"created_at":"2025-12-14T08:54:46.954Z","updated_at":"2025-12-14T08:54:47.992Z","avatar_url":"https://github.com/ThePoetCoder.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# safexl - The Safe Way to Excel\r\n##### A wrapper around the pywin32 module for easier use and automated cleanup of Excel Application COM objects\r\nThe pywin32 library grants extraordinary capabilities to interact with Windows applications, \r\nbut includes many oddities usually learned through trial and error, as seen in Stack Overflow posts such as these:\r\n* [COM: excelApplication.Application.Quit() preserves the process](https://stackoverflow.com/questions/18421457/com-excelapplication-application-quit-preserves-the-process)\r\n* [Can't close Excel completely using win32com on Python](https://stackoverflow.com/questions/10221150/cant-close-excel-completely-using-win32com-on-python)\r\n* [Loading addins when Excel is instantiated programmatically](https://stackoverflow.com/questions/213375/loading-addins-when-excel-is-instantiated-programmatically)\r\n* [AutoFilter method of Range class failed (Dispatch vs EnsureDispatch)](https://stackoverflow.com/questions/22930751/autofilter-method-of-range-class-failed-dispatch-vs-ensuredispatch)\r\n\r\nMy experience with automating Excel using pywin32 led me to create `safexl`, a pywin32 wrapper centered around easier use and \r\nautomated cleanup of Excel Application COM objects in Python. The main functionality of this package is a context-managed \r\n`application` generator that you can use inside a `with` block, built with some pywin32 best practices in place and a few psutil \r\ntools focused on working with Excel.\r\n\r\n----------------------------------------------------------------------------------------------------------------------------------\r\n\r\n## Install\r\nYou can import the library with pip:\r\n```cmd\r\npip install safexl\r\n```\r\nOr you can download it [here](https://github.com/ThePoetCoder/safexl) at GitHub.\r\n\r\nThis module requires:\r\n* [pywin32](https://pypi.org/project/pywin32/)\r\n* [psutil](https://pypi.org/project/psutil/)\r\n\r\n----------------------------------------------------------------------------------------------------------------------------------\r\n\r\n## Usage\r\nThis package makes writing pywin32 code in Python as simple as:\r\n```python\r\nimport safexl\r\n\r\nwith safexl.application(kill_after=False, maximize=True, include_addins=True) as app:\r\n    wb = app.Workbooks.Add()\r\n    ws = wb.ActiveSheet\r\n    rng = ws.Range(\"B1\")\r\n    rng.Value = \"Hello, World!\"\r\n    rng.Interior.Color = safexl.colors.rgbRed  # colors are included\r\n    rng.EntireColumn.AutoFit()\r\n    ws.Columns(\"A\").Delete(Shift=safexl.xl_constants.xlToLeft)  # constants are included\r\n\r\n# This results in Excel being opened to a Sheet where cell \"A1\" has 'Hello, World!' in it with a red background\r\n```\r\n\r\nIf you've programmatically worked with Excel in a Win32 environment before, this code should look very familiar, \r\nas I am not altering the COM object itself before yielding it to you inside a `with` block; I am instead providing \r\na means to create and delete it more easily. \r\n\r\n_If you would like to alter the COM object (for things like turning off ScreenUpdating\r\n while your code runs), then please see the **Performance** section near the bottom._\r\n\r\nIn this way, the following two code snippets will have the same effect:\r\n#### 1.) without safexl\r\n```python\r\nimport pythoncom\r\nimport win32com.client\r\n\r\npythoncom.CoInitialize()\r\napp = win32com.client.Dispatch(\"Excel.Application\")\r\ntry:\r\n    #######################\r\n    # Your code goes here #\r\n    #######################\r\nfinally:\r\n    app.Quit()\r\n    del app\r\n    pythoncom.CoUninitialize()\r\n```\r\n\r\n#### 2.) with safexl\r\n```python\r\nimport safexl\r\n\r\nwith safexl.application(kill_after=True) as app:\r\n    #######################\r\n    # Your code goes here #\r\n    #######################\r\n```\r\nAs you can see, using safexl results in a lot less boilerplate code, from 9 lines to 2.\r\n\r\nThe `application` wrapper comes with 3 boolean parameters to indicate what you would like to do with the application once your \r\n`with` block is complete:\r\n1. `kill_after` - kill the Excel process upon leaving the `with` block\r\n2. `maximize` - Optional / Defaults to `True` - Will not be used if you set `kill_after=True`. Maximizes each Excel Window for\r\neach Workbook added during the `with` block.\r\n3. `include_addins` - Optional / Defaults to `False` - Will not be used if you set `kill_after=True`. Loads your installed Excel \r\nAdd-ins to the newly created instance (with a performance hit to do so).\r\n\r\nIn the event of an error occuring inside your `with` block, the `safexl.application` cleanup process will carefully remove any new\r\nworkbooks you've opened in Excel, leaving any workbooks you already had open prior to the `with` block untouched. The same goes \r\nfor if you chose to set `kill_after=True`; only the Workbooks you create inside the `with` block will be closed.\r\nIn addition to the `application` wrapper, I have included an handful of other tools to make working with Excel even easier, including:\r\n\r\n* is_excel_open()\r\n* kill_all_instances_of_excel()\r\n* close_workbooks(app, workbooks)\r\n* see_excel(app, window_state)\r\n* workbooks_currently_open(app)\r\n* last_row(worksheet)\r\n* last_column(worksheet)\r\n* worksheet_name_sanitization(worksheet_name)\r\n\r\n----------------------------------------------------------------------------------------------------------------------------------\r\n\r\n## Performance\r\nA number of performance enhancing options can be set on Excel Application objects, and will come in handy most whenever you are \r\nworking with large workbooks and amounts of data. In my balance between allowing you the most freedom to do what you wish with the \r\napplication object and wrapping your object for safer error handling, I am yielding a bare pywin32 application object to you \r\ninside the `with` block. If you wish to take advantage of the various performance enhancing settings available natively in the \r\nExcel Application, I suggest using your own error handling inside the `with` block, to verify that the settings get switched back \r\nto normal when you're finished, even if you encounter an error during your work. Using safexl in this way would look something \r\nlike this:\r\n```python\r\nimport safexl\r\n\r\nwith safexl.application(kill_after=False) as app:\r\n    try:\r\n        app.ScreenUpdating = False\r\n        app.DisplayStatusBar = False\r\n        app.EnableEvents = False\r\n\r\n        wb = app.Workbooks.Add()\r\n        # can only set calculation once at least 1 workbook is open\r\n        app.Calculation = safexl.xl_constants.xlCalculationManual\r\n\r\n        #######################\r\n        # Your code goes here #\r\n        #######################\r\n        \r\n    except Exception as e:\r\n        # if you don't re-raise the error here, you will not be warned that an error occured \r\n        # or get the benefit of reading the error message\r\n        raise e\r\n    \r\n    else:\r\n        pass\r\n    \r\n    finally:\r\n        app.ScreenUpdating = True\r\n        app.DisplayStatusBar = True\r\n        app.EnableEvents = True\r\n        app.Calculation = safexl.xl_constants.xlCalculationAutomatic\r\n\r\n```\r\n\r\n##### A note on setting the Calculation\r\nUnfortunately, due to an oddity in the Excel Application OOP design, even though the Calculation mode is set on the Application object \r\n(instead of the Workbook object) if no workbooks are open or visible in your instance of the Application, then the constant for \r\nan \"#N/A\" error is returned, as seen by code like this:\r\n```\r\n\u003e\u003e\u003e import win32com.client\r\n\u003e\u003e\u003e import pythoncom\r\n\u003e\u003e\u003e pythoncom.CoInitialize()\r\n\u003e\u003e\u003e app = win32com.client.Dispatch(\"Excel.Application\")\r\n\u003e\u003e\u003e app.Calculation  # expect constant for #N/A\r\n-2146826246\r\n\u003e\u003e\u003e wb = app.Workbooks.Add()\r\n\u003e\u003e\u003e app.Calculation  # expect XlCalculation constant\r\n-4105\r\n\u003e\u003e\u003e wb.Close()\r\n\u003e\u003e\u003e app.Calculation  # expect constant for #N/A\r\n-2146826246\r\n```\r\nMore can be read about how Excel handles Calculation modes at these links:\r\n* [How Excel determines the current mode of calculation](https://docs.microsoft.com/en-us/office/troubleshoot/excel/current-mode-of-calculation)\r\n* [Excel: 'Unable to set the Calculation property of the Application class'](https://stackoverflow.com/questions/275630/excel-unable-to-set-the-calculation-property-of-the-application-class)\r\n\r\nSuffice it to say, even though we think about the calculation mode being an attribute of each individual workbook, it is actually \r\n__set__ at the application level. I'm assuming this was for performance and/or sanity reasons, but the end result is that you are unable to \r\nget or set a proper Calculation mode for the application until you open a workbook first.\r\n\r\n## Cookbook\r\n\r\n##### Create \u0026 Save Workbook without viewing Application\r\n```python\r\nimport safexl\r\n\r\nwith safexl.application(kill_after=True) as app:\r\n    wb = app.Workbooks.Add()\r\n    \r\n    #######################\r\n    # Your code goes here #\r\n    #######################\r\n    \r\n    wb.SaveAs(\"Cookbook.xlsx\")\r\n    wb.Close()\r\n```\r\n\r\n##### Create a Workbook \u0026 View it Without Saving\r\n```python\r\nimport safexl\r\n\r\nwith safexl.application(kill_after=False, maximize=True, include_addins=True) as app:\r\n    wb = app.Workbooks.Add()\r\n\r\n    #######################\r\n    # Your code goes here #\r\n    #######################\r\n```\r\n\r\n##### Minimize All Excel Windows that are Currently Open\r\n```python\r\nimport safexl\r\n\r\nwith safexl.application(kill_after=True) as app:\r\n    safexl.see_excel(app.Workbooks, safexl.xl_constants.xlMinimized)\r\n```\r\n\r\n##### Send Pandas Dataframe to Excel Worksheet\r\n```python\r\nimport safexl\r\nimport pandas as pd\r\ndata = {\r\n    'A': [1, 2, 3],\r\n    'B': [4, 5, 6],\r\n    'C': [7, 8, 9]\r\n    }\r\ndf = pd.DataFrame(data)\r\n\r\nwith safexl.application(kill_after=False) as app:\r\n    wb = app.Workbooks.Add()\r\n    ws = wb.ActiveSheet\r\n\r\n    df.to_clipboard(excel=True)\r\n    ws.Paste()\r\n    ws.Range(\"A1\").Select()  # Otherwise entire dataframe range will be selected upon viewing\r\n```\r\n\r\n----------------------------------------------------------------------------------------------------------------------------------\r\n## Similar Packages to Consider\r\n* [xlwings](https://docs.xlwings.org/en/stable/)\r\n* [OpenPyXL](https://openpyxl.readthedocs.io/en/stable/)\r\n* [XlsxWriter](https://xlsxwriter.readthedocs.io/)\r\n\r\n## Contact Me\r\n* ThePoetCoder at gmail.com\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthepoetcoder%2Fsafexl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthepoetcoder%2Fsafexl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthepoetcoder%2Fsafexl/lists"}