diff --git a/README.rst b/README.rst
index 1294e96b..a5f445e9 100644
--- a/README.rst
+++ b/README.rst
@@ -3,7 +3,7 @@ WinPython tools
Copyright @ 2012-2013 Pierre Raybaut
-Copyright @ 2014-2024+ The Winpython development team https://github.com/winpython/
+Copyright @ 2014-2025+ The Winpython development team https://github.com/winpython/
Licensed under the terms of the MIT License
(see winpython/__init__.py for details)
@@ -29,7 +29,7 @@ WinPython build toolchain
Dependencies
------------
-* Python3 >= 3.8
+* Python3 >= 3.9
Requirements
diff --git a/changelogs/WinPythondot-64bit-3.12.10.0.md b/changelogs/WinPythondot-64bit-3.12.10.0.md
new file mode 100644
index 00000000..31153ea2
--- /dev/null
+++ b/changelogs/WinPythondot-64bit-3.12.10.0.md
@@ -0,0 +1,29 @@
+## WinPython 3.12.10.0dot
+
+The following packages are included in WinPython-64bit v3.12.10.0dot .
+
+
+
+### Tools
+
+Name | Version | Description
+-----|---------|------------
+
+
+### Python packages
+
+Name | Version | Description
+-----|---------|------------
+[Python](http://www.python.org/) | 3.12.10 | Python programming language with standard library
+[build](https://pypi.org/project/build) | 1.2.2.post1 | A simple, correct Python build frontend
+[colorama](https://pypi.org/project/colorama) | 0.4.6 | Cross-platform colored terminal text.
+[packaging](https://pypi.org/project/packaging) | 24.2 | Core utilities for Python packages
+[pip](https://pypi.org/project/pip) | 25.0.1 | The PyPA recommended tool for installing Python packages.
+[pyproject-hooks](https://pypi.org/project/pyproject-hooks) | 1.1.0 | Wrappers to call pyproject.toml-based build backend hooks.
+[setuptools](https://pypi.org/project/setuptools) | 75.8.2 | Easily download, build, install, upgrade, and uninstall Python packages
+[sqlite-bro](https://pypi.org/project/sqlite-bro) | 0.13.1 | a graphic SQLite Client in 1 Python file
+[sv-ttk](https://pypi.org/project/sv-ttk) | 2.6.0 | A gorgeous theme for Tkinter, based on Windows 11's UI
+[wheel](https://pypi.org/project/wheel) | 0.45.1 | A built-package format for Python
+[winpython](https://pypi.org/project/winpython) | 15.3.20250425 | WinPython distribution tools, including WPPM
+
+
diff --git a/changelogs/WinPythondot-64bit-3.12.10.0_History.md b/changelogs/WinPythondot-64bit-3.12.10.0_History.md
new file mode 100644
index 00000000..aef40183
--- /dev/null
+++ b/changelogs/WinPythondot-64bit-3.12.10.0_History.md
@@ -0,0 +1,22 @@
+## History of changes for WinPython-64bit 3.12.10.0dot
+
+The following changes were made to WinPython-64bit distribution since version 3.12.9.0dot.
+
+
+
+### Python packages
+
+New packages:
+
+ * [sv-ttk](https://pypi.org/project/sv-ttk) 2.6.0 (A gorgeous theme for Tkinter, based on Windows 11's UI)
+
+Upgraded packages:
+
+ * [pip](https://pypi.org/project/pip) 24.3.1 → 25.0.1 (The PyPA recommended tool for installing Python packages.)
+ * [Python](http://www.python.org/) 3.12.9 → 3.12.10 (Python programming language with standard library)
+ * [setuptools](https://pypi.org/project/setuptools) 75.6.0 → 75.8.2 (Easily download, build, install, upgrade, and uninstall Python packages)
+ * [winpython](https://pypi.org/project/winpython) 13.1.20250215 → 15.3.20250425 (WinPython distribution tools, including WPPM)
+
+
+
+* * *
diff --git a/changelogs/WinPythondot-64bit-3.13.3.0.md b/changelogs/WinPythondot-64bit-3.13.3.0.md
new file mode 100644
index 00000000..210bbc6d
--- /dev/null
+++ b/changelogs/WinPythondot-64bit-3.13.3.0.md
@@ -0,0 +1,29 @@
+## WinPython 3.13.3.0dot
+
+The following packages are included in WinPython-64bit v3.13.3.0dot .
+
+
+
+### Tools
+
+Name | Version | Description
+-----|---------|------------
+
+
+### Python packages
+
+Name | Version | Description
+-----|---------|------------
+[Python](http://www.python.org/) | 3.13.3 | Python programming language with standard library
+[build](https://pypi.org/project/build) | 1.2.2.post1 | A simple, correct Python build frontend
+[colorama](https://pypi.org/project/colorama) | 0.4.6 | Cross-platform colored terminal text.
+[packaging](https://pypi.org/project/packaging) | 24.2 | Core utilities for Python packages
+[pip](https://pypi.org/project/pip) | 25.0.1 | The PyPA recommended tool for installing Python packages.
+[pyproject-hooks](https://pypi.org/project/pyproject-hooks) | 1.1.0 | Wrappers to call pyproject.toml-based build backend hooks.
+[setuptools](https://pypi.org/project/setuptools) | 75.8.2 | Easily download, build, install, upgrade, and uninstall Python packages
+[sqlite-bro](https://pypi.org/project/sqlite-bro) | 0.13.1 | a graphic SQLite Client in 1 Python file
+[sv-ttk](https://pypi.org/project/sv-ttk) | 2.6.0 | A gorgeous theme for Tkinter, based on Windows 11's UI
+[wheel](https://pypi.org/project/wheel) | 0.45.1 | A built-package format for Python
+[winpython](https://pypi.org/project/winpython) | 15.3.20250425 | WinPython distribution tools, including WPPM
+
+
diff --git a/changelogs/WinPythondot-64bit-3.13.3.0_History.md b/changelogs/WinPythondot-64bit-3.13.3.0_History.md
new file mode 100644
index 00000000..105c04ff
--- /dev/null
+++ b/changelogs/WinPythondot-64bit-3.13.3.0_History.md
@@ -0,0 +1,22 @@
+## History of changes for WinPython-64bit 3.13.3.0dot
+
+The following changes were made to WinPython-64bit distribution since version 3.13.2.0dot.
+
+
+
+### Python packages
+
+New packages:
+
+ * [sv-ttk](https://pypi.org/project/sv-ttk) 2.6.0 (A gorgeous theme for Tkinter, based on Windows 11's UI)
+
+Upgraded packages:
+
+ * [pip](https://pypi.org/project/pip) 24.3.1 → 25.0.1 (The PyPA recommended tool for installing Python packages.)
+ * [Python](http://www.python.org/) 3.13.2 → 3.13.3 (Python programming language with standard library)
+ * [setuptools](https://pypi.org/project/setuptools) 75.6.0 → 75.8.2 (Easily download, build, install, upgrade, and uninstall Python packages)
+ * [winpython](https://pypi.org/project/winpython) 13.1.20250222 → 15.3.20250425 (WinPython distribution tools, including WPPM)
+
+
+
+* * *
diff --git a/changelogs/WinPythonslim-64bit-3.12.10.0.md b/changelogs/WinPythonslim-64bit-3.12.10.0.md
new file mode 100644
index 00000000..a8084d3f
--- /dev/null
+++ b/changelogs/WinPythonslim-64bit-3.12.10.0.md
@@ -0,0 +1,515 @@
+## WinPython 3.12.10.0slim
+
+The following packages are included in WinPython-64bit v3.12.10.0slim .
+
+
+
+### Tools
+
+Name | Version | Description
+-----|---------|------------
+[Pandoc](https://pandoc.org) | 3.1.9 | an universal document converter
+
+### Python packages
+
+Name | Version | Description
+-----|---------|------------
+[Python](http://www.python.org/) | 3.12.10 | Python programming language with standard library
+[absl-py](https://pypi.org/project/absl-py) | 2.0.0 | Abseil Python Common Libraries, see https://github.com/abseil/abseil-py.
+[adbc-driver-manager](https://pypi.org/project/adbc-driver-manager) | 1.3.0 | A generic entrypoint for ADBC drivers.
+[aiofiles](https://pypi.org/project/aiofiles) | 23.2.1 | File support for asyncio.
+[aiohappyeyeballs](https://pypi.org/project/aiohappyeyeballs) | 2.4.4 | Happy Eyeballs for asyncio
+[aiohttp](https://pypi.org/project/aiohttp) | 3.11.11 | Async http client/server framework (asyncio)
+[aiosignal](https://pypi.org/project/aiosignal) | 1.3.1 | aiosignal: a list of registered asynchronous callbacks
+[aiosqlite](https://pypi.org/project/aiosqlite) | 0.20.0 | asyncio bridge to the standard sqlite3 module
+[alabaster](https://pypi.org/project/alabaster) | 0.7.16 | A light, configurable Sphinx theme
+[alembic](https://pypi.org/project/alembic) | 1.15.1 | A database migration tool for SQLAlchemy.
+[altair](https://pypi.org/project/altair) | 5.5.0 | Vega-Altair: A declarative statistical visualization library for Python.
+[aniso8601](https://pypi.org/project/aniso8601) | 9.0.1 | A library for parsing ISO 8601 strings.
+[annotated-types](https://pypi.org/project/annotated-types) | 0.6.0 | Reusable constraint types to use with typing.Annotated
+[ansicolors](https://pypi.org/project/ansicolors) | 1.1.8 | ANSI colors for Python
+[anthropic](https://pypi.org/project/anthropic) | 0.49.0 | The official Python library for the anthropic API
+[anyio](https://pypi.org/project/anyio) | 4.8.0 | High level compatibility layer for multiple asynchronous event loop implementations
+[anywidget](https://pypi.org/project/anywidget) | 0.9.12 | custom jupyter widgets made easy
+[appdirs](https://pypi.org/project/appdirs) | 1.4.4 | A small Python module for determining appropriate platform-specific dirs, e.g. a "user data dir".
+[argon2-cffi](https://pypi.org/project/argon2-cffi) | 23.1.0 | Argon2 for Python
+[argon2-cffi-bindings](https://pypi.org/project/argon2-cffi-bindings) | 21.2.0 | Low-level CFFI bindings for Argon2
+[array-api-compat](https://pypi.org/project/array-api-compat) | 1.11.1 | A wrapper around NumPy and other array libraries to make them compatible with the Array API standard
+[arrow](https://pypi.org/project/arrow) | 1.3.0 | Better dates & times for Python
+[asgi-csrf](https://pypi.org/project/asgi-csrf) | 0.9 | ASGI middleware for protecting against CSRF attacks
+[asgiref](https://pypi.org/project/asgiref) | 3.8.1 | ASGI specs, helper code, and adapters
+[asn1crypto](https://pypi.org/project/asn1crypto) | 1.5.1 | Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12,
+[asteval](https://pypi.org/project/asteval) | 0.9.31 | Safe, minimalistic evaluator of python expression using ast module
+[astroid](https://pypi.org/project/astroid) | 3.1.0 | An abstract syntax tree for Python with inference support.
+[astropy](https://pypi.org/project/astropy) | 6.1.6 | Astronomy and astrophysics core library
+[astropy-iers-data](https://pypi.org/project/astropy-iers-data) | 0.2024.12.23.0.33.24 | IERS Earth Rotation and Leap Second tables for the astropy core package
+[asttokens](https://pypi.org/project/asttokens) | 2.4.1 | Annotate AST trees with source code positions
+[async-lru](https://pypi.org/project/async-lru) | 2.0.4 | Simple LRU cache for asyncio
+[asyncssh](https://pypi.org/project/asyncssh) | 2.20.0 | AsyncSSH: Asynchronous SSHv2 client and server library
+[atomicwrites](https://pypi.org/project/atomicwrites) | 1.4.0 | Atomic file writes.
+[attrs](https://pypi.org/project/attrs) | 23.2.0 | Classes Without Boilerplate
+[autopep8](https://pypi.org/project/autopep8) | 2.0.4 | A tool that automatically formats Python code to conform to the PEP 8 style guide
+[azure-core](https://pypi.org/project/azure-core) | 1.32.0 | Microsoft Azure Core Library for Python
+[azure-cosmos](https://pypi.org/project/azure-cosmos) | 4.9.0 | Microsoft Azure Cosmos Client Library for Python
+[azure-identity](https://pypi.org/project/azure-identity) | 1.21.0 | Microsoft Azure Identity Library for Python
+[babel](https://pypi.org/project/babel) | 2.16.0 | Internationalization utilities
+[baresql](https://pypi.org/project/baresql) | 1.0.0 | playing SQL directly on Python datas
+[beautifulsoup4](https://pypi.org/project/beautifulsoup4) | 4.12.2 | Screen-scraping library
+[binaryornot](https://pypi.org/project/binaryornot) | 0.4.4 | Ultra-lightweight pure Python package to check if a file is binary or text.
+[black](https://pypi.org/project/black) | 25.1.0 | The uncompromising code formatter.
+[bleach](https://pypi.org/project/bleach) | 6.1.0 | An easy safelist-based HTML-sanitizing tool.
+[blinker](https://pypi.org/project/blinker) | 1.9.0 | Fast, simple object-to-object and broadcast signaling
+[bokeh](https://pypi.org/project/bokeh) | 3.7.2 | Interactive plots and applications in the browser from Python
+[branca](https://pypi.org/project/branca) | 0.8.0 | Generate complex HTML+JS pages with Python
+[brotli](https://pypi.org/project/brotli) | 1.1.0 | Python bindings for the Brotli compression library
+[build](https://pypi.org/project/build) | 1.2.2.post1 | A simple, correct Python build frontend
+[cachetools](https://pypi.org/project/cachetools) | 5.5.2 | Extensible memoizing collections and decorators
+[certifi](https://pypi.org/project/certifi) | 2025.1.31 | Python package for providing Mozilla's CA Bundle.
+[cffi](https://pypi.org/project/cffi) | 1.17.1 | Foreign Function Interface for Python calling C code.
+[chardet](https://pypi.org/project/chardet) | 5.2.0 | Universal encoding detector for Python 3
+[charset-normalizer](https://pypi.org/project/charset-normalizer) | 3.4.0 | The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet.
+[clarabel](https://pypi.org/project/clarabel) | 0.10.0 | Clarabel Conic Interior Point Solver for Rust / Python
+[click](https://pypi.org/project/click) | 8.1.8 | Composable command line interface toolkit
+[click-default-group](https://pypi.org/project/click-default-group) | 1.2.4 | click_default_group
+[cloudpickle](https://pypi.org/project/cloudpickle) | 3.1.1 | Pickler class to extend the standard pickle.Pickler functionality
+[cohere](https://pypi.org/project/cohere) | 5.13.12 |
+[colorama](https://pypi.org/project/colorama) | 0.4.6 | Cross-platform colored terminal text.
+[colorcet](https://pypi.org/project/colorcet) | 3.1.0 | Collection of perceptually uniform colormaps
+[colorlog](https://pypi.org/project/colorlog) | 6.8.2 | Add colours to the output of Python's logging module.
+[comm](https://pypi.org/project/comm) | 0.2.2 | Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc.
+[contourpy](https://pypi.org/project/contourpy) | 1.3.1 | Python library for calculating contours of 2D quadrilateral grids
+[cookiecutter](https://pypi.org/project/cookiecutter) | 2.6.0 | A command-line utility that creates projects from project templates, e.g.
+[cryptography](https://pypi.org/project/cryptography) | 44.0.0 | cryptography is a package which provides cryptographic recipes and primitives to Python developers.
+[cvxopt](https://pypi.org/project/cvxopt) | 1.3.2 | Convex optimization package
+[cvxpy](https://pypi.org/project/cvxpy) | 1.6.4 | A domain-specific language for modeling convex optimization problems in Python.
+[cycler](https://pypi.org/project/cycler) | 0.12.1 | Composable style cycles
+[cython](https://pypi.org/project/cython) | 3.0.12 | The Cython compiler for writing C extensions in the Python language.
+[cytoolz](https://pypi.org/project/cytoolz) | 1.0.1 | Cython implementation of Toolz: High performance functional utilities
+[dask](https://pypi.org/project/dask) | 2025.3.0 | Parallel PyData with Task Scheduling
+[datasette](https://pypi.org/project/datasette) | 0.65.1 | An open source multi-tool for exploring and publishing data
+[datasette-graphql](https://pypi.org/project/datasette-graphql) | 2.2 | Datasette plugin providing an automatic GraphQL API for your SQLite databases
+[datashader](https://pypi.org/project/datashader) | 0.17.0 | Data visualization toolchain based on aggregating into a grid
+[deap](https://pypi.org/project/deap) | 1.4.2 | Distributed Evolutionary Algorithms in Python
+[debugpy](https://pypi.org/project/debugpy) | 1.8.0 | An implementation of the Debug Adapter Protocol for Python
+[decorator](https://pypi.org/project/decorator) | 5.1.1 | Decorators for Humans
+[defusedxml](https://pypi.org/project/defusedxml) | 0.7.1 | XML bomb protection for Python stdlib modules
+[deprecated](https://pypi.org/project/deprecated) | 1.2.14 | Python @deprecated decorator to deprecate old python classes, functions or methods.
+[diff-match-patch](https://pypi.org/project/diff-match-patch) | 20241021 | Repackaging of Google's Diff Match and Patch libraries.
+[dill](https://pypi.org/project/dill) | 0.3.9 | serialize all of Python
+[distributed](https://pypi.org/project/distributed) | 2025.3.0 | Distributed scheduler for Dask
+[distro](https://pypi.org/project/distro) | 1.8.0 | Distro - an OS platform information API
+[django](https://pypi.org/project/django) | 5.0.7 | A high-level Python web framework that encourages rapid development and clean, pragmatic design.
+[dnspython](https://pypi.org/project/dnspython) | 2.6.1 | DNS toolkit
+[docstring-to-markdown](https://pypi.org/project/docstring-to-markdown) | 0.15 | On the fly conversion of Python docstrings to markdown
+[docutils](https://pypi.org/project/docutils) | 0.21.2 | Docutils -- Python Documentation Utilities
+[duckdb](https://pypi.org/project/duckdb) | 1.2.2 | DuckDB in-process database
+[entrypoints](https://pypi.org/project/entrypoints) | 0.4 | Discover and load entry points from installed packages.
+[et-xmlfile](https://pypi.org/project/et-xmlfile) | 1.1.0 | An implementation of lxml.xmlfile for the standard library
+[eval-type-backport](https://pypi.org/project/eval-type-backport) | 0.2.2 | Like `typing._eval_type`, but lets older Python versions use newer typing features.
+[executing](https://pypi.org/project/executing) | 2.0.1 | Get the currently executing AST node of a frame, and other information
+[faker](https://pypi.org/project/faker) | 36.1.1 | Faker is a Python package that generates fake data for you.
+[fast-histogram](https://pypi.org/project/fast-histogram) | 0.14 | Fast simple 1D and 2D histograms
+[fastapi](https://pypi.org/project/fastapi) | 0.115.8 | FastAPI framework, high performance, easy to learn, fast to code, ready for production
+[fastavro](https://pypi.org/project/fastavro) | 1.10.0 | Fast read/write of AVRO files
+[fastjsonschema](https://pypi.org/project/fastjsonschema) | 2.18.0 | Fastest Python implementation of JSON schema
+[filelock](https://pypi.org/project/filelock) | 3.17.0 | A platform independent file lock.
+[flake8](https://pypi.org/project/flake8) | 7.1.1 | the modular source code checker: pep8 pyflakes and co
+[flask](https://pypi.org/project/flask) | 3.1.0 | A simple framework for building complex web applications.
+[flexcache](https://pypi.org/project/flexcache) | 0.3 | Saves and loads to the cache a transformed versions of a source object.
+[flexparser](https://pypi.org/project/flexparser) | 0.4 | Parsing made fun ... using typing.
+[flit](https://pypi.org/project/flit) | 3.10.1 | A simple packaging tool for simple packages.
+[flit-core](https://pypi.org/project/flit-core) | 3.10.1 | Distribution-building parts of Flit. See flit package for more information
+[folium](https://pypi.org/project/folium) | 0.19.5 | Make beautiful maps with Leaflet.js & Python
+[fonttools](https://pypi.org/project/fonttools) | 4.55.3 | Tools to manipulate font files
+[fqdn](https://pypi.org/project/fqdn) | 1.5.1 | Validates fully-qualified domain names against RFC 1123, so that they are acceptable to modern bowsers
+[frozenlist](https://pypi.org/project/frozenlist) | 1.5.0 | A list-like structure which implements collections.abc.MutableSequence
+[fsspec](https://pypi.org/project/fsspec) | 2024.6.1 | File-system specification
+[fuzzywuzzy](https://pypi.org/project/fuzzywuzzy) | 0.18.0 | Fuzzy string matching in python
+[geographiclib](https://pypi.org/project/geographiclib) | 2.0 | The geodesic routines from GeographicLib
+[geopandas](https://pypi.org/project/geopandas) | 1.0.1 | Geographic pandas extensions
+[geopy](https://pypi.org/project/geopy) | 2.4.1 | Python Geocoding Toolbox
+[gitdb](https://pypi.org/project/gitdb) | 4.0.10 | Git Object Database
+[gitpython](https://pypi.org/project/gitpython) | 3.1.32 | GitPython is a Python library used to interact with Git repositories
+[google-auth](https://pypi.org/project/google-auth) | 2.37.0 | Google Authentication Library
+[graphene](https://pypi.org/project/graphene) | 3.3 | GraphQL Framework for Python
+[graphql-core](https://pypi.org/project/graphql-core) | 3.2.3 | GraphQL implementation for Python, a port of GraphQL.js, the JavaScript reference implementation for GraphQL.
+[graphql-relay](https://pypi.org/project/graphql-relay) | 3.2.0 | Relay library for graphql-core
+[greenlet](https://pypi.org/project/greenlet) | 3.1.1 | Lightweight in-process concurrent programming
+[griffe](https://pypi.org/project/griffe) | 1.5.5 | Signatures for entire Python programs.
+[groq](https://pypi.org/project/groq) | 0.13.1 | The official Python library for the groq API
+[guidata](https://pypi.org/project/guidata) | 3.7.1 | Automatic GUI generation for easy dataset editing and display
+[h11](https://pypi.org/project/h11) | 0.14.0 | A pure-Python, bring-your-own-I/O implementation of HTTP/1.1
+[h2](https://pypi.org/project/h2) | 4.1.0 | HTTP/2 State-Machine based protocol implementation
+[h5py](https://pypi.org/project/h5py) | 3.12.1 | Read and write HDF5 files from Python
+[hatchling](https://pypi.org/project/hatchling) | 1.27.0 | Modern, extensible Python build backend
+[holoviews](https://pypi.org/project/holoviews) | 1.20.2 | A high-level plotting API for the PyData ecosystem built on HoloViews.
+[hpack](https://pypi.org/project/hpack) | 4.1.0 | Pure-Python HPACK header encoding
+[html5lib](https://pypi.org/project/html5lib) | 1.1 | HTML parser based on the WHATWG HTML specification
+[httpcore](https://pypi.org/project/httpcore) | 1.0.5 | A minimal low-level HTTP client.
+[httpie](https://pypi.org/project/httpie) | 3.2.4 | HTTPie: modern, user-friendly command-line HTTP client for the API era.
+[httpx](https://pypi.org/project/httpx) | 0.27.2 | The next generation HTTP client.
+[httpx-sse](https://pypi.org/project/httpx-sse) | 0.4.0 | Consume Server-Sent Event (SSE) messages with HTTPX.
+[huggingface-hub](https://pypi.org/project/huggingface-hub) | 0.29.3 | Client library to download and publish models, datasets and other repos on the huggingface.co hub
+[hupper](https://pypi.org/project/hupper) | 1.12 | Integrated process monitor for developing and reloading daemons.
+[hvplot](https://pypi.org/project/hvplot) | 0.11.2 | A high-level plotting API for the PyData ecosystem built on HoloViews.
+[hypercorn](https://pypi.org/project/hypercorn) | 0.17.3 | A ASGI Server based on Hyper libraries and inspired by Gunicorn
+[hyperframe](https://pypi.org/project/hyperframe) | 6.1.0 | Pure-Python HTTP/2 framing
+[hypothesis](https://pypi.org/project/hypothesis) | 6.130.4 | A library for property-based testing
+[id](https://pypi.org/project/id) | 1.5.0 | A tool for generating OIDC identities
+[idna](https://pypi.org/project/idna) | 3.10 | Internationalized Domain Names in Applications (IDNA)
+[imageio](https://pypi.org/project/imageio) | 2.37.0 | Library for reading and writing a wide range of image, video, scientific, and volumetric data formats.
+[imagesize](https://pypi.org/project/imagesize) | 1.4.1 | Getting image size from png/jpeg/jpeg2000/gif file
+[imbalanced-learn](https://pypi.org/project/imbalanced-learn) | 0.13.0 | Toolbox for imbalanced dataset in machine learning
+[importlib-metadata](https://pypi.org/project/importlib-metadata) | 8.6.1 | Read metadata from Python packages
+[inflection](https://pypi.org/project/inflection) | 0.5.1 | A port of Ruby on Rails inflector to Python
+[iniconfig](https://pypi.org/project/iniconfig) | 2.0.0 | brain-dead simple config-ini parsing
+[intervaltree](https://pypi.org/project/intervaltree) | 3.0.2 | Editable interval tree data structure for Python 2 and 3
+[ipycanvas](https://pypi.org/project/ipycanvas) | 0.13.3 | Interactive widgets library exposing the browser's Canvas API
+[ipykernel](https://pypi.org/project/ipykernel) | 6.29.5 | IPython Kernel for Jupyter
+[ipyleaflet](https://pypi.org/project/ipyleaflet) | 0.19.2 | A Jupyter widget for dynamic Leaflet maps
+[ipympl](https://pypi.org/project/ipympl) | 0.9.7 | Matplotlib Jupyter Extension
+[ipython](https://pypi.org/project/ipython) | 8.34.0 | IPython: Productive Interactive Computing
+[ipython-genutils](https://pypi.org/project/ipython-genutils) | 0.2.0 | Vestigial utilities from IPython
+[ipython-sql](https://pypi.org/project/ipython-sql) | 0.5.0 | RDBMS access via IPython
+[ipywidgets](https://pypi.org/project/ipywidgets) | 8.1.6 | Jupyter interactive widgets
+[isoduration](https://pypi.org/project/isoduration) | 20.11.0 | Operations with ISO 8601 durations
+[isort](https://pypi.org/project/isort) | 5.13.2 | A Python utility / library to sort Python imports.
+[itsdangerous](https://pypi.org/project/itsdangerous) | 2.2.0 | Safely pass data to untrusted environments and back.
+[janus](https://pypi.org/project/janus) | 2.0.0 | Mixed sync-async queue to interoperate between asyncio tasks and classic threads
+[jaraco-classes](https://pypi.org/project/jaraco-classes) | 3.4.0 | Utility functions for Python class constructs
+[jaraco-context](https://pypi.org/project/jaraco-context) | 6.0.1 | Useful decorators and context managers
+[jaraco-functools](https://pypi.org/project/jaraco-functools) | 4.1.0 | Functools like those found in stdlib
+[jedi](https://pypi.org/project/jedi) | 0.19.2 | An autocompletion tool for Python that can be used for text editors.
+[jellyfish](https://pypi.org/project/jellyfish) | 1.1.3 | Approximate and phonetic matching of strings.
+[jinja2](https://pypi.org/project/jinja2) | 3.1.2 | A very fast and expressive template engine.
+[jiter](https://pypi.org/project/jiter) | 0.8.2 | Fast iterable JSON parser.
+[joblib](https://pypi.org/project/joblib) | 1.4.2 | Lightweight pipelining with Python functions
+[json5](https://pypi.org/project/json5) | 0.9.14 | A Python implementation of the JSON5 data format.
+[jsonpatch](https://pypi.org/project/jsonpatch) | 1.33 | Apply JSON-Patches (RFC 6902)
+[jsonpath-python](https://pypi.org/project/jsonpath-python) | 1.0.6 | A more powerful JSONPath implementation in modern python
+[jsonpointer](https://pypi.org/project/jsonpointer) | 2.4 | Identify specific nodes in a JSON document (RFC 6901)
+[jsonschema](https://pypi.org/project/jsonschema) | 4.19.2 | An implementation of JSON Schema validation for Python
+[jsonschema-specifications](https://pypi.org/project/jsonschema-specifications) | 2023.12.1 | The JSON Schema meta-schemas and vocabularies, exposed as a Registry
+[julia](https://pypi.org/project/julia) | 0.6.2 | Julia/Python bridge with IPython support.
+[jupyter](https://pypi.org/project/jupyter) | 1.1.1 | Jupyter metapackage. Install all the Jupyter components in one go.
+[jupyter-bokeh](https://pypi.org/project/jupyter-bokeh) | 4.0.5 | A Jupyter extension for rendering Bokeh content.
+[jupyter-client](https://pypi.org/project/jupyter-client) | 8.6.3 | Jupyter protocol implementation and client libraries
+[jupyter-console](https://pypi.org/project/jupyter-console) | 6.6.3 | Jupyter terminal console
+[jupyter-core](https://pypi.org/project/jupyter-core) | 5.7.2 | Jupyter core package. A base package on which Jupyter projects rely.
+[jupyter-events](https://pypi.org/project/jupyter-events) | 0.12.0 | Jupyter Event System library
+[jupyter-leaflet](https://pypi.org/project/jupyter-leaflet) | 0.19.2 | ipyleaflet extensions for JupyterLab and Jupyter Notebook
+[jupyter-lsp](https://pypi.org/project/jupyter-lsp) | 2.2.5 | Multi-Language Server WebSocket proxy for Jupyter Notebook/Lab server
+[jupyter-server](https://pypi.org/project/jupyter-server) | 2.14.2 | The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications.
+[jupyter-server-terminals](https://pypi.org/project/jupyter-server-terminals) | 0.5.3 | A Jupyter Server Extension Providing Terminals.
+[jupyterlab](https://pypi.org/project/jupyterlab) | 4.4.1 | JupyterLab computational environment
+[jupyterlab-pygments](https://pypi.org/project/jupyterlab-pygments) | 0.3.0 | Pygments theme using JupyterLab CSS variables
+[jupyterlab-server](https://pypi.org/project/jupyterlab-server) | 2.27.3 | A set of server components for JupyterLab and JupyterLab like applications.
+[jupyterlab-widgets](https://pypi.org/project/jupyterlab-widgets) | 3.0.14 | Jupyter interactive widgets for JupyterLab
+[keras](https://pypi.org/project/keras) | 3.9.2 | Multi-backend Keras
+[keyring](https://pypi.org/project/keyring) | 25.6.0 | Store and access your passwords safely.
+[kiwisolver](https://pypi.org/project/kiwisolver) | 1.4.8 | A fast implementation of the Cassowary constraint solver
+[langchain](https://pypi.org/project/langchain) | 0.3.23 | Building applications with LLMs through composability
+[langchain-core](https://pypi.org/project/langchain-core) | 0.3.51 | Building applications with LLMs through composability
+[langchain-text-splitters](https://pypi.org/project/langchain-text-splitters) | 0.3.8 | LangChain text splitting utilities
+[langsmith](https://pypi.org/project/langsmith) | 0.3.24 | Client library to connect to the LangSmith LLM Tracing and Evaluation Platform.
+[lazy-loader](https://pypi.org/project/lazy-loader) | 0.4 | Makes it easy to load subpackages and functions on demand.
+[linkify-it-py](https://pypi.org/project/linkify-it-py) | 2.0.2 | Links recognition library with FULL unicode support.
+[llvmlite](https://pypi.org/project/llvmlite) | 0.44.0 | lightweight wrapper around basic LLVM functionality
+[lmfit](https://pypi.org/project/lmfit) | 1.3.1 | Least-Squares Minimization with Bounds and Constraints
+[locket](https://pypi.org/project/locket) | 1.0.0 | File-based locks for Python on Linux and Windows
+[logfire-api](https://pypi.org/project/logfire-api) | 3.5.3 | Shim for the Logfire SDK which does nothing unless Logfire is installed
+[lxml](https://pypi.org/project/lxml) | 5.3.0 | Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API.
+[mako](https://pypi.org/project/mako) | 1.3.5 | A super-fast templating language that borrows the best ideas from the existing templating languages.
+[markdown](https://pypi.org/project/markdown) | 3.7 | Python implementation of John Gruber's Markdown.
+[markdown-it-py](https://pypi.org/project/markdown-it-py) | 2.2.0 | Python port of markdown-it. Markdown parsing, done right!
+[markupsafe](https://pypi.org/project/markupsafe) | 3.0.2 | Safely add untrusted strings to HTML/XML markup.
+[matplotlib](https://pypi.org/project/matplotlib) | 3.10.1 | Python plotting package
+[matplotlib-inline](https://pypi.org/project/matplotlib-inline) | 0.1.7 | Inline Matplotlib backend for Jupyter
+[maturin](https://pypi.org/project/maturin) | 1.8.1 | Build and publish crates with pyo3, cffi and uniffi bindings as well as rust binaries as python packages
+[mccabe](https://pypi.org/project/mccabe) | 0.7.0 | McCabe checker, plugin for flake8
+[mdit-py-plugins](https://pypi.org/project/mdit-py-plugins) | 0.3.5 | Collection of plugins for markdown-it-py
+[mdurl](https://pypi.org/project/mdurl) | 0.1.2 | Markdown URL utilities
+[mercantile](https://pypi.org/project/mercantile) | 1.2.1 | Web mercator XYZ tile utilities
+[mergedeep](https://pypi.org/project/mergedeep) | 1.3.4 | A deep merge function for 🐍.
+[missingno](https://pypi.org/project/missingno) | 0.5.1 | Missing data visualization module for Python.
+[mistralai](https://pypi.org/project/mistralai) | 1.2.5 | Python Client SDK for the Mistral AI API.
+[mistune](https://pypi.org/project/mistune) | 2.0.5 | A sane Markdown parser with useful plugins and renderers
+[mizani](https://pypi.org/project/mizani) | 0.11.4 | Scales for Python
+[ml-dtypes](https://pypi.org/project/ml-dtypes) | 0.5.0 |
+[mlxtend](https://pypi.org/project/mlxtend) | 0.23.3 | Machine Learning Library Extensions
+[more-itertools](https://pypi.org/project/more-itertools) | 10.2.0 | More routines for operating on iterables, beyond itertools
+[mpl-scatter-density](https://pypi.org/project/mpl-scatter-density) | 0.7 | Matplotlib helpers to make density scatter plots
+[mpld3](https://pypi.org/project/mpld3) | 0.5.8 | D3 Viewer for Matplotlib
+[mpmath](https://pypi.org/project/mpmath) | 1.3.0 | Python library for arbitrary-precision floating-point arithmetic
+[msal](https://pypi.org/project/msal) | 1.30.0 | The Microsoft Authentication Library (MSAL) for Python library enables your app to access the Microsoft Cloud by supporting authentication of us
+[msal-extensions](https://pypi.org/project/msal-extensions) | 1.2.0 | Microsoft Authentication Library extensions (MSAL EX) provides a persistence API that can save your data on disk, encrypted on Windows, macOS an
+[msgpack](https://pypi.org/project/msgpack) | 1.1.0 | MessagePack serializer
+[multidict](https://pypi.org/project/multidict) | 6.1.0 | multidict implementation
+[multipledispatch](https://pypi.org/project/multipledispatch) | 1.0.0 | Multiple dispatch
+[mypy](https://pypi.org/project/mypy) | 1.15.0 | Optional static typing for Python
+[mypy-extensions](https://pypi.org/project/mypy-extensions) | 1.0.0 | Type system extensions for programs checked with the mypy type checker.
+[mysql-connector-python](https://pypi.org/project/mysql-connector-python) | 9.2.0 | A self-contained Python driver for communicating with MySQL servers, using an API that is compliant with the Python Database API Specification v
+[namex](https://pypi.org/project/namex) | 0.0.8 | A simple utility to separate the implementation of your Python package and its public API surface.
+[narwhals](https://pypi.org/project/narwhals) | 1.30.0 | Extremely lightweight compatibility layer between dataframe libraries
+[nbclient](https://pypi.org/project/nbclient) | 0.10.0 | A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor.
+[nbconvert](https://pypi.org/project/nbconvert) | 7.16.1 | Converting Jupyter Notebooks (.ipynb files) to other formats. Output formats include asciidoc, html, latex, markdown, pdf, py, rst, script.
+[nbformat](https://pypi.org/project/nbformat) | 5.10.4 | The Jupyter Notebook format
+[nest-asyncio](https://pypi.org/project/nest-asyncio) | 1.6.0 | Patch asyncio to allow nested event loops
+[networkx](https://pypi.org/project/networkx) | 3.4.2 | Python package for creating and manipulating graphs and networks
+[nh3](https://pypi.org/project/nh3) | 0.2.18 | Python bindings to the ammonia HTML sanitization library.
+[nltk](https://pypi.org/project/nltk) | 3.9.1 | Natural Language Toolkit
+[notebook](https://pypi.org/project/notebook) | 7.4.0 | Jupyter Notebook - A web-based notebook environment for interactive computing
+[notebook-shim](https://pypi.org/project/notebook-shim) | 0.2.4 | A shim layer for notebook traits and config
+[numba](https://pypi.org/project/numba) | 0.61.2 | compiling Python code using LLVM
+[numpy](https://pypi.org/project/numpy) | 2.2.4 | Fundamental package for array computing in Python
+[numpydoc](https://pypi.org/project/numpydoc) | 1.6.0 | Sphinx extension to support docstrings in Numpy format
+[openai](https://pypi.org/project/openai) | 1.72.0 | The official Python library for the openai API
+[opencv-python](https://pypi.org/project/opencv-python) | 4.11.0.86 | Wrapper package for OpenCV python bindings.
+[openpyxl](https://pypi.org/project/openpyxl) | 3.1.2 | A Python library to read/write Excel 2010 xlsx/xlsm files
+[optree](https://pypi.org/project/optree) | 0.14.0 | Optimized PyTree Utilities.
+[optuna](https://pypi.org/project/optuna) | 3.6.1 | A hyperparameter optimization framework
+[orjson](https://pypi.org/project/orjson) | 3.10.12 | Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy
+[osqp](https://pypi.org/project/osqp) | 0.6.7.post3 | OSQP: The Operator Splitting QP Solver
+[outcome](https://pypi.org/project/outcome) | 1.3.0.post0 | Capture the outcome of Python function calls.
+[overrides](https://pypi.org/project/overrides) | 7.7.0 | A decorator to automatically detect mismatch when overriding a method.
+[packaging](https://pypi.org/project/packaging) | 24.2 | Core utilities for Python packages
+[pandas](https://pypi.org/project/pandas) | 2.2.3 | Powerful data structures for data analysis, time series, and statistics
+[pandocfilters](https://pypi.org/project/pandocfilters) | 1.5.0 | Utilities for writing pandoc filters in python
+[panel](https://pypi.org/project/panel) | 1.6.2 | The powerful data exploration & web app framework for Python.
+[papermill](https://pypi.org/project/papermill) | 2.6.0 | Parameterize and run Jupyter and nteract Notebooks
+[param](https://pypi.org/project/param) | 2.2.0 | Make your Python code clearer and more reliable by declaring Parameters.
+[parso](https://pypi.org/project/parso) | 0.8.4 | A Python Parser
+[partd](https://pypi.org/project/partd) | 1.4.0 | Appendable key-value storage
+[pathspec](https://pypi.org/project/pathspec) | 0.11.0 | Utility library for gitignore style pattern matching of file paths.
+[patsy](https://pypi.org/project/patsy) | 0.5.6 | A Python package for describing statistical models and for building design matrices.
+[pep8](https://pypi.org/project/pep8) | 1.7.1 | Python style guide checker
+[pexpect](https://pypi.org/project/pexpect) | 4.8.0 | Pexpect allows easy control of interactive console applications.
+[pg8000](https://pypi.org/project/pg8000) | 1.23.0 | PostgreSQL interface library
+[pickleshare](https://pypi.org/project/pickleshare) | 0.7.5 | Tiny 'shelve'-like database with concurrency support
+[pillow](https://pypi.org/project/pillow) | 11.1.0 | Python Imaging Library (Fork)
+[pip](https://pypi.org/project/pip) | 25.0.1 | The PyPA recommended tool for installing Python packages.
+[platformdirs](https://pypi.org/project/platformdirs) | 4.3.6 | A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`.
+[plotly](https://pypi.org/project/plotly) | 6.0.1 | An open-source interactive data visualization library for Python
+[plotnine](https://pypi.org/project/plotnine) | 0.13.6 | A Grammar of Graphics for Python
+[plotpy](https://pypi.org/project/plotpy) | 2.7.2 | Curve and image plotting tools for Python/Qt applications
+[pluggy](https://pypi.org/project/pluggy) | 1.5.0 | plugin and hook calling mechanisms for python
+[ply](https://pypi.org/project/ply) | 3.11 | Python Lex & Yacc
+[polars](https://pypi.org/project/polars) | 1.27.1 | Blazingly fast DataFrame library
+[portalocker](https://pypi.org/project/portalocker) | 2.7.0 | Wraps the portalocker recipe for easy usage
+[prettytable](https://pypi.org/project/prettytable) | 3.3.0 | A simple Python library for easily displaying tabular data in a visually appealing ASCII table format
+[prince](https://pypi.org/project/prince) | 0.15.0 | Factor analysis in Python: PCA, CA, MCA, MFA, FAMD, GPA
+[priority](https://pypi.org/project/priority) | 2.0.0 | A pure-Python implementation of the HTTP/2 priority tree
+[prometheus-client](https://pypi.org/project/prometheus-client) | 0.21.1 | Python client for the Prometheus monitoring system.
+[prompt-toolkit](https://pypi.org/project/prompt-toolkit) | 3.0.50 | Library for building powerful interactive command lines in Python
+[propcache](https://pypi.org/project/propcache) | 0.2.1 | Accelerated property cache
+[protobuf](https://pypi.org/project/protobuf) | 5.27.3 |
+[psutil](https://pypi.org/project/psutil) | 5.9.8 | Cross-platform lib for process and system monitoring in Python.
+[psygnal](https://pypi.org/project/psygnal) | 0.11.1 | Fast python callback/event system modeled after Qt Signals
+[ptpython](https://pypi.org/project/ptpython) | 3.0.29 | Python REPL build on top of prompt_toolkit
+[ptyprocess](https://pypi.org/project/ptyprocess) | 0.7.0 | Run a subprocess in a pseudo terminal
+[pure-eval](https://pypi.org/project/pure-eval) | 0.2.2 | Safely evaluate AST nodes without side effects
+[pyarrow](https://pypi.org/project/pyarrow) | 19.0.1 | Python library for Apache Arrow
+[pyasn1](https://pypi.org/project/pyasn1) | 0.6.1 | Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)
+[pyasn1-modules](https://pypi.org/project/pyasn1-modules) | 0.4.1 | A collection of ASN.1-based protocols modules
+[pybind11](https://pypi.org/project/pybind11) | 2.13.6 | Seamless operability between C++11 and Python
+[pycodestyle](https://pypi.org/project/pycodestyle) | 2.12.0 | Python style guide checker
+[pycparser](https://pypi.org/project/pycparser) | 2.22 | C parser in Python
+[pyct](https://pypi.org/project/pyct) | 0.5.0 | Python package common tasks for users (e.g. copy examples, fetch data, ...)
+[pydantic](https://pypi.org/project/pydantic) | 2.10.6 | Data validation using Python type hints
+[pydantic-ai](https://pypi.org/project/pydantic-ai) | 0.0.24 | Agent Framework / shim to use Pydantic with LLMs
+[pydantic-ai-slim](https://pypi.org/project/pydantic-ai-slim) | 0.0.24 | Agent Framework / shim to use Pydantic with LLMs, slim package
+[pydantic-core](https://pypi.org/project/pydantic-core) | 2.27.2 | Core functionality for Pydantic validation and serialization
+[pydantic-graph](https://pypi.org/project/pydantic-graph) | 0.0.24 | Graph and state machine library
+[pydeck](https://pypi.org/project/pydeck) | 0.9.1 | Widget for deck.gl maps
+[pydocstyle](https://pypi.org/project/pydocstyle) | 6.3.0 | Python docstring style checker
+[pydub](https://pypi.org/project/pydub) | 0.25.1 | Manipulate audio with an simple and easy high level interface
+[pyerfa](https://pypi.org/project/pyerfa) | 2.0.1.4 | Python bindings for ERFA
+[pyflakes](https://pypi.org/project/pyflakes) | 3.2.0 | passive checker of Python programs
+[pygithub](https://pypi.org/project/pygithub) | 2.6.1 | Use the full Github API v3
+[pygments](https://pypi.org/project/pygments) | 2.19.1 | Pygments is a syntax highlighting package written in Python.
+[pyjwt](https://pypi.org/project/pyjwt) | 2.10.1 | JSON Web Token implementation in Python
+[pylint](https://pypi.org/project/pylint) | 3.1.0 | python code static checker
+[pylint-venv](https://pypi.org/project/pylint-venv) | 3.0.3 | pylint-venv provides a Pylint init-hook to use the same Pylint installation with different virtual environments.
+[pyls-spyder](https://pypi.org/project/pyls-spyder) | 0.4.0 | Spyder extensions for the python-lsp-server
+[pymongo](https://pypi.org/project/pymongo) | 4.10.1 | Python driver for MongoDB
+[pympler](https://pypi.org/project/pympler) | 1.1 | A development tool to measure, monitor and analyze the memory behavior of Python objects.
+[pynacl](https://pypi.org/project/pynacl) | 1.5.0 | Python binding to the Networking and Cryptography (NaCl) library
+[pynndescent](https://pypi.org/project/pynndescent) | 0.5.12 | Nearest Neighbor Descent
+[pyodbc](https://pypi.org/project/pyodbc) | 5.2.0 | DB API module for ODBC
+[pyogrio](https://pypi.org/project/pyogrio) | 0.10.0 | Vectorized spatial vector file format I/O using GDAL/OGR
+[pyomo](https://pypi.org/project/pyomo) | 6.9.1 | Pyomo: Python Optimization Modeling Objects
+[pypandoc](https://pypi.org/project/pypandoc) | 1.15 | Thin wrapper for pandoc.
+[pyparsing](https://pypi.org/project/pyparsing) | 3.2.1 | pyparsing module - Classes and methods to define and execute parsing grammars
+[pypdf](https://pypi.org/project/pypdf) | 5.1.0 | A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files
+[pyproj](https://pypi.org/project/pyproj) | 3.7.0 | Python interface to PROJ (cartographic projections and coordinate transformations library)
+[pyproject-hooks](https://pypi.org/project/pyproject-hooks) | 1.1.0 | Wrappers to call pyproject.toml-based build backend hooks.
+[pyqt5](https://pypi.org/project/pyqt5) | 5.15.10 | Python bindings for the Qt cross platform application toolkit
+[pyqt5-qt5](https://pypi.org/project/pyqt5-qt5) | 5.15.2 | The subset of a Qt installation needed by PyQt5.
+[pyqt5-sip](https://pypi.org/project/pyqt5-sip) | 12.16.1 | The sip module support for PyQt5
+[pyqtgraph](https://pypi.org/project/pyqtgraph) | 0.13.7 | Scientific Graphics and GUI Library for Python
+[pyqtwebengine](https://pypi.org/project/pyqtwebengine) | 5.15.6 | Python bindings for the Qt WebEngine framework
+[pyqtwebengine-qt5](https://pypi.org/project/pyqtwebengine-qt5) | 5.15.2 | The subset of a Qt installation needed by PyQtWebEngine.
+[pyserial](https://pypi.org/project/pyserial) | 3.5 | Python Serial Port Extension
+[pysocks](https://pypi.org/project/pysocks) | 1.7.1 | A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information.
+[pyspnego](https://pypi.org/project/pyspnego) | 0.11.2 | Windows Negotiate Authentication Client and Server
+[pytest](https://pypi.org/project/pytest) | 8.3.4 | pytest: simple powerful testing with Python
+[python-barcode](https://pypi.org/project/python-barcode) | 0.15.1 | Create standard barcodes with Python. No external modules needed. (optional Pillow support included).
+[python-dateutil](https://pypi.org/project/python-dateutil) | 2.8.2 | Extensions to the standard Python datetime module
+[python-dotenv](https://pypi.org/project/python-dotenv) | 1.0.1 | Read key-value pairs from a .env file and set them as environment variables
+[python-json-logger](https://pypi.org/project/python-json-logger) | 2.0.7 | A python library adding a json log formatter
+[python-lsp-black](https://pypi.org/project/python-lsp-black) | 2.0.0 | Black plugin for the Python LSP Server
+[python-lsp-jsonrpc](https://pypi.org/project/python-lsp-jsonrpc) | 1.1.2 | JSON RPC 2.0 server library
+[python-lsp-server](https://pypi.org/project/python-lsp-server) | 1.12.0 | Python Language Server for the Language Server Protocol
+[python-multipart](https://pypi.org/project/python-multipart) | 0.0.9 | A streaming multipart parser for Python
+[python-slugify](https://pypi.org/project/python-slugify) | 8.0.4 | A Python slugify application that also handles Unicode
+[pythonqwt](https://pypi.org/project/pythonqwt) | 0.14.4 | Qt plotting widgets for Python
+[pytoolconfig](https://pypi.org/project/pytoolconfig) | 1.3.1 | Python tool configuration
+[pytz](https://pypi.org/project/pytz) | 2024.2 | World timezone definitions, modern and historical
+[pyuca](https://pypi.org/project/pyuca) | 1.2 | a Python implementation of the Unicode Collation Algorithm
+[pyusb](https://pypi.org/project/pyusb) | 1.3.1 | Easy USB access for Python
+[pyviz-comms](https://pypi.org/project/pyviz-comms) | 3.0.3 | A JupyterLab extension for rendering HoloViz content.
+[pywavelets](https://pypi.org/project/pywavelets) | 1.8.0 | PyWavelets, wavelet transform module
+[pywin32](https://pypi.org/project/pywin32) | 308 | Python for Window Extensions
+[pywin32-ctypes](https://pypi.org/project/pywin32-ctypes) | 0.2.2 | A (partial) reimplementation of pywin32 using ctypes/cffi
+[pywinpty](https://pypi.org/project/pywinpty) | 2.0.14 | Pseudo terminal support for Windows from Python.
+[pyyaml](https://pypi.org/project/pyyaml) | 6.0.2 | YAML parser and emitter for Python
+[pyzmq](https://pypi.org/project/pyzmq) | 26.2.1 | Python bindings for 0MQ
+[qdarkstyle](https://pypi.org/project/qdarkstyle) | 3.2.3 | The most complete dark/light style sheet for C++/Python and Qt applications
+[qdldl](https://pypi.org/project/qdldl) | 0.1.7.post5 | QDLDL, a free LDL factorization routine.
+[qrcode](https://pypi.org/project/qrcode) | 8.0 | QR Code image generator
+[qstylizer](https://pypi.org/project/qstylizer) | 0.2.2 | Stylesheet Generator for PyQt{4-5}/PySide{1-2}
+[qtawesome](https://pypi.org/project/qtawesome) | 1.4.0 | FontAwesome icons in PyQt and PySide applications
+[qtconsole](https://pypi.org/project/qtconsole) | 5.6.1 | Jupyter Qt console
+[qtpy](https://pypi.org/project/qtpy) | 2.4.1 | Provides an abstraction layer on top of the various Qt bindings (PyQt5/6 and PySide2/6).
+[quantecon](https://pypi.org/project/quantecon) | 0.7.2 | Import the main names to top level.
+[quart](https://pypi.org/project/quart) | 0.20.0 | A Python ASGI web framework with the same API as Flask
+[rapidfuzz](https://pypi.org/project/rapidfuzz) | 3.9.6 | rapid fuzzy string matching
+[readme-renderer](https://pypi.org/project/readme-renderer) | 44.0 | readme_renderer is a library for rendering readme descriptions for Warehouse
+[redis](https://pypi.org/project/redis) | 5.0.8 | Python client for Redis database and key-value store
+[referencing](https://pypi.org/project/referencing) | 0.35.1 | JSON Referencing + Python
+[regex](https://pypi.org/project/regex) | 2024.11.6 | Alternative regular expression module, to replace re.
+[reportlab](https://pypi.org/project/reportlab) | 4.2.5 | The Reportlab Toolkit
+[requests](https://pypi.org/project/requests) | 2.32.3 | Python HTTP for Humans.
+[requests-ntlm](https://pypi.org/project/requests-ntlm) | 1.3.0 | This package allows for HTTP NTLM authentication using the requests library.
+[requests-toolbelt](https://pypi.org/project/requests-toolbelt) | 1.0.0 | A utility belt for advanced users of python-requests
+[rfc3339-validator](https://pypi.org/project/rfc3339-validator) | 0.1.4 | A pure python RFC3339 validator
+[rfc3986](https://pypi.org/project/rfc3986) | 2.0.0 | Validating URI References per RFC 3986
+[rfc3986-validator](https://pypi.org/project/rfc3986-validator) | 0.1.1 | Pure python rfc3986 validator
+[rich](https://pypi.org/project/rich) | 13.9.4 | Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal
+[rope](https://pypi.org/project/rope) | 1.12.0 | a python refactoring library...
+[rpds-py](https://pypi.org/project/rpds-py) | 0.22.3 | Python bindings to Rust's persistent data structures (rpds)
+[rsa](https://pypi.org/project/rsa) | 4.7.2 | Pure-Python RSA implementation
+[rtree](https://pypi.org/project/rtree) | 1.3.0 | R-Tree spatial index for Python GIS
+[rx](https://pypi.org/project/rx) | 3.2.0 | Reactive Extensions (Rx) for Python
+[scikit-image](https://pypi.org/project/scikit-image) | 0.25.2 | Image processing in Python
+[scikit-learn](https://pypi.org/project/scikit-learn) | 1.6.1 | A set of python modules for machine learning and data mining
+[scipy](https://pypi.org/project/scipy) | 1.15.2 | Fundamental algorithms for scientific computing in Python
+[scramp](https://pypi.org/project/scramp) | 1.4.5 | An implementation of the SCRAM protocol.
+[scs](https://pypi.org/project/scs) | 3.2.7.post2 | Splitting conic solver
+[seaborn](https://pypi.org/project/seaborn) | 0.13.2 | Statistical data visualization
+[send2trash](https://pypi.org/project/send2trash) | 1.8.3 | Send file to trash natively under Mac OS X, Windows and Linux
+[setuptools](https://pypi.org/project/setuptools) | 75.8.2 | Easily download, build, install, upgrade, and uninstall Python packages
+[shapely](https://pypi.org/project/shapely) | 2.0.6 | Manipulation and analysis of geometric objects
+[shellingham](https://pypi.org/project/shellingham) | 1.5.4 | Tool to Detect Surrounding Shell
+[simplejson](https://pypi.org/project/simplejson) | 3.19.3 | Simple, fast, extensible JSON encoder/decoder for Python
+[simpy](https://pypi.org/project/simpy) | 4.1.1 | Event discrete, process based simulation for Python.
+[six](https://pypi.org/project/six) | 1.16.0 | Python 2 and 3 compatibility utilities
+[sklearn-compat](https://pypi.org/project/sklearn-compat) | 0.1.3 | Ease support for compatible scikit-learn estimators across versions
+[skrub](https://pypi.org/project/skrub) | 0.5.1 | Prepping tables for machine learning
+[smmap](https://pypi.org/project/smmap) | 5.0.0 | A pure Python implementation of a sliding window memory map manager
+[sniffio](https://pypi.org/project/sniffio) | 1.3.0 | Sniff out which async library your code is running under
+[snowballstemmer](https://pypi.org/project/snowballstemmer) | 2.2.0 | This package provides 29 stemmers for 28 languages generated from Snowball algorithms.
+[sortedcontainers](https://pypi.org/project/sortedcontainers) | 2.4.0 | Sorted Containers -- Sorted List, Sorted Dict, Sorted Set
+[sounddevice](https://pypi.org/project/sounddevice) | 0.5.1 | Play and Record Sound with Python
+[soupsieve](https://pypi.org/project/soupsieve) | 2.6 | A modern CSS selector implementation for Beautiful Soup.
+[sphinx](https://pypi.org/project/sphinx) | 7.3.7 | Python documentation generator
+[sphinx-rtd-theme](https://pypi.org/project/sphinx-rtd-theme) | 3.0.2 | Read the Docs theme for Sphinx
+[sphinxcontrib-applehelp](https://pypi.org/project/sphinxcontrib-applehelp) | 2.0.0 | sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books
+[sphinxcontrib-devhelp](https://pypi.org/project/sphinxcontrib-devhelp) | 2.0.0 | sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents
+[sphinxcontrib-htmlhelp](https://pypi.org/project/sphinxcontrib-htmlhelp) | 2.1.0 | sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files
+[sphinxcontrib-jquery](https://pypi.org/project/sphinxcontrib-jquery) | 4.1 | Extension to include jQuery on newer Sphinx releases
+[sphinxcontrib-jsmath](https://pypi.org/project/sphinxcontrib-jsmath) | 1.0.1 | A sphinx extension which renders display math in HTML via JavaScript
+[sphinxcontrib-qthelp](https://pypi.org/project/sphinxcontrib-qthelp) | 2.0.0 | sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents
+[sphinxcontrib-serializinghtml](https://pypi.org/project/sphinxcontrib-serializinghtml) | 2.0.0 | sphinxcontrib-serializinghtml is a sphinx extension which outputs "serialized" HTML files (json and pickle)
+[spyder](https://pypi.org/project/spyder) | 6.0.5 | The Scientific Python Development Environment
+[spyder-kernels](https://pypi.org/project/spyder-kernels) | 3.0.3 | Jupyter kernels for Spyder's console
+[sqlalchemy](https://pypi.org/project/sqlalchemy) | 2.0.38 | Database Abstraction Library
+[sqlite-bro](https://pypi.org/project/sqlite-bro) | 0.13.1 | a graphic SQLite Client in 1 Python file
+[sqlite-fts4](https://pypi.org/project/sqlite-fts4) | 1.0.3 | Python functions for working with SQLite FTS4 search
+[sqlite-utils](https://pypi.org/project/sqlite-utils) | 3.38 | CLI tool and Python library for manipulating SQLite databases
+[sqlparse](https://pypi.org/project/sqlparse) | 0.5.3 | A non-validating SQL parser.
+[squarify](https://pypi.org/project/squarify) | 0.4.4 | Pure Python implementation of the squarify treemap layout algorithm
+[sspilib](https://pypi.org/project/sspilib) | 0.2.0 | SSPI API bindings for Python
+[stack-data](https://pypi.org/project/stack-data) | 0.6.3 | Extract data from python stack frames and tracebacks for informative displays
+[starlette](https://pypi.org/project/starlette) | 0.45.3 | The little ASGI library that shines.
+[statsmodels](https://pypi.org/project/statsmodels) | 0.14.4 | Statistical computations and models for Python
+[streamlit](https://pypi.org/project/streamlit) | 1.44.0 | A faster way to build and share data apps
+[superqt](https://pypi.org/project/superqt) | 0.7.1 | Missing widgets and components for PyQt/PySide
+[sv-ttk](https://pypi.org/project/sv-ttk) | 2.6.0 | A gorgeous theme for Tkinter, based on Windows 11's UI
+[sympy](https://pypi.org/project/sympy) | 1.13.3 | Computer algebra system (CAS) in Python
+[tabulate](https://pypi.org/project/tabulate) | 0.9.0 | Pretty-print tabular data
+[tblib](https://pypi.org/project/tblib) | 3.0.0 | Traceback serialization library.
+[tenacity](https://pypi.org/project/tenacity) | 9.0.0 | Retry code until it succeeds
+[termcolor](https://pypi.org/project/termcolor) | 2.5.0 | ANSI color formatting for output in terminal
+[terminado](https://pypi.org/project/terminado) | 0.18.1 | Tornado websocket backend for the Xterm.js Javascript terminal emulator library.
+[text-unidecode](https://pypi.org/project/text-unidecode) | 1.3 | The most basic Text::Unidecode port
+[textdistance](https://pypi.org/project/textdistance) | 4.6.3 | Compute distance between the two texts.
+[thefuzz](https://pypi.org/project/thefuzz) | 0.22.1 | Fuzzy string matching in python
+[threadpoolctl](https://pypi.org/project/threadpoolctl) | 3.5.0 | threadpoolctl
+[three-merge](https://pypi.org/project/three-merge) | 0.1.1 | Simple library for merging two strings with respect to a base one
+[tifffile](https://pypi.org/project/tifffile) | 2025.1.10 | Read and write TIFF files
+[tiktoken](https://pypi.org/project/tiktoken) | 0.8.0 | tiktoken is a fast BPE tokeniser for use with OpenAI's models
+[tinycss2](https://pypi.org/project/tinycss2) | 1.4.0 | A tiny CSS parser
+[tokenizers](https://pypi.org/project/tokenizers) | 0.21.0 |
+[toml](https://pypi.org/project/toml) | 0.10.2 | Python Library for Tom's Obvious, Minimal Language
+[tomli](https://pypi.org/project/tomli) | 2.2.1 | A lil' TOML parser
+[tomli-w](https://pypi.org/project/tomli-w) | 1.2.0 | A lil' TOML writer
+[tomlkit](https://pypi.org/project/tomlkit) | 0.13.2 | Style preserving TOML library
+[toolz](https://pypi.org/project/toolz) | 1.0.0 | List processing tools and functional utilities
+[tornado](https://pypi.org/project/tornado) | 6.4.2 | Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed.
+[tqdm](https://pypi.org/project/tqdm) | 4.66.4 | Fast, Extensible Progress Meter
+[traitlets](https://pypi.org/project/traitlets) | 5.14.1 | Traitlets Python configuration system
+[traittypes](https://pypi.org/project/traittypes) | 0.2.1 | Scipy trait types
+[trio](https://pypi.org/project/trio) | 0.29.0 | A friendly Python library for async concurrency and I/O
+[trove-classifiers](https://pypi.org/project/trove-classifiers) | 2024.10.21.16 | Canonical source for classifiers on PyPI (pypi.org).
+[twine](https://pypi.org/project/twine) | 6.1.0 | Collection of utilities for publishing packages on PyPI
+[typer](https://pypi.org/project/typer) | 0.15.2 | Typer, build great CLIs. Easy to code. Based on Python type hints.
+[types-python-dateutil](https://pypi.org/project/types-python-dateutil) | 2.9.0.20240316 | Typing stubs for python-dateutil
+[types-requests](https://pypi.org/project/types-requests) | 2.32.0.20241016 | Typing stubs for requests
+[typing-extensions](https://pypi.org/project/typing-extensions) | 4.12.2 | Backported and Experimental Type Hints for Python 3.8+
+[typing-inspect](https://pypi.org/project/typing-inspect) | 0.9.0 | Runtime inspection utilities for typing module.
+[tzdata](https://pypi.org/project/tzdata) | 2024.1 | Provider of IANA time zone data
+[tzlocal](https://pypi.org/project/tzlocal) | 5.2 | tzinfo object for the local timezone
+[uc-micro-py](https://pypi.org/project/uc-micro-py) | 1.0.1 | Micro subset of unicode data files for linkify-it-py projects.
+[ujson](https://pypi.org/project/ujson) | 5.10.0 | Ultra fast JSON encoder and decoder for Python
+[umap-learn](https://pypi.org/project/umap-learn) | 0.5.6 | Uniform Manifold Approximation and Projection
+[uncertainties](https://pypi.org/project/uncertainties) | 3.2.2 | calculations with values with uncertainties, error propagation
+[uri-template](https://pypi.org/project/uri-template) | 1.3.0 | RFC 6570 URI Template Processor
+[urllib3](https://pypi.org/project/urllib3) | 2.2.3 | HTTP library with thread-safe connection pooling, file post, and more.
+[uvicorn](https://pypi.org/project/uvicorn) | 0.34.0 | The lightning-fast ASGI server.
+[vega-datasets](https://pypi.org/project/vega-datasets) | 0.9.0 | A Python package for offline access to Vega datasets
+[waitress](https://pypi.org/project/waitress) | 3.0.0 | Waitress WSGI server
+[watchdog](https://pypi.org/project/watchdog) | 6.0.0 | Filesystem events monitoring
+[wcwidth](https://pypi.org/project/wcwidth) | 0.2.13 | Measures the displayed width of unicode strings in a terminal
+[webcolors](https://pypi.org/project/webcolors) | 24.11.1 | A library for working with the color formats defined by HTML and CSS.
+[webencodings](https://pypi.org/project/webencodings) | 0.5.1 | Character encoding aliases for legacy web content
+[websocket-client](https://pypi.org/project/websocket-client) | 1.8.0 | WebSocket client for Python with low level API options
+[werkzeug](https://pypi.org/project/werkzeug) | 3.1.3 | The comprehensive WSGI web application library.
+[whatthepatch](https://pypi.org/project/whatthepatch) | 1.0.7 | A patch parsing and application library.
+[wheel](https://pypi.org/project/wheel) | 0.45.1 | A built-package format for Python
+[widgetsnbextension](https://pypi.org/project/widgetsnbextension) | 4.0.14 | Jupyter interactive widgets for Jupyter Notebook
+[winpython](https://pypi.org/project/winpython) | 15.3.20250425 | WinPython distribution tools, including WPPM
+[wordcloud](https://pypi.org/project/wordcloud) | 1.9.4 | A little word cloud generator
+[wrapt](https://pypi.org/project/wrapt) | 1.16.0 | Module for decorators, wrappers and monkey patching.
+[wsproto](https://pypi.org/project/wsproto) | 1.2.0 | WebSockets state-machine based protocol implementation
+[xarray](https://pypi.org/project/xarray) | 2025.3.0 | N-D labeled arrays and datasets in Python
+[xlsxwriter](https://pypi.org/project/xlsxwriter) | 3.1.9 | A Python module for creating Excel XLSX files.
+[xyzservices](https://pypi.org/project/xyzservices) | 2023.10.1 | Source of XYZ tiles providers
+[yapf](https://pypi.org/project/yapf) | 0.40.1 | A formatter for Python code.
+[yarl](https://pypi.org/project/yarl) | 1.18.3 | Yet another URL library
+[yt-dlp](https://pypi.org/project/yt-dlp) | 2025.2.19 | A feature-rich command-line audio/video downloader
+[zict](https://pypi.org/project/zict) | 3.0.0 | Mutable mapping tools
+[zipp](https://pypi.org/project/zipp) | 3.21.0 | Backport of pathlib-compatible object wrapper for zip files
+[zstandard](https://pypi.org/project/zstandard) | 0.23.0 | Zstandard bindings for Python
+
+
diff --git a/changelogs/WinPythonslim-64bit-3.12.10.0_History.md b/changelogs/WinPythonslim-64bit-3.12.10.0_History.md
new file mode 100644
index 00000000..07147b6d
--- /dev/null
+++ b/changelogs/WinPythonslim-64bit-3.12.10.0_History.md
@@ -0,0 +1,147 @@
+## History of changes for WinPython-64bit 3.12.10.0slim
+
+The following changes were made to WinPython-64bit distribution since version 3.12.9.0slim.
+
+
+
+### Tools
+
+New packages:
+
+ * [Pandoc](https://pandoc.org) 3.1.9 (an universal document converter)
+
+### Python packages
+
+New packages:
+
+ * [asyncssh](https://pypi.org/project/asyncssh) 2.20.0 (AsyncSSH: Asynchronous SSHv2 client and server library)
+ * [deprecated](https://pypi.org/project/deprecated) 1.2.14 (Python @deprecated decorator to deprecate old python classes, functions or methods.)
+ * [flexcache](https://pypi.org/project/flexcache) 0.3 (Saves and loads to the cache a transformed versions of a source object.)
+ * [flexparser](https://pypi.org/project/flexparser) 0.4 (Parsing made fun ... using typing.)
+ * [id](https://pypi.org/project/id) 1.5.0 (A tool for generating OIDC identities)
+ * [pygithub](https://pypi.org/project/pygithub) 2.6.1 (Use the full Github API v3)
+ * [pyuca](https://pypi.org/project/pyuca) 1.2 (a Python implementation of the Unicode Collation Algorithm)
+ * [shellingham](https://pypi.org/project/shellingham) 1.5.4 (Tool to Detect Surrounding Shell)
+ * [skrub](https://pypi.org/project/skrub) 0.5.1 (Prepping tables for machine learning)
+ * [superqt](https://pypi.org/project/superqt) 0.7.1 (Missing widgets and components for PyQt/PySide)
+ * [sv-ttk](https://pypi.org/project/sv-ttk) 2.6.0 (A gorgeous theme for Tkinter, based on Windows 11's UI)
+ * [typer](https://pypi.org/project/typer) 0.15.2 (Typer, build great CLIs. Easy to code. Based on Python type hints.)
+ * [wrapt](https://pypi.org/project/wrapt) 1.16.0 (Module for decorators, wrappers and monkey patching.)
+
+Upgraded packages:
+
+ * [alembic](https://pypi.org/project/alembic) 1.13.1 → 1.15.1 (A database migration tool for SQLAlchemy.)
+ * [anthropic](https://pypi.org/project/anthropic) 0.42.0 → 0.49.0 (The official Python library for the anthropic API)
+ * [anyio](https://pypi.org/project/anyio) 4.7.0 → 4.8.0 (High level compatibility layer for multiple asynchronous event loop implementations)
+ * [array-api-compat](https://pypi.org/project/array-api-compat) 1.10.0 → 1.11.1 (A wrapper around NumPy and other array libraries to make them compatible with the Array API standard)
+ * [azure-core](https://pypi.org/project/azure-core) 1.30.2 → 1.32.0 (Microsoft Azure Core Library for Python)
+ * [azure-cosmos](https://pypi.org/project/azure-cosmos) 4.7.0 → 4.9.0 (Microsoft Azure Cosmos Client Library for Python)
+ * [azure-identity](https://pypi.org/project/azure-identity) 1.16.1 → 1.21.0 (Microsoft Azure Identity Library for Python)
+ * [black](https://pypi.org/project/black) 24.10.0 → 25.1.0 (The uncompromising code formatter.)
+ * [bokeh](https://pypi.org/project/bokeh) 3.6.3 → 3.7.2 (Interactive plots and applications in the browser from Python)
+ * [cachetools](https://pypi.org/project/cachetools) 5.4.0 → 5.5.2 (Extensible memoizing collections and decorators)
+ * [certifi](https://pypi.org/project/certifi) 2024.6.2 → 2025.1.31 (Python package for providing Mozilla's CA Bundle.)
+ * [click](https://pypi.org/project/click) 8.1.7 → 8.1.8 (Composable command line interface toolkit)
+ * [cloudpickle](https://pypi.org/project/cloudpickle) 3.0.0 → 3.1.1 (Pickler class to extend the standard pickle.Pickler functionality)
+ * [cvxpy](https://pypi.org/project/cvxpy) 1.6.0 → 1.6.4 (A domain-specific language for modeling convex optimization problems in Python.)
+ * [cython](https://pypi.org/project/cython) 3.0.11 → 3.0.12 (The Cython compiler for writing C extensions in the Python language.)
+ * [dask](https://pypi.org/project/dask) 2024.12.1 → 2025.3.0 (Parallel PyData with Task Scheduling)
+ * [datasette](https://pypi.org/project/datasette) 0.64.8 → 0.65.1 (An open source multi-tool for exploring and publishing data)
+ * [datashader](https://pypi.org/project/datashader) 0.16.3 → 0.17.0 (Data visualization toolchain based on aggregating into a grid)
+ * [diff-match-patch](https://pypi.org/project/diff-match-patch) 20230430 → 20241021 (Repackaging of Google's Diff Match and Patch libraries.)
+ * [distributed](https://pypi.org/project/distributed) 2024.12.1 → 2025.3.0 (Distributed scheduler for Dask)
+ * [docstring-to-markdown](https://pypi.org/project/docstring-to-markdown) 0.13 → 0.15 (On the fly conversion of Python docstrings to markdown)
+ * [duckdb](https://pypi.org/project/duckdb) 1.2.0 → 1.2.2 (DuckDB in-process database)
+ * [faker](https://pypi.org/project/faker) 33.3.1 → 36.1.1 (Faker is a Python package that generates fake data for you.)
+ * [fastapi](https://pypi.org/project/fastapi) 0.115.6 → 0.115.8 (FastAPI framework, high performance, easy to learn, fast to code, ready for production)
+ * [filelock](https://pypi.org/project/filelock) 3.14.0 → 3.17.0 (A platform independent file lock.)
+ * [folium](https://pypi.org/project/folium) 0.18.0 → 0.19.5 (Make beautiful maps with Leaflet.js & Python)
+ * [holoviews](https://pypi.org/project/holoviews) 1.20.0 → 1.20.2 (A high-level plotting API for the PyData ecosystem built on HoloViews.)
+ * [hpack](https://pypi.org/project/hpack) 4.0.0 → 4.1.0 (Pure-Python HPACK header encoding)
+ * [huggingface-hub](https://pypi.org/project/huggingface-hub) 0.28.1 → 0.29.3 (Client library to download and publish models, datasets and other repos on the huggingface.co hub)
+ * [hypercorn](https://pypi.org/project/hypercorn) 0.16.0 → 0.17.3 (A ASGI Server based on Hyper libraries and inspired by Gunicorn)
+ * [hyperframe](https://pypi.org/project/hyperframe) 6.0.1 → 6.1.0 (Pure-Python HTTP/2 framing)
+ * [hypothesis](https://pypi.org/project/hypothesis) 6.122.3 → 6.130.4 (A library for property-based testing)
+ * [idna](https://pypi.org/project/idna) 3.7 → 3.10 (Internationalized Domain Names in Applications (IDNA))
+ * [imageio](https://pypi.org/project/imageio) 2.33.1 → 2.37.0 (Library for reading and writing a wide range of image, video, scientific, and volumetric data formats.)
+ * [importlib-metadata](https://pypi.org/project/importlib-metadata) 7.1.0 → 8.6.1 (Read metadata from Python packages)
+ * [ipympl](https://pypi.org/project/ipympl) 0.9.6 → 0.9.7 (Matplotlib Jupyter Extension)
+ * [ipython](https://pypi.org/project/ipython) 8.32.0 → 8.34.0 (IPython: Productive Interactive Computing)
+ * [ipywidgets](https://pypi.org/project/ipywidgets) 8.1.5 → 8.1.6 (Jupyter interactive widgets)
+ * [jupyter-client](https://pypi.org/project/jupyter-client) 8.6.2 → 8.6.3 (Jupyter protocol implementation and client libraries)
+ * [jupyter-events](https://pypi.org/project/jupyter-events) 0.10.0 → 0.12.0 (Jupyter Event System library)
+ * [jupyterlab](https://pypi.org/project/jupyterlab) 4.3.5 → 4.4.1 (JupyterLab computational environment)
+ * [jupyterlab-widgets](https://pypi.org/project/jupyterlab-widgets) 3.0.13 → 3.0.14 (Jupyter interactive widgets for JupyterLab)
+ * [keras](https://pypi.org/project/keras) 3.8.0 → 3.9.2 (Multi-backend Keras)
+ * [langchain](https://pypi.org/project/langchain) 0.3.18 → 0.3.23 (Building applications with LLMs through composability)
+ * [langchain-core](https://pypi.org/project/langchain-core) 0.3.34 → 0.3.51 (Building applications with LLMs through composability)
+ * [langchain-text-splitters](https://pypi.org/project/langchain-text-splitters) 0.3.6 → 0.3.8 (LangChain text splitting utilities)
+ * [langsmith](https://pypi.org/project/langsmith) 0.2.11 → 0.3.24 (Client library to connect to the LangSmith LLM Tracing and Evaluation Platform.)
+ * [markdown](https://pypi.org/project/markdown) 3.5.1 → 3.7 (Python implementation of John Gruber's Markdown.)
+ * [matplotlib](https://pypi.org/project/matplotlib) 3.10.0 → 3.10.1 (Python plotting package)
+ * [mypy](https://pypi.org/project/mypy) 1.14.0 → 1.15.0 (Optional static typing for Python)
+ * [mysql-connector-python](https://pypi.org/project/mysql-connector-python) 8.0.21 → 9.2.0 (A self-contained Python driver for communicating with MySQL servers, using an API that is compliant with the Python Database API Specification v)
+ * [narwhals](https://pypi.org/project/narwhals) 1.21.1 → 1.30.0 (Extremely lightweight compatibility layer between dataframe libraries)
+ * [notebook](https://pypi.org/project/notebook) 7.3.1 → 7.4.0 (Jupyter Notebook - A web-based notebook environment for interactive computing)
+ * [numba](https://pypi.org/project/numba) 0.61.0 → 0.61.2 (compiling Python code using LLVM)
+ * [numpy](https://pypi.org/project/numpy) 2.1.3 → 2.2.4 (Fundamental package for array computing in Python)
+ * [openai](https://pypi.org/project/openai) 1.61.1 → 1.72.0 (The official Python library for the openai API)
+ * [opencv-python](https://pypi.org/project/opencv-python) 4.10.0.84 → 4.11.0.86 (Wrapper package for OpenCV python bindings.)
+ * [optree](https://pypi.org/project/optree) 0.13.1 → 0.14.0 (Optimized PyTree Utilities.)
+ * [panel](https://pypi.org/project/panel) 1.6.0 → 1.6.2 (The powerful data exploration & web app framework for Python.)
+ * [param](https://pypi.org/project/param) 2.1.1 → 2.2.0 (Make your Python code clearer and more reliable by declaring Parameters.)
+ * [pip](https://pypi.org/project/pip) 24.3.1 → 25.0.1 (The PyPA recommended tool for installing Python packages.)
+ * [platformdirs](https://pypi.org/project/platformdirs) 4.2.2 → 4.3.6 (A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`.)
+ * [plotly](https://pypi.org/project/plotly) 5.24.1 → 6.0.1 (An open-source interactive data visualization library for Python)
+ * [polars](https://pypi.org/project/polars) 1.22.0 → 1.27.1 (Blazingly fast DataFrame library)
+ * [prometheus-client](https://pypi.org/project/prometheus-client) 0.18.0 → 0.21.1 (Python client for the Prometheus monitoring system.)
+ * [prompt-toolkit](https://pypi.org/project/prompt-toolkit) 3.0.48 → 3.0.50 (Library for building powerful interactive command lines in Python)
+ * [pyarrow](https://pypi.org/project/pyarrow) 19.0.0 → 19.0.1 (Python library for Apache Arrow)
+ * [pyasn1](https://pypi.org/project/pyasn1) 0.4.8 → 0.6.1 (Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208))
+ * [pyasn1-modules](https://pypi.org/project/pyasn1-modules) 0.2.8 → 0.4.1 (A collection of ASN.1-based protocols modules)
+ * [pyjwt](https://pypi.org/project/pyjwt) 2.8.0 → 2.10.1 (JSON Web Token implementation in Python)
+ * [pympler](https://pypi.org/project/pympler) 1.0.1 → 1.1 (A development tool to measure, monitor and analyze the memory behavior of Python objects.)
+ * [pyomo](https://pypi.org/project/pyomo) 6.8.2 → 6.9.1 (Pyomo: Python Optimization Modeling Objects)
+ * [pypandoc](https://pypi.org/project/pypandoc) 1.5 → 1.15 (Thin wrapper for pandoc.)
+ * [pytest](https://pypi.org/project/pytest) 8.2.2 → 8.3.4 (pytest: simple powerful testing with Python)
+ * [Python](http://www.python.org/) 3.12.9 → 3.12.10 (Python programming language with standard library)
+ * [pytz](https://pypi.org/project/pytz) 2024.1 → 2024.2 (World timezone definitions, modern and historical)
+ * [pyzmq](https://pypi.org/project/pyzmq) 26.2.0 → 26.2.1 (Python bindings for 0MQ)
+ * [qtawesome](https://pypi.org/project/qtawesome) 1.3.1 → 1.4.0 (FontAwesome icons in PyQt and PySide applications)
+ * [qtconsole](https://pypi.org/project/qtconsole) 5.5.2 → 5.6.1 (Jupyter Qt console)
+ * [quart](https://pypi.org/project/quart) 0.19.4 → 0.20.0 (A Python ASGI web framework with the same API as Flask)
+ * [rtree](https://pypi.org/project/rtree) 1.1.0 → 1.3.0 (R-Tree spatial index for Python GIS)
+ * [rx](https://pypi.org/project/rx) 3.1.1 → 3.2.0 (Reactive Extensions (Rx) for Python)
+ * [scikit-image](https://pypi.org/project/scikit-image) 0.25.0 → 0.25.2 (Image processing in Python)
+ * [scipy](https://pypi.org/project/scipy) 1.15.1 → 1.15.2 (Fundamental algorithms for scientific computing in Python)
+ * [send2trash](https://pypi.org/project/send2trash) 1.8.2 → 1.8.3 (Send file to trash natively under Mac OS X, Windows and Linux)
+ * [setuptools](https://pypi.org/project/setuptools) 75.6.0 → 75.8.2 (Easily download, build, install, upgrade, and uninstall Python packages)
+ * [simpy](https://pypi.org/project/simpy) 4.0.1 → 4.1.1 (Event discrete, process based simulation for Python.)
+ * [spyder](https://pypi.org/project/spyder) 5.5.6 → 6.0.5 (The Scientific Python Development Environment)
+ * [spyder-kernels](https://pypi.org/project/spyder-kernels) 2.5.2 → 3.0.3 (Jupyter kernels for Spyder's console)
+ * [sqlalchemy](https://pypi.org/project/sqlalchemy) 2.0.35 → 2.0.38 (Database Abstraction Library)
+ * [starlette](https://pypi.org/project/starlette) 0.41.3 → 0.45.3 (The little ASGI library that shines.)
+ * [streamlit](https://pypi.org/project/streamlit) 1.41.1 → 1.44.0 (A faster way to build and share data apps)
+ * [tomli-w](https://pypi.org/project/tomli-w) 1.1.0 → 1.2.0 (A lil' TOML writer)
+ * [trio](https://pypi.org/project/trio) 0.28.0 → 0.29.0 (A friendly Python library for async concurrency and I/O)
+ * [twine](https://pypi.org/project/twine) 6.0.1 → 6.1.0 (Collection of utilities for publishing packages on PyPI)
+ * [widgetsnbextension](https://pypi.org/project/widgetsnbextension) 4.0.13 → 4.0.14 (Jupyter interactive widgets for Jupyter Notebook)
+ * [winpython](https://pypi.org/project/winpython) 13.1.20250222 → 15.3.20250425 (WinPython distribution tools, including WPPM)
+ * [xarray](https://pypi.org/project/xarray) 2025.1.1 → 2025.3.0 (N-D labeled arrays and datasets in Python)
+ * [yt-dlp](https://pypi.org/project/yt-dlp) 2023.7.6 → 2025.2.19 (A feature-rich command-line audio/video downloader)
+
+Removed packages:
+
+ * [bcrypt](https://pypi.org/project/bcrypt) 4.0.1 (Modern password hashing for your software and your servers)
+ * [dask_expr](https://pypi.org/project/dask_expr) 1.1.21 (High Level Expressions for Dask )
+ * [mutagen](https://pypi.org/project/mutagen) 1.47.0 (read and write audio tags for many formats)
+ * [paramiko](https://pypi.org/project/paramiko) 2.8.0 (SSH2 protocol library)
+ * [pint](https://pypi.org/project/pint) 0.23 (Physical quantities module)
+ * [pkginfo](https://pypi.org/project/pkginfo) 1.11.2 (Query metadata from sdists / bdists / installed packages.)
+ * [pycryptodomex](https://pypi.org/project/pycryptodomex) 3.20.0 (Cryptographic library for Python)
+ * [streamz](https://pypi.org/project/streamz) 0.6.3 (Streams)
+ * [websockets](https://pypi.org/project/websockets) 14.2 (An implementation of the WebSocket Protocol (RFC 6455 & 7692))
+
+
+
+* * *
diff --git a/changelogs/WinPythonslim-64bit-3.13.3.0.md b/changelogs/WinPythonslim-64bit-3.13.3.0.md
new file mode 100644
index 00000000..3cc4c6b7
--- /dev/null
+++ b/changelogs/WinPythonslim-64bit-3.13.3.0.md
@@ -0,0 +1,515 @@
+## WinPython 3.13.3.0slim
+
+The following packages are included in WinPython-64bit v3.13.3.0slim .
+
+
+
+### Tools
+
+Name | Version | Description
+-----|---------|------------
+[Pandoc](https://pandoc.org) | 3.1.9 | an universal document converter
+
+### Python packages
+
+Name | Version | Description
+-----|---------|------------
+[Python](http://www.python.org/) | 3.13.3 | Python programming language with standard library
+[absl-py](https://pypi.org/project/absl-py) | 2.0.0 | Abseil Python Common Libraries, see https://github.com/abseil/abseil-py.
+[adbc-driver-manager](https://pypi.org/project/adbc-driver-manager) | 1.3.0 | A generic entrypoint for ADBC drivers.
+[aiofiles](https://pypi.org/project/aiofiles) | 23.2.1 | File support for asyncio.
+[aiohappyeyeballs](https://pypi.org/project/aiohappyeyeballs) | 2.4.4 | Happy Eyeballs for asyncio
+[aiohttp](https://pypi.org/project/aiohttp) | 3.11.11 | Async http client/server framework (asyncio)
+[aiosignal](https://pypi.org/project/aiosignal) | 1.3.1 | aiosignal: a list of registered asynchronous callbacks
+[aiosqlite](https://pypi.org/project/aiosqlite) | 0.20.0 | asyncio bridge to the standard sqlite3 module
+[alabaster](https://pypi.org/project/alabaster) | 0.7.16 | A light, configurable Sphinx theme
+[alembic](https://pypi.org/project/alembic) | 1.15.1 | A database migration tool for SQLAlchemy.
+[altair](https://pypi.org/project/altair) | 5.5.0 | Vega-Altair: A declarative statistical visualization library for Python.
+[aniso8601](https://pypi.org/project/aniso8601) | 9.0.1 | A library for parsing ISO 8601 strings.
+[annotated-types](https://pypi.org/project/annotated-types) | 0.6.0 | Reusable constraint types to use with typing.Annotated
+[ansicolors](https://pypi.org/project/ansicolors) | 1.1.8 | ANSI colors for Python
+[anthropic](https://pypi.org/project/anthropic) | 0.49.0 | The official Python library for the anthropic API
+[anyio](https://pypi.org/project/anyio) | 4.8.0 | High level compatibility layer for multiple asynchronous event loop implementations
+[anywidget](https://pypi.org/project/anywidget) | 0.9.12 | custom jupyter widgets made easy
+[appdirs](https://pypi.org/project/appdirs) | 1.4.4 | A small Python module for determining appropriate platform-specific dirs, e.g. a "user data dir".
+[argon2-cffi](https://pypi.org/project/argon2-cffi) | 23.1.0 | Argon2 for Python
+[argon2-cffi-bindings](https://pypi.org/project/argon2-cffi-bindings) | 21.2.0 | Low-level CFFI bindings for Argon2
+[array-api-compat](https://pypi.org/project/array-api-compat) | 1.11.1 | A wrapper around NumPy and other array libraries to make them compatible with the Array API standard
+[arrow](https://pypi.org/project/arrow) | 1.3.0 | Better dates & times for Python
+[asgi-csrf](https://pypi.org/project/asgi-csrf) | 0.9 | ASGI middleware for protecting against CSRF attacks
+[asgiref](https://pypi.org/project/asgiref) | 3.8.1 | ASGI specs, helper code, and adapters
+[asn1crypto](https://pypi.org/project/asn1crypto) | 1.5.1 | Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12,
+[asteval](https://pypi.org/project/asteval) | 0.9.31 | Safe, minimalistic evaluator of python expression using ast module
+[astroid](https://pypi.org/project/astroid) | 3.1.0 | An abstract syntax tree for Python with inference support.
+[astropy](https://pypi.org/project/astropy) | 6.1.6 | Astronomy and astrophysics core library
+[astropy-iers-data](https://pypi.org/project/astropy-iers-data) | 0.2024.12.23.0.33.24 | IERS Earth Rotation and Leap Second tables for the astropy core package
+[asttokens](https://pypi.org/project/asttokens) | 2.4.1 | Annotate AST trees with source code positions
+[async-lru](https://pypi.org/project/async-lru) | 2.0.4 | Simple LRU cache for asyncio
+[asyncssh](https://pypi.org/project/asyncssh) | 2.20.0 | AsyncSSH: Asynchronous SSHv2 client and server library
+[atomicwrites](https://pypi.org/project/atomicwrites) | 1.4.0 | Atomic file writes.
+[attrs](https://pypi.org/project/attrs) | 23.2.0 | Classes Without Boilerplate
+[autopep8](https://pypi.org/project/autopep8) | 2.0.4 | A tool that automatically formats Python code to conform to the PEP 8 style guide
+[azure-core](https://pypi.org/project/azure-core) | 1.32.0 | Microsoft Azure Core Library for Python
+[azure-cosmos](https://pypi.org/project/azure-cosmos) | 4.9.0 | Microsoft Azure Cosmos Client Library for Python
+[azure-identity](https://pypi.org/project/azure-identity) | 1.21.0 | Microsoft Azure Identity Library for Python
+[babel](https://pypi.org/project/babel) | 2.16.0 | Internationalization utilities
+[baresql](https://pypi.org/project/baresql) | 1.0.0 | playing SQL directly on Python datas
+[beautifulsoup4](https://pypi.org/project/beautifulsoup4) | 4.12.2 | Screen-scraping library
+[binaryornot](https://pypi.org/project/binaryornot) | 0.4.4 | Ultra-lightweight pure Python package to check if a file is binary or text.
+[black](https://pypi.org/project/black) | 25.1.0 | The uncompromising code formatter.
+[bleach](https://pypi.org/project/bleach) | 6.1.0 | An easy safelist-based HTML-sanitizing tool.
+[blinker](https://pypi.org/project/blinker) | 1.9.0 | Fast, simple object-to-object and broadcast signaling
+[bokeh](https://pypi.org/project/bokeh) | 3.7.2 | Interactive plots and applications in the browser from Python
+[branca](https://pypi.org/project/branca) | 0.8.0 | Generate complex HTML+JS pages with Python
+[brotli](https://pypi.org/project/brotli) | 1.1.0 | Python bindings for the Brotli compression library
+[build](https://pypi.org/project/build) | 1.2.2.post1 | A simple, correct Python build frontend
+[cachetools](https://pypi.org/project/cachetools) | 5.5.2 | Extensible memoizing collections and decorators
+[certifi](https://pypi.org/project/certifi) | 2025.1.31 | Python package for providing Mozilla's CA Bundle.
+[cffi](https://pypi.org/project/cffi) | 1.17.1 | Foreign Function Interface for Python calling C code.
+[chardet](https://pypi.org/project/chardet) | 5.2.0 | Universal encoding detector for Python 3
+[charset-normalizer](https://pypi.org/project/charset-normalizer) | 3.4.0 | The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet.
+[clarabel](https://pypi.org/project/clarabel) | 0.10.0 | Clarabel Conic Interior Point Solver for Rust / Python
+[click](https://pypi.org/project/click) | 8.1.8 | Composable command line interface toolkit
+[click-default-group](https://pypi.org/project/click-default-group) | 1.2.4 | click_default_group
+[cloudpickle](https://pypi.org/project/cloudpickle) | 3.1.1 | Pickler class to extend the standard pickle.Pickler functionality
+[cohere](https://pypi.org/project/cohere) | 5.13.12 |
+[colorama](https://pypi.org/project/colorama) | 0.4.6 | Cross-platform colored terminal text.
+[colorcet](https://pypi.org/project/colorcet) | 3.1.0 | Collection of perceptually uniform colormaps
+[colorlog](https://pypi.org/project/colorlog) | 6.8.2 | Add colours to the output of Python's logging module.
+[comm](https://pypi.org/project/comm) | 0.2.2 | Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc.
+[contourpy](https://pypi.org/project/contourpy) | 1.3.1 | Python library for calculating contours of 2D quadrilateral grids
+[cookiecutter](https://pypi.org/project/cookiecutter) | 2.6.0 | A command-line utility that creates projects from project templates, e.g.
+[cryptography](https://pypi.org/project/cryptography) | 44.0.0 | cryptography is a package which provides cryptographic recipes and primitives to Python developers.
+[cvxopt](https://pypi.org/project/cvxopt) | 1.3.2 | Convex optimization package
+[cvxpy](https://pypi.org/project/cvxpy) | 1.6.4 | A domain-specific language for modeling convex optimization problems in Python.
+[cycler](https://pypi.org/project/cycler) | 0.12.1 | Composable style cycles
+[cython](https://pypi.org/project/cython) | 3.0.12 | The Cython compiler for writing C extensions in the Python language.
+[cytoolz](https://pypi.org/project/cytoolz) | 1.0.1 | Cython implementation of Toolz: High performance functional utilities
+[dask](https://pypi.org/project/dask) | 2025.3.0 | Parallel PyData with Task Scheduling
+[datasette](https://pypi.org/project/datasette) | 0.65.1 | An open source multi-tool for exploring and publishing data
+[datasette-graphql](https://pypi.org/project/datasette-graphql) | 2.2 | Datasette plugin providing an automatic GraphQL API for your SQLite databases
+[datashader](https://pypi.org/project/datashader) | 0.17.0 | Data visualization toolchain based on aggregating into a grid
+[deap](https://pypi.org/project/deap) | 1.4.2 | Distributed Evolutionary Algorithms in Python
+[debugpy](https://pypi.org/project/debugpy) | 1.8.0 | An implementation of the Debug Adapter Protocol for Python
+[decorator](https://pypi.org/project/decorator) | 5.1.1 | Decorators for Humans
+[defusedxml](https://pypi.org/project/defusedxml) | 0.7.1 | XML bomb protection for Python stdlib modules
+[deprecated](https://pypi.org/project/deprecated) | 1.2.14 | Python @deprecated decorator to deprecate old python classes, functions or methods.
+[diff-match-patch](https://pypi.org/project/diff-match-patch) | 20241021 | Repackaging of Google's Diff Match and Patch libraries.
+[dill](https://pypi.org/project/dill) | 0.3.9 | serialize all of Python
+[distributed](https://pypi.org/project/distributed) | 2025.3.0 | Distributed scheduler for Dask
+[distro](https://pypi.org/project/distro) | 1.8.0 | Distro - an OS platform information API
+[django](https://pypi.org/project/django) | 5.0.7 | A high-level Python web framework that encourages rapid development and clean, pragmatic design.
+[dnspython](https://pypi.org/project/dnspython) | 2.6.1 | DNS toolkit
+[docstring-to-markdown](https://pypi.org/project/docstring-to-markdown) | 0.15 | On the fly conversion of Python docstrings to markdown
+[docutils](https://pypi.org/project/docutils) | 0.21.2 | Docutils -- Python Documentation Utilities
+[duckdb](https://pypi.org/project/duckdb) | 1.2.2 | DuckDB in-process database
+[entrypoints](https://pypi.org/project/entrypoints) | 0.4 | Discover and load entry points from installed packages.
+[et-xmlfile](https://pypi.org/project/et-xmlfile) | 1.1.0 | An implementation of lxml.xmlfile for the standard library
+[eval-type-backport](https://pypi.org/project/eval-type-backport) | 0.2.2 | Like `typing._eval_type`, but lets older Python versions use newer typing features.
+[executing](https://pypi.org/project/executing) | 2.2.0 | Get the currently executing AST node of a frame, and other information
+[faker](https://pypi.org/project/faker) | 36.1.1 | Faker is a Python package that generates fake data for you.
+[fast-histogram](https://pypi.org/project/fast-histogram) | 0.14 | Fast simple 1D and 2D histograms
+[fastapi](https://pypi.org/project/fastapi) | 0.115.8 | FastAPI framework, high performance, easy to learn, fast to code, ready for production
+[fastavro](https://pypi.org/project/fastavro) | 1.10.0 | Fast read/write of AVRO files
+[fastjsonschema](https://pypi.org/project/fastjsonschema) | 2.18.0 | Fastest Python implementation of JSON schema
+[filelock](https://pypi.org/project/filelock) | 3.17.0 | A platform independent file lock.
+[flake8](https://pypi.org/project/flake8) | 7.1.1 | the modular source code checker: pep8 pyflakes and co
+[flask](https://pypi.org/project/flask) | 3.1.0 | A simple framework for building complex web applications.
+[flexcache](https://pypi.org/project/flexcache) | 0.3 | Saves and loads to the cache a transformed versions of a source object.
+[flexparser](https://pypi.org/project/flexparser) | 0.4 | Parsing made fun ... using typing.
+[flit](https://pypi.org/project/flit) | 3.10.1 | A simple packaging tool for simple packages.
+[flit-core](https://pypi.org/project/flit-core) | 3.10.1 | Distribution-building parts of Flit. See flit package for more information
+[folium](https://pypi.org/project/folium) | 0.19.5 | Make beautiful maps with Leaflet.js & Python
+[fonttools](https://pypi.org/project/fonttools) | 4.55.3 | Tools to manipulate font files
+[fqdn](https://pypi.org/project/fqdn) | 1.5.1 | Validates fully-qualified domain names against RFC 1123, so that they are acceptable to modern bowsers
+[frozenlist](https://pypi.org/project/frozenlist) | 1.5.0 | A list-like structure which implements collections.abc.MutableSequence
+[fsspec](https://pypi.org/project/fsspec) | 2024.6.1 | File-system specification
+[fuzzywuzzy](https://pypi.org/project/fuzzywuzzy) | 0.18.0 | Fuzzy string matching in python
+[geographiclib](https://pypi.org/project/geographiclib) | 2.0 | The geodesic routines from GeographicLib
+[geopandas](https://pypi.org/project/geopandas) | 1.0.1 | Geographic pandas extensions
+[geopy](https://pypi.org/project/geopy) | 2.4.1 | Python Geocoding Toolbox
+[gitdb](https://pypi.org/project/gitdb) | 4.0.10 | Git Object Database
+[gitpython](https://pypi.org/project/gitpython) | 3.1.32 | GitPython is a Python library used to interact with Git repositories
+[google-auth](https://pypi.org/project/google-auth) | 2.37.0 | Google Authentication Library
+[graphene](https://pypi.org/project/graphene) | 3.3 | GraphQL Framework for Python
+[graphql-core](https://pypi.org/project/graphql-core) | 3.2.3 | GraphQL implementation for Python, a port of GraphQL.js, the JavaScript reference implementation for GraphQL.
+[graphql-relay](https://pypi.org/project/graphql-relay) | 3.2.0 | Relay library for graphql-core
+[greenlet](https://pypi.org/project/greenlet) | 3.1.1 | Lightweight in-process concurrent programming
+[griffe](https://pypi.org/project/griffe) | 1.5.5 | Signatures for entire Python programs.
+[groq](https://pypi.org/project/groq) | 0.13.1 | The official Python library for the groq API
+[guidata](https://pypi.org/project/guidata) | 3.7.1 | Automatic GUI generation for easy dataset editing and display
+[h11](https://pypi.org/project/h11) | 0.14.0 | A pure-Python, bring-your-own-I/O implementation of HTTP/1.1
+[h2](https://pypi.org/project/h2) | 4.1.0 | HTTP/2 State-Machine based protocol implementation
+[h5py](https://pypi.org/project/h5py) | 3.12.1 | Read and write HDF5 files from Python
+[hatchling](https://pypi.org/project/hatchling) | 1.27.0 | Modern, extensible Python build backend
+[holoviews](https://pypi.org/project/holoviews) | 1.20.2 | A high-level plotting API for the PyData ecosystem built on HoloViews.
+[hpack](https://pypi.org/project/hpack) | 4.1.0 | Pure-Python HPACK header encoding
+[html5lib](https://pypi.org/project/html5lib) | 1.1 | HTML parser based on the WHATWG HTML specification
+[httpcore](https://pypi.org/project/httpcore) | 1.0.5 | A minimal low-level HTTP client.
+[httpie](https://pypi.org/project/httpie) | 3.2.4 | HTTPie: modern, user-friendly command-line HTTP client for the API era.
+[httpx](https://pypi.org/project/httpx) | 0.27.2 | The next generation HTTP client.
+[httpx-sse](https://pypi.org/project/httpx-sse) | 0.4.0 | Consume Server-Sent Event (SSE) messages with HTTPX.
+[huggingface-hub](https://pypi.org/project/huggingface-hub) | 0.29.3 | Client library to download and publish models, datasets and other repos on the huggingface.co hub
+[hupper](https://pypi.org/project/hupper) | 1.12 | Integrated process monitor for developing and reloading daemons.
+[hvplot](https://pypi.org/project/hvplot) | 0.11.2 | A high-level plotting API for the PyData ecosystem built on HoloViews.
+[hypercorn](https://pypi.org/project/hypercorn) | 0.17.3 | A ASGI Server based on Hyper libraries and inspired by Gunicorn
+[hyperframe](https://pypi.org/project/hyperframe) | 6.1.0 | Pure-Python HTTP/2 framing
+[hypothesis](https://pypi.org/project/hypothesis) | 6.130.4 | A library for property-based testing
+[id](https://pypi.org/project/id) | 1.5.0 | A tool for generating OIDC identities
+[idna](https://pypi.org/project/idna) | 3.10 | Internationalized Domain Names in Applications (IDNA)
+[imageio](https://pypi.org/project/imageio) | 2.37.0 | Library for reading and writing a wide range of image, video, scientific, and volumetric data formats.
+[imagesize](https://pypi.org/project/imagesize) | 1.4.1 | Getting image size from png/jpeg/jpeg2000/gif file
+[imbalanced-learn](https://pypi.org/project/imbalanced-learn) | 0.13.0 | Toolbox for imbalanced dataset in machine learning
+[importlib-metadata](https://pypi.org/project/importlib-metadata) | 8.6.1 | Read metadata from Python packages
+[inflection](https://pypi.org/project/inflection) | 0.5.1 | A port of Ruby on Rails inflector to Python
+[iniconfig](https://pypi.org/project/iniconfig) | 2.0.0 | brain-dead simple config-ini parsing
+[intervaltree](https://pypi.org/project/intervaltree) | 3.0.2 | Editable interval tree data structure for Python 2 and 3
+[ipycanvas](https://pypi.org/project/ipycanvas) | 0.13.3 | Interactive widgets library exposing the browser's Canvas API
+[ipykernel](https://pypi.org/project/ipykernel) | 6.29.5 | IPython Kernel for Jupyter
+[ipyleaflet](https://pypi.org/project/ipyleaflet) | 0.19.2 | A Jupyter widget for dynamic Leaflet maps
+[ipympl](https://pypi.org/project/ipympl) | 0.9.7 | Matplotlib Jupyter Extension
+[ipython](https://pypi.org/project/ipython) | 8.34.0 | IPython: Productive Interactive Computing
+[ipython-genutils](https://pypi.org/project/ipython-genutils) | 0.2.0 | Vestigial utilities from IPython
+[ipython-sql](https://pypi.org/project/ipython-sql) | 0.5.0 | RDBMS access via IPython
+[ipywidgets](https://pypi.org/project/ipywidgets) | 8.1.6 | Jupyter interactive widgets
+[isoduration](https://pypi.org/project/isoduration) | 20.11.0 | Operations with ISO 8601 durations
+[isort](https://pypi.org/project/isort) | 5.13.2 | A Python utility / library to sort Python imports.
+[itsdangerous](https://pypi.org/project/itsdangerous) | 2.2.0 | Safely pass data to untrusted environments and back.
+[janus](https://pypi.org/project/janus) | 2.0.0 | Mixed sync-async queue to interoperate between asyncio tasks and classic threads
+[jaraco-classes](https://pypi.org/project/jaraco-classes) | 3.4.0 | Utility functions for Python class constructs
+[jaraco-context](https://pypi.org/project/jaraco-context) | 6.0.1 | Useful decorators and context managers
+[jaraco-functools](https://pypi.org/project/jaraco-functools) | 4.1.0 | Functools like those found in stdlib
+[jedi](https://pypi.org/project/jedi) | 0.19.2 | An autocompletion tool for Python that can be used for text editors.
+[jellyfish](https://pypi.org/project/jellyfish) | 1.1.3 | Approximate and phonetic matching of strings.
+[jinja2](https://pypi.org/project/jinja2) | 3.1.2 | A very fast and expressive template engine.
+[jiter](https://pypi.org/project/jiter) | 0.8.2 | Fast iterable JSON parser.
+[joblib](https://pypi.org/project/joblib) | 1.4.2 | Lightweight pipelining with Python functions
+[json5](https://pypi.org/project/json5) | 0.9.14 | A Python implementation of the JSON5 data format.
+[jsonpatch](https://pypi.org/project/jsonpatch) | 1.33 | Apply JSON-Patches (RFC 6902)
+[jsonpath-python](https://pypi.org/project/jsonpath-python) | 1.0.6 | A more powerful JSONPath implementation in modern python
+[jsonpointer](https://pypi.org/project/jsonpointer) | 2.4 | Identify specific nodes in a JSON document (RFC 6901)
+[jsonschema](https://pypi.org/project/jsonschema) | 4.19.2 | An implementation of JSON Schema validation for Python
+[jsonschema-specifications](https://pypi.org/project/jsonschema-specifications) | 2023.12.1 | The JSON Schema meta-schemas and vocabularies, exposed as a Registry
+[julia](https://pypi.org/project/julia) | 0.6.2 | Julia/Python bridge with IPython support.
+[jupyter](https://pypi.org/project/jupyter) | 1.1.1 | Jupyter metapackage. Install all the Jupyter components in one go.
+[jupyter-bokeh](https://pypi.org/project/jupyter-bokeh) | 4.0.5 | A Jupyter extension for rendering Bokeh content.
+[jupyter-client](https://pypi.org/project/jupyter-client) | 8.6.3 | Jupyter protocol implementation and client libraries
+[jupyter-console](https://pypi.org/project/jupyter-console) | 6.6.3 | Jupyter terminal console
+[jupyter-core](https://pypi.org/project/jupyter-core) | 5.7.2 | Jupyter core package. A base package on which Jupyter projects rely.
+[jupyter-events](https://pypi.org/project/jupyter-events) | 0.12.0 | Jupyter Event System library
+[jupyter-leaflet](https://pypi.org/project/jupyter-leaflet) | 0.19.2 | ipyleaflet extensions for JupyterLab and Jupyter Notebook
+[jupyter-lsp](https://pypi.org/project/jupyter-lsp) | 2.2.5 | Multi-Language Server WebSocket proxy for Jupyter Notebook/Lab server
+[jupyter-server](https://pypi.org/project/jupyter-server) | 2.14.2 | The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications.
+[jupyter-server-terminals](https://pypi.org/project/jupyter-server-terminals) | 0.5.3 | A Jupyter Server Extension Providing Terminals.
+[jupyterlab](https://pypi.org/project/jupyterlab) | 4.4.1 | JupyterLab computational environment
+[jupyterlab-pygments](https://pypi.org/project/jupyterlab-pygments) | 0.3.0 | Pygments theme using JupyterLab CSS variables
+[jupyterlab-server](https://pypi.org/project/jupyterlab-server) | 2.27.3 | A set of server components for JupyterLab and JupyterLab like applications.
+[jupyterlab-widgets](https://pypi.org/project/jupyterlab-widgets) | 3.0.14 | Jupyter interactive widgets for JupyterLab
+[keras](https://pypi.org/project/keras) | 3.9.2 | Multi-backend Keras
+[keyring](https://pypi.org/project/keyring) | 25.6.0 | Store and access your passwords safely.
+[kiwisolver](https://pypi.org/project/kiwisolver) | 1.4.8 | A fast implementation of the Cassowary constraint solver
+[langchain](https://pypi.org/project/langchain) | 0.3.23 | Building applications with LLMs through composability
+[langchain-core](https://pypi.org/project/langchain-core) | 0.3.51 | Building applications with LLMs through composability
+[langchain-text-splitters](https://pypi.org/project/langchain-text-splitters) | 0.3.8 | LangChain text splitting utilities
+[langsmith](https://pypi.org/project/langsmith) | 0.3.24 | Client library to connect to the LangSmith LLM Tracing and Evaluation Platform.
+[lazy-loader](https://pypi.org/project/lazy-loader) | 0.4 | Makes it easy to load subpackages and functions on demand.
+[linkify-it-py](https://pypi.org/project/linkify-it-py) | 2.0.2 | Links recognition library with FULL unicode support.
+[llvmlite](https://pypi.org/project/llvmlite) | 0.44.0 | lightweight wrapper around basic LLVM functionality
+[lmfit](https://pypi.org/project/lmfit) | 1.3.1 | Least-Squares Minimization with Bounds and Constraints
+[locket](https://pypi.org/project/locket) | 1.0.0 | File-based locks for Python on Linux and Windows
+[logfire-api](https://pypi.org/project/logfire-api) | 3.5.3 | Shim for the Logfire SDK which does nothing unless Logfire is installed
+[lxml](https://pypi.org/project/lxml) | 5.3.0 | Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API.
+[mako](https://pypi.org/project/mako) | 1.3.5 | A super-fast templating language that borrows the best ideas from the existing templating languages.
+[markdown](https://pypi.org/project/markdown) | 3.7 | Python implementation of John Gruber's Markdown.
+[markdown-it-py](https://pypi.org/project/markdown-it-py) | 2.2.0 | Python port of markdown-it. Markdown parsing, done right!
+[markupsafe](https://pypi.org/project/markupsafe) | 3.0.2 | Safely add untrusted strings to HTML/XML markup.
+[matplotlib](https://pypi.org/project/matplotlib) | 3.10.1 | Python plotting package
+[matplotlib-inline](https://pypi.org/project/matplotlib-inline) | 0.1.7 | Inline Matplotlib backend for Jupyter
+[maturin](https://pypi.org/project/maturin) | 1.8.1 | Build and publish crates with pyo3, cffi and uniffi bindings as well as rust binaries as python packages
+[mccabe](https://pypi.org/project/mccabe) | 0.7.0 | McCabe checker, plugin for flake8
+[mdit-py-plugins](https://pypi.org/project/mdit-py-plugins) | 0.3.5 | Collection of plugins for markdown-it-py
+[mdurl](https://pypi.org/project/mdurl) | 0.1.2 | Markdown URL utilities
+[mercantile](https://pypi.org/project/mercantile) | 1.2.1 | Web mercator XYZ tile utilities
+[mergedeep](https://pypi.org/project/mergedeep) | 1.3.4 | A deep merge function for 🐍.
+[missingno](https://pypi.org/project/missingno) | 0.5.1 | Missing data visualization module for Python.
+[mistralai](https://pypi.org/project/mistralai) | 1.2.5 | Python Client SDK for the Mistral AI API.
+[mistune](https://pypi.org/project/mistune) | 2.0.5 | A sane Markdown parser with useful plugins and renderers
+[mizani](https://pypi.org/project/mizani) | 0.11.4 | Scales for Python
+[ml-dtypes](https://pypi.org/project/ml-dtypes) | 0.5.0 |
+[mlxtend](https://pypi.org/project/mlxtend) | 0.23.3 | Machine Learning Library Extensions
+[more-itertools](https://pypi.org/project/more-itertools) | 10.2.0 | More routines for operating on iterables, beyond itertools
+[mpl-scatter-density](https://pypi.org/project/mpl-scatter-density) | 0.7 | Matplotlib helpers to make density scatter plots
+[mpld3](https://pypi.org/project/mpld3) | 0.5.8 | D3 Viewer for Matplotlib
+[mpmath](https://pypi.org/project/mpmath) | 1.3.0 | Python library for arbitrary-precision floating-point arithmetic
+[msal](https://pypi.org/project/msal) | 1.30.0 | The Microsoft Authentication Library (MSAL) for Python library enables your app to access the Microsoft Cloud by supporting authentication of us
+[msal-extensions](https://pypi.org/project/msal-extensions) | 1.2.0 | Microsoft Authentication Library extensions (MSAL EX) provides a persistence API that can save your data on disk, encrypted on Windows, macOS an
+[msgpack](https://pypi.org/project/msgpack) | 1.1.0 | MessagePack serializer
+[multidict](https://pypi.org/project/multidict) | 6.1.0 | multidict implementation
+[multipledispatch](https://pypi.org/project/multipledispatch) | 1.0.0 | Multiple dispatch
+[mypy](https://pypi.org/project/mypy) | 1.15.0 | Optional static typing for Python
+[mypy-extensions](https://pypi.org/project/mypy-extensions) | 1.0.0 | Type system extensions for programs checked with the mypy type checker.
+[mysql-connector-python](https://pypi.org/project/mysql-connector-python) | 9.2.0 | A self-contained Python driver for communicating with MySQL servers, using an API that is compliant with the Python Database API Specification v
+[namex](https://pypi.org/project/namex) | 0.0.8 | A simple utility to separate the implementation of your Python package and its public API surface.
+[narwhals](https://pypi.org/project/narwhals) | 1.30.0 | Extremely lightweight compatibility layer between dataframe libraries
+[nbclient](https://pypi.org/project/nbclient) | 0.10.0 | A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor.
+[nbconvert](https://pypi.org/project/nbconvert) | 7.16.1 | Converting Jupyter Notebooks (.ipynb files) to other formats. Output formats include asciidoc, html, latex, markdown, pdf, py, rst, script.
+[nbformat](https://pypi.org/project/nbformat) | 5.10.4 | The Jupyter Notebook format
+[nest-asyncio](https://pypi.org/project/nest-asyncio) | 1.6.0 | Patch asyncio to allow nested event loops
+[networkx](https://pypi.org/project/networkx) | 3.4.2 | Python package for creating and manipulating graphs and networks
+[nh3](https://pypi.org/project/nh3) | 0.2.18 | Python bindings to the ammonia HTML sanitization library.
+[nltk](https://pypi.org/project/nltk) | 3.9.1 | Natural Language Toolkit
+[notebook](https://pypi.org/project/notebook) | 7.4.0 | Jupyter Notebook - A web-based notebook environment for interactive computing
+[notebook-shim](https://pypi.org/project/notebook-shim) | 0.2.4 | A shim layer for notebook traits and config
+[numba](https://pypi.org/project/numba) | 0.61.2 | compiling Python code using LLVM
+[numpy](https://pypi.org/project/numpy) | 2.2.4 | Fundamental package for array computing in Python
+[numpydoc](https://pypi.org/project/numpydoc) | 1.6.0 | Sphinx extension to support docstrings in Numpy format
+[openai](https://pypi.org/project/openai) | 1.72.0 | The official Python library for the openai API
+[opencv-python](https://pypi.org/project/opencv-python) | 4.11.0.86 | Wrapper package for OpenCV python bindings.
+[openpyxl](https://pypi.org/project/openpyxl) | 3.1.2 | A Python library to read/write Excel 2010 xlsx/xlsm files
+[optree](https://pypi.org/project/optree) | 0.14.0 | Optimized PyTree Utilities.
+[optuna](https://pypi.org/project/optuna) | 3.6.1 | A hyperparameter optimization framework
+[orjson](https://pypi.org/project/orjson) | 3.10.12 | Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy
+[osqp](https://pypi.org/project/osqp) | 0.6.7.post3 | OSQP: The Operator Splitting QP Solver
+[outcome](https://pypi.org/project/outcome) | 1.3.0.post0 | Capture the outcome of Python function calls.
+[overrides](https://pypi.org/project/overrides) | 7.7.0 | A decorator to automatically detect mismatch when overriding a method.
+[packaging](https://pypi.org/project/packaging) | 24.2 | Core utilities for Python packages
+[pandas](https://pypi.org/project/pandas) | 2.2.3 | Powerful data structures for data analysis, time series, and statistics
+[pandocfilters](https://pypi.org/project/pandocfilters) | 1.5.0 | Utilities for writing pandoc filters in python
+[panel](https://pypi.org/project/panel) | 1.6.2 | The powerful data exploration & web app framework for Python.
+[papermill](https://pypi.org/project/papermill) | 2.6.0 | Parameterize and run Jupyter and nteract Notebooks
+[param](https://pypi.org/project/param) | 2.2.0 | Make your Python code clearer and more reliable by declaring Parameters.
+[parso](https://pypi.org/project/parso) | 0.8.4 | A Python Parser
+[partd](https://pypi.org/project/partd) | 1.4.0 | Appendable key-value storage
+[pathspec](https://pypi.org/project/pathspec) | 0.11.0 | Utility library for gitignore style pattern matching of file paths.
+[patsy](https://pypi.org/project/patsy) | 0.5.6 | A Python package for describing statistical models and for building design matrices.
+[pep8](https://pypi.org/project/pep8) | 1.7.1 | Python style guide checker
+[pexpect](https://pypi.org/project/pexpect) | 4.8.0 | Pexpect allows easy control of interactive console applications.
+[pg8000](https://pypi.org/project/pg8000) | 1.23.0 | PostgreSQL interface library
+[pickleshare](https://pypi.org/project/pickleshare) | 0.7.5 | Tiny 'shelve'-like database with concurrency support
+[pillow](https://pypi.org/project/pillow) | 11.1.0 | Python Imaging Library (Fork)
+[pip](https://pypi.org/project/pip) | 25.0.1 | The PyPA recommended tool for installing Python packages.
+[platformdirs](https://pypi.org/project/platformdirs) | 4.3.6 | A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`.
+[plotly](https://pypi.org/project/plotly) | 6.0.1 | An open-source interactive data visualization library for Python
+[plotnine](https://pypi.org/project/plotnine) | 0.13.6 | A Grammar of Graphics for Python
+[plotpy](https://pypi.org/project/plotpy) | 2.7.2 | Curve and image plotting tools for Python/Qt applications
+[pluggy](https://pypi.org/project/pluggy) | 1.5.0 | plugin and hook calling mechanisms for python
+[ply](https://pypi.org/project/ply) | 3.11 | Python Lex & Yacc
+[polars](https://pypi.org/project/polars) | 1.27.1 | Blazingly fast DataFrame library
+[portalocker](https://pypi.org/project/portalocker) | 2.7.0 | Wraps the portalocker recipe for easy usage
+[prettytable](https://pypi.org/project/prettytable) | 3.3.0 | A simple Python library for easily displaying tabular data in a visually appealing ASCII table format
+[prince](https://pypi.org/project/prince) | 0.15.0 | Factor analysis in Python: PCA, CA, MCA, MFA, FAMD, GPA
+[priority](https://pypi.org/project/priority) | 2.0.0 | A pure-Python implementation of the HTTP/2 priority tree
+[prometheus-client](https://pypi.org/project/prometheus-client) | 0.21.1 | Python client for the Prometheus monitoring system.
+[prompt-toolkit](https://pypi.org/project/prompt-toolkit) | 3.0.50 | Library for building powerful interactive command lines in Python
+[propcache](https://pypi.org/project/propcache) | 0.2.1 | Accelerated property cache
+[protobuf](https://pypi.org/project/protobuf) | 5.27.3 |
+[psutil](https://pypi.org/project/psutil) | 5.9.8 | Cross-platform lib for process and system monitoring in Python.
+[psygnal](https://pypi.org/project/psygnal) | 0.11.1 | Fast python callback/event system modeled after Qt Signals
+[ptpython](https://pypi.org/project/ptpython) | 3.0.29 | Python REPL build on top of prompt_toolkit
+[ptyprocess](https://pypi.org/project/ptyprocess) | 0.7.0 | Run a subprocess in a pseudo terminal
+[pure-eval](https://pypi.org/project/pure-eval) | 0.2.2 | Safely evaluate AST nodes without side effects
+[pyarrow](https://pypi.org/project/pyarrow) | 19.0.1 | Python library for Apache Arrow
+[pyasn1](https://pypi.org/project/pyasn1) | 0.6.1 | Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)
+[pyasn1-modules](https://pypi.org/project/pyasn1-modules) | 0.4.1 | A collection of ASN.1-based protocols modules
+[pybind11](https://pypi.org/project/pybind11) | 2.13.6 | Seamless operability between C++11 and Python
+[pycodestyle](https://pypi.org/project/pycodestyle) | 2.12.0 | Python style guide checker
+[pycparser](https://pypi.org/project/pycparser) | 2.22 | C parser in Python
+[pyct](https://pypi.org/project/pyct) | 0.5.0 | Python package common tasks for users (e.g. copy examples, fetch data, ...)
+[pydantic](https://pypi.org/project/pydantic) | 2.10.6 | Data validation using Python type hints
+[pydantic-ai](https://pypi.org/project/pydantic-ai) | 0.0.24 | Agent Framework / shim to use Pydantic with LLMs
+[pydantic-ai-slim](https://pypi.org/project/pydantic-ai-slim) | 0.0.24 | Agent Framework / shim to use Pydantic with LLMs, slim package
+[pydantic-core](https://pypi.org/project/pydantic-core) | 2.27.2 | Core functionality for Pydantic validation and serialization
+[pydantic-graph](https://pypi.org/project/pydantic-graph) | 0.0.24 | Graph and state machine library
+[pydeck](https://pypi.org/project/pydeck) | 0.9.1 | Widget for deck.gl maps
+[pydocstyle](https://pypi.org/project/pydocstyle) | 6.3.0 | Python docstring style checker
+[pydub](https://pypi.org/project/pydub) | 0.25.1 | Manipulate audio with an simple and easy high level interface
+[pyerfa](https://pypi.org/project/pyerfa) | 2.0.1.4 | Python bindings for ERFA
+[pyflakes](https://pypi.org/project/pyflakes) | 3.2.0 | passive checker of Python programs
+[pygithub](https://pypi.org/project/pygithub) | 2.6.1 | Use the full Github API v3
+[pygments](https://pypi.org/project/pygments) | 2.19.1 | Pygments is a syntax highlighting package written in Python.
+[pyjwt](https://pypi.org/project/pyjwt) | 2.10.1 | JSON Web Token implementation in Python
+[pylint](https://pypi.org/project/pylint) | 3.1.0 | python code static checker
+[pylint-venv](https://pypi.org/project/pylint-venv) | 3.0.3 | pylint-venv provides a Pylint init-hook to use the same Pylint installation with different virtual environments.
+[pyls-spyder](https://pypi.org/project/pyls-spyder) | 0.4.0 | Spyder extensions for the python-lsp-server
+[pymongo](https://pypi.org/project/pymongo) | 4.10.1 | Python driver for MongoDB
+[pympler](https://pypi.org/project/pympler) | 1.1 | A development tool to measure, monitor and analyze the memory behavior of Python objects.
+[pynacl](https://pypi.org/project/pynacl) | 1.5.0 | Python binding to the Networking and Cryptography (NaCl) library
+[pynndescent](https://pypi.org/project/pynndescent) | 0.5.12 | Nearest Neighbor Descent
+[pyodbc](https://pypi.org/project/pyodbc) | 5.2.0 | DB API module for ODBC
+[pyogrio](https://pypi.org/project/pyogrio) | 0.10.0 | Vectorized spatial vector file format I/O using GDAL/OGR
+[pyomo](https://pypi.org/project/pyomo) | 6.9.1 | Pyomo: Python Optimization Modeling Objects
+[pypandoc](https://pypi.org/project/pypandoc) | 1.15 | Thin wrapper for pandoc.
+[pyparsing](https://pypi.org/project/pyparsing) | 3.2.1 | pyparsing module - Classes and methods to define and execute parsing grammars
+[pypdf](https://pypi.org/project/pypdf) | 5.1.0 | A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files
+[pyproj](https://pypi.org/project/pyproj) | 3.7.0 | Python interface to PROJ (cartographic projections and coordinate transformations library)
+[pyproject-hooks](https://pypi.org/project/pyproject-hooks) | 1.1.0 | Wrappers to call pyproject.toml-based build backend hooks.
+[pyqt5](https://pypi.org/project/pyqt5) | 5.15.10 | Python bindings for the Qt cross platform application toolkit
+[pyqt5-qt5](https://pypi.org/project/pyqt5-qt5) | 5.15.2 | The subset of a Qt installation needed by PyQt5.
+[pyqt5-sip](https://pypi.org/project/pyqt5-sip) | 12.16.1 | The sip module support for PyQt5
+[pyqtgraph](https://pypi.org/project/pyqtgraph) | 0.13.7 | Scientific Graphics and GUI Library for Python
+[pyqtwebengine](https://pypi.org/project/pyqtwebengine) | 5.15.6 | Python bindings for the Qt WebEngine framework
+[pyqtwebengine-qt5](https://pypi.org/project/pyqtwebengine-qt5) | 5.15.2 | The subset of a Qt installation needed by PyQtWebEngine.
+[pyserial](https://pypi.org/project/pyserial) | 3.5 | Python Serial Port Extension
+[pysocks](https://pypi.org/project/pysocks) | 1.7.1 | A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information.
+[pyspnego](https://pypi.org/project/pyspnego) | 0.11.2 | Windows Negotiate Authentication Client and Server
+[pytest](https://pypi.org/project/pytest) | 8.3.4 | pytest: simple powerful testing with Python
+[python-barcode](https://pypi.org/project/python-barcode) | 0.15.1 | Create standard barcodes with Python. No external modules needed. (optional Pillow support included).
+[python-dateutil](https://pypi.org/project/python-dateutil) | 2.8.2 | Extensions to the standard Python datetime module
+[python-dotenv](https://pypi.org/project/python-dotenv) | 1.0.1 | Read key-value pairs from a .env file and set them as environment variables
+[python-json-logger](https://pypi.org/project/python-json-logger) | 2.0.7 | A python library adding a json log formatter
+[python-lsp-black](https://pypi.org/project/python-lsp-black) | 2.0.0 | Black plugin for the Python LSP Server
+[python-lsp-jsonrpc](https://pypi.org/project/python-lsp-jsonrpc) | 1.1.2 | JSON RPC 2.0 server library
+[python-lsp-server](https://pypi.org/project/python-lsp-server) | 1.12.0 | Python Language Server for the Language Server Protocol
+[python-multipart](https://pypi.org/project/python-multipart) | 0.0.9 | A streaming multipart parser for Python
+[python-slugify](https://pypi.org/project/python-slugify) | 8.0.4 | A Python slugify application that also handles Unicode
+[pythonqwt](https://pypi.org/project/pythonqwt) | 0.14.4 | Qt plotting widgets for Python
+[pytoolconfig](https://pypi.org/project/pytoolconfig) | 1.3.1 | Python tool configuration
+[pytz](https://pypi.org/project/pytz) | 2024.2 | World timezone definitions, modern and historical
+[pyuca](https://pypi.org/project/pyuca) | 1.2 | a Python implementation of the Unicode Collation Algorithm
+[pyusb](https://pypi.org/project/pyusb) | 1.3.1 | Easy USB access for Python
+[pyviz-comms](https://pypi.org/project/pyviz-comms) | 3.0.3 | A JupyterLab extension for rendering HoloViz content.
+[pywavelets](https://pypi.org/project/pywavelets) | 1.8.0 | PyWavelets, wavelet transform module
+[pywin32](https://pypi.org/project/pywin32) | 308 | Python for Window Extensions
+[pywin32-ctypes](https://pypi.org/project/pywin32-ctypes) | 0.2.2 | A (partial) reimplementation of pywin32 using ctypes/cffi
+[pywinpty](https://pypi.org/project/pywinpty) | 2.0.14 | Pseudo terminal support for Windows from Python.
+[pyyaml](https://pypi.org/project/pyyaml) | 6.0.2 | YAML parser and emitter for Python
+[pyzmq](https://pypi.org/project/pyzmq) | 26.2.1 | Python bindings for 0MQ
+[qdarkstyle](https://pypi.org/project/qdarkstyle) | 3.2.3 | The most complete dark/light style sheet for C++/Python and Qt applications
+[qdldl](https://pypi.org/project/qdldl) | 0.1.7.post5 | QDLDL, a free LDL factorization routine.
+[qrcode](https://pypi.org/project/qrcode) | 8.0 | QR Code image generator
+[qstylizer](https://pypi.org/project/qstylizer) | 0.2.2 | Stylesheet Generator for PyQt{4-5}/PySide{1-2}
+[qtawesome](https://pypi.org/project/qtawesome) | 1.4.0 | FontAwesome icons in PyQt and PySide applications
+[qtconsole](https://pypi.org/project/qtconsole) | 5.6.1 | Jupyter Qt console
+[qtpy](https://pypi.org/project/qtpy) | 2.4.1 | Provides an abstraction layer on top of the various Qt bindings (PyQt5/6 and PySide2/6).
+[quantecon](https://pypi.org/project/quantecon) | 0.7.2 | Import the main names to top level.
+[quart](https://pypi.org/project/quart) | 0.20.0 | A Python ASGI web framework with the same API as Flask
+[rapidfuzz](https://pypi.org/project/rapidfuzz) | 3.9.6 | rapid fuzzy string matching
+[readme-renderer](https://pypi.org/project/readme-renderer) | 44.0 | readme_renderer is a library for rendering readme descriptions for Warehouse
+[redis](https://pypi.org/project/redis) | 5.0.8 | Python client for Redis database and key-value store
+[referencing](https://pypi.org/project/referencing) | 0.35.1 | JSON Referencing + Python
+[regex](https://pypi.org/project/regex) | 2024.11.6 | Alternative regular expression module, to replace re.
+[reportlab](https://pypi.org/project/reportlab) | 4.2.5 | The Reportlab Toolkit
+[requests](https://pypi.org/project/requests) | 2.32.3 | Python HTTP for Humans.
+[requests-ntlm](https://pypi.org/project/requests-ntlm) | 1.3.0 | This package allows for HTTP NTLM authentication using the requests library.
+[requests-toolbelt](https://pypi.org/project/requests-toolbelt) | 1.0.0 | A utility belt for advanced users of python-requests
+[rfc3339-validator](https://pypi.org/project/rfc3339-validator) | 0.1.4 | A pure python RFC3339 validator
+[rfc3986](https://pypi.org/project/rfc3986) | 2.0.0 | Validating URI References per RFC 3986
+[rfc3986-validator](https://pypi.org/project/rfc3986-validator) | 0.1.1 | Pure python rfc3986 validator
+[rich](https://pypi.org/project/rich) | 13.9.4 | Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal
+[rope](https://pypi.org/project/rope) | 1.12.0 | a python refactoring library...
+[rpds-py](https://pypi.org/project/rpds-py) | 0.22.3 | Python bindings to Rust's persistent data structures (rpds)
+[rsa](https://pypi.org/project/rsa) | 4.7.2 | Pure-Python RSA implementation
+[rtree](https://pypi.org/project/rtree) | 1.3.0 | R-Tree spatial index for Python GIS
+[rx](https://pypi.org/project/rx) | 3.2.0 | Reactive Extensions (Rx) for Python
+[scikit-image](https://pypi.org/project/scikit-image) | 0.25.2 | Image processing in Python
+[scikit-learn](https://pypi.org/project/scikit-learn) | 1.6.1 | A set of python modules for machine learning and data mining
+[scipy](https://pypi.org/project/scipy) | 1.15.2 | Fundamental algorithms for scientific computing in Python
+[scramp](https://pypi.org/project/scramp) | 1.4.5 | An implementation of the SCRAM protocol.
+[scs](https://pypi.org/project/scs) | 3.2.7.post2 | Splitting conic solver
+[seaborn](https://pypi.org/project/seaborn) | 0.13.2 | Statistical data visualization
+[send2trash](https://pypi.org/project/send2trash) | 1.8.3 | Send file to trash natively under Mac OS X, Windows and Linux
+[setuptools](https://pypi.org/project/setuptools) | 75.8.2 | Easily download, build, install, upgrade, and uninstall Python packages
+[shapely](https://pypi.org/project/shapely) | 2.0.6 | Manipulation and analysis of geometric objects
+[shellingham](https://pypi.org/project/shellingham) | 1.5.4 | Tool to Detect Surrounding Shell
+[simplejson](https://pypi.org/project/simplejson) | 3.19.3 | Simple, fast, extensible JSON encoder/decoder for Python
+[simpy](https://pypi.org/project/simpy) | 4.1.1 | Event discrete, process based simulation for Python.
+[six](https://pypi.org/project/six) | 1.16.0 | Python 2 and 3 compatibility utilities
+[sklearn-compat](https://pypi.org/project/sklearn-compat) | 0.1.3 | Ease support for compatible scikit-learn estimators across versions
+[skrub](https://pypi.org/project/skrub) | 0.5.1 | Prepping tables for machine learning
+[smmap](https://pypi.org/project/smmap) | 5.0.0 | A pure Python implementation of a sliding window memory map manager
+[sniffio](https://pypi.org/project/sniffio) | 1.3.0 | Sniff out which async library your code is running under
+[snowballstemmer](https://pypi.org/project/snowballstemmer) | 2.2.0 | This package provides 29 stemmers for 28 languages generated from Snowball algorithms.
+[sortedcontainers](https://pypi.org/project/sortedcontainers) | 2.4.0 | Sorted Containers -- Sorted List, Sorted Dict, Sorted Set
+[sounddevice](https://pypi.org/project/sounddevice) | 0.5.1 | Play and Record Sound with Python
+[soupsieve](https://pypi.org/project/soupsieve) | 2.6 | A modern CSS selector implementation for Beautiful Soup.
+[sphinx](https://pypi.org/project/sphinx) | 7.3.7 | Python documentation generator
+[sphinx-rtd-theme](https://pypi.org/project/sphinx-rtd-theme) | 3.0.2 | Read the Docs theme for Sphinx
+[sphinxcontrib-applehelp](https://pypi.org/project/sphinxcontrib-applehelp) | 2.0.0 | sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books
+[sphinxcontrib-devhelp](https://pypi.org/project/sphinxcontrib-devhelp) | 2.0.0 | sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents
+[sphinxcontrib-htmlhelp](https://pypi.org/project/sphinxcontrib-htmlhelp) | 2.1.0 | sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files
+[sphinxcontrib-jquery](https://pypi.org/project/sphinxcontrib-jquery) | 4.1 | Extension to include jQuery on newer Sphinx releases
+[sphinxcontrib-jsmath](https://pypi.org/project/sphinxcontrib-jsmath) | 1.0.1 | A sphinx extension which renders display math in HTML via JavaScript
+[sphinxcontrib-qthelp](https://pypi.org/project/sphinxcontrib-qthelp) | 2.0.0 | sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents
+[sphinxcontrib-serializinghtml](https://pypi.org/project/sphinxcontrib-serializinghtml) | 2.0.0 | sphinxcontrib-serializinghtml is a sphinx extension which outputs "serialized" HTML files (json and pickle)
+[spyder](https://pypi.org/project/spyder) | 6.0.5 | The Scientific Python Development Environment
+[spyder-kernels](https://pypi.org/project/spyder-kernels) | 3.0.3 | Jupyter kernels for Spyder's console
+[sqlalchemy](https://pypi.org/project/sqlalchemy) | 2.0.38 | Database Abstraction Library
+[sqlite-bro](https://pypi.org/project/sqlite-bro) | 0.13.1 | a graphic SQLite Client in 1 Python file
+[sqlite-fts4](https://pypi.org/project/sqlite-fts4) | 1.0.3 | Python functions for working with SQLite FTS4 search
+[sqlite-utils](https://pypi.org/project/sqlite-utils) | 3.38 | CLI tool and Python library for manipulating SQLite databases
+[sqlparse](https://pypi.org/project/sqlparse) | 0.5.3 | A non-validating SQL parser.
+[squarify](https://pypi.org/project/squarify) | 0.4.4 | Pure Python implementation of the squarify treemap layout algorithm
+[sspilib](https://pypi.org/project/sspilib) | 0.2.0 | SSPI API bindings for Python
+[stack-data](https://pypi.org/project/stack-data) | 0.6.3 | Extract data from python stack frames and tracebacks for informative displays
+[starlette](https://pypi.org/project/starlette) | 0.45.3 | The little ASGI library that shines.
+[statsmodels](https://pypi.org/project/statsmodels) | 0.14.4 | Statistical computations and models for Python
+[streamlit](https://pypi.org/project/streamlit) | 1.44.0 | A faster way to build and share data apps
+[superqt](https://pypi.org/project/superqt) | 0.7.1 | Missing widgets and components for PyQt/PySide
+[sv-ttk](https://pypi.org/project/sv-ttk) | 2.6.0 | A gorgeous theme for Tkinter, based on Windows 11's UI
+[sympy](https://pypi.org/project/sympy) | 1.13.3 | Computer algebra system (CAS) in Python
+[tabulate](https://pypi.org/project/tabulate) | 0.9.0 | Pretty-print tabular data
+[tblib](https://pypi.org/project/tblib) | 3.0.0 | Traceback serialization library.
+[tenacity](https://pypi.org/project/tenacity) | 9.0.0 | Retry code until it succeeds
+[termcolor](https://pypi.org/project/termcolor) | 2.5.0 | ANSI color formatting for output in terminal
+[terminado](https://pypi.org/project/terminado) | 0.18.1 | Tornado websocket backend for the Xterm.js Javascript terminal emulator library.
+[text-unidecode](https://pypi.org/project/text-unidecode) | 1.3 | The most basic Text::Unidecode port
+[textdistance](https://pypi.org/project/textdistance) | 4.6.3 | Compute distance between the two texts.
+[thefuzz](https://pypi.org/project/thefuzz) | 0.22.1 | Fuzzy string matching in python
+[threadpoolctl](https://pypi.org/project/threadpoolctl) | 3.5.0 | threadpoolctl
+[three-merge](https://pypi.org/project/three-merge) | 0.1.1 | Simple library for merging two strings with respect to a base one
+[tifffile](https://pypi.org/project/tifffile) | 2025.1.10 | Read and write TIFF files
+[tiktoken](https://pypi.org/project/tiktoken) | 0.8.0 | tiktoken is a fast BPE tokeniser for use with OpenAI's models
+[tinycss2](https://pypi.org/project/tinycss2) | 1.4.0 | A tiny CSS parser
+[tokenizers](https://pypi.org/project/tokenizers) | 0.21.0 |
+[toml](https://pypi.org/project/toml) | 0.10.2 | Python Library for Tom's Obvious, Minimal Language
+[tomli](https://pypi.org/project/tomli) | 2.2.1 | A lil' TOML parser
+[tomli-w](https://pypi.org/project/tomli-w) | 1.2.0 | A lil' TOML writer
+[tomlkit](https://pypi.org/project/tomlkit) | 0.13.2 | Style preserving TOML library
+[toolz](https://pypi.org/project/toolz) | 1.0.0 | List processing tools and functional utilities
+[tornado](https://pypi.org/project/tornado) | 6.4.2 | Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed.
+[tqdm](https://pypi.org/project/tqdm) | 4.66.4 | Fast, Extensible Progress Meter
+[traitlets](https://pypi.org/project/traitlets) | 5.14.1 | Traitlets Python configuration system
+[traittypes](https://pypi.org/project/traittypes) | 0.2.1 | Scipy trait types
+[trio](https://pypi.org/project/trio) | 0.29.0 | A friendly Python library for async concurrency and I/O
+[trove-classifiers](https://pypi.org/project/trove-classifiers) | 2024.10.21.16 | Canonical source for classifiers on PyPI (pypi.org).
+[twine](https://pypi.org/project/twine) | 6.1.0 | Collection of utilities for publishing packages on PyPI
+[typer](https://pypi.org/project/typer) | 0.15.2 | Typer, build great CLIs. Easy to code. Based on Python type hints.
+[types-python-dateutil](https://pypi.org/project/types-python-dateutil) | 2.9.0.20240316 | Typing stubs for python-dateutil
+[types-requests](https://pypi.org/project/types-requests) | 2.32.0.20241016 | Typing stubs for requests
+[typing-extensions](https://pypi.org/project/typing-extensions) | 4.12.2 | Backported and Experimental Type Hints for Python 3.8+
+[typing-inspect](https://pypi.org/project/typing-inspect) | 0.9.0 | Runtime inspection utilities for typing module.
+[tzdata](https://pypi.org/project/tzdata) | 2024.1 | Provider of IANA time zone data
+[tzlocal](https://pypi.org/project/tzlocal) | 5.2 | tzinfo object for the local timezone
+[uc-micro-py](https://pypi.org/project/uc-micro-py) | 1.0.1 | Micro subset of unicode data files for linkify-it-py projects.
+[ujson](https://pypi.org/project/ujson) | 5.10.0 | Ultra fast JSON encoder and decoder for Python
+[umap-learn](https://pypi.org/project/umap-learn) | 0.5.6 | Uniform Manifold Approximation and Projection
+[uncertainties](https://pypi.org/project/uncertainties) | 3.2.2 | calculations with values with uncertainties, error propagation
+[uri-template](https://pypi.org/project/uri-template) | 1.3.0 | RFC 6570 URI Template Processor
+[urllib3](https://pypi.org/project/urllib3) | 2.2.3 | HTTP library with thread-safe connection pooling, file post, and more.
+[uvicorn](https://pypi.org/project/uvicorn) | 0.34.0 | The lightning-fast ASGI server.
+[vega-datasets](https://pypi.org/project/vega-datasets) | 0.9.0 | A Python package for offline access to Vega datasets
+[waitress](https://pypi.org/project/waitress) | 3.0.0 | Waitress WSGI server
+[watchdog](https://pypi.org/project/watchdog) | 6.0.0 | Filesystem events monitoring
+[wcwidth](https://pypi.org/project/wcwidth) | 0.2.13 | Measures the displayed width of unicode strings in a terminal
+[webcolors](https://pypi.org/project/webcolors) | 24.11.1 | A library for working with the color formats defined by HTML and CSS.
+[webencodings](https://pypi.org/project/webencodings) | 0.5.1 | Character encoding aliases for legacy web content
+[websocket-client](https://pypi.org/project/websocket-client) | 1.8.0 | WebSocket client for Python with low level API options
+[werkzeug](https://pypi.org/project/werkzeug) | 3.1.3 | The comprehensive WSGI web application library.
+[whatthepatch](https://pypi.org/project/whatthepatch) | 1.0.7 | A patch parsing and application library.
+[wheel](https://pypi.org/project/wheel) | 0.45.1 | A built-package format for Python
+[widgetsnbextension](https://pypi.org/project/widgetsnbextension) | 4.0.14 | Jupyter interactive widgets for Jupyter Notebook
+[winpython](https://pypi.org/project/winpython) | 15.3.20250425 | WinPython distribution tools, including WPPM
+[wordcloud](https://pypi.org/project/wordcloud) | 1.9.4 | A little word cloud generator
+[wrapt](https://pypi.org/project/wrapt) | 1.16.0 | Module for decorators, wrappers and monkey patching.
+[wsproto](https://pypi.org/project/wsproto) | 1.2.0 | WebSockets state-machine based protocol implementation
+[xarray](https://pypi.org/project/xarray) | 2025.3.0 | N-D labeled arrays and datasets in Python
+[xlsxwriter](https://pypi.org/project/xlsxwriter) | 3.1.9 | A Python module for creating Excel XLSX files.
+[xyzservices](https://pypi.org/project/xyzservices) | 2023.10.1 | Source of XYZ tiles providers
+[yapf](https://pypi.org/project/yapf) | 0.40.1 | A formatter for Python code.
+[yarl](https://pypi.org/project/yarl) | 1.18.3 | Yet another URL library
+[yt-dlp](https://pypi.org/project/yt-dlp) | 2025.2.19 | A feature-rich command-line audio/video downloader
+[zict](https://pypi.org/project/zict) | 3.0.0 | Mutable mapping tools
+[zipp](https://pypi.org/project/zipp) | 3.21.0 | Backport of pathlib-compatible object wrapper for zip files
+[zstandard](https://pypi.org/project/zstandard) | 0.23.0 | Zstandard bindings for Python
+
+
diff --git a/changelogs/WinPythonslim-64bit-3.13.3.0_History.md b/changelogs/WinPythonslim-64bit-3.13.3.0_History.md
new file mode 100644
index 00000000..9767b1e5
--- /dev/null
+++ b/changelogs/WinPythonslim-64bit-3.13.3.0_History.md
@@ -0,0 +1,148 @@
+## History of changes for WinPython-64bit 3.13.3.0slim
+
+The following changes were made to WinPython-64bit distribution since version 3.13.2.0slim.
+
+
+
+### Tools
+
+New packages:
+
+ * [Pandoc](https://pandoc.org) 3.1.9 (an universal document converter)
+
+### Python packages
+
+New packages:
+
+ * [asyncssh](https://pypi.org/project/asyncssh) 2.20.0 (AsyncSSH: Asynchronous SSHv2 client and server library)
+ * [deprecated](https://pypi.org/project/deprecated) 1.2.14 (Python @deprecated decorator to deprecate old python classes, functions or methods.)
+ * [flexcache](https://pypi.org/project/flexcache) 0.3 (Saves and loads to the cache a transformed versions of a source object.)
+ * [flexparser](https://pypi.org/project/flexparser) 0.4 (Parsing made fun ... using typing.)
+ * [id](https://pypi.org/project/id) 1.5.0 (A tool for generating OIDC identities)
+ * [pygithub](https://pypi.org/project/pygithub) 2.6.1 (Use the full Github API v3)
+ * [pyuca](https://pypi.org/project/pyuca) 1.2 (a Python implementation of the Unicode Collation Algorithm)
+ * [shellingham](https://pypi.org/project/shellingham) 1.5.4 (Tool to Detect Surrounding Shell)
+ * [skrub](https://pypi.org/project/skrub) 0.5.1 (Prepping tables for machine learning)
+ * [superqt](https://pypi.org/project/superqt) 0.7.1 (Missing widgets and components for PyQt/PySide)
+ * [sv-ttk](https://pypi.org/project/sv-ttk) 2.6.0 (A gorgeous theme for Tkinter, based on Windows 11's UI)
+ * [typer](https://pypi.org/project/typer) 0.15.2 (Typer, build great CLIs. Easy to code. Based on Python type hints.)
+ * [wrapt](https://pypi.org/project/wrapt) 1.16.0 (Module for decorators, wrappers and monkey patching.)
+
+Upgraded packages:
+
+ * [alembic](https://pypi.org/project/alembic) 1.13.1 → 1.15.1 (A database migration tool for SQLAlchemy.)
+ * [anthropic](https://pypi.org/project/anthropic) 0.42.0 → 0.49.0 (The official Python library for the anthropic API)
+ * [anyio](https://pypi.org/project/anyio) 4.7.0 → 4.8.0 (High level compatibility layer for multiple asynchronous event loop implementations)
+ * [array-api-compat](https://pypi.org/project/array-api-compat) 1.10.0 → 1.11.1 (A wrapper around NumPy and other array libraries to make them compatible with the Array API standard)
+ * [azure-core](https://pypi.org/project/azure-core) 1.30.2 → 1.32.0 (Microsoft Azure Core Library for Python)
+ * [azure-cosmos](https://pypi.org/project/azure-cosmos) 4.7.0 → 4.9.0 (Microsoft Azure Cosmos Client Library for Python)
+ * [azure-identity](https://pypi.org/project/azure-identity) 1.16.1 → 1.21.0 (Microsoft Azure Identity Library for Python)
+ * [black](https://pypi.org/project/black) 24.10.0 → 25.1.0 (The uncompromising code formatter.)
+ * [bokeh](https://pypi.org/project/bokeh) 3.6.3 → 3.7.2 (Interactive plots and applications in the browser from Python)
+ * [cachetools](https://pypi.org/project/cachetools) 5.4.0 → 5.5.2 (Extensible memoizing collections and decorators)
+ * [certifi](https://pypi.org/project/certifi) 2024.6.2 → 2025.1.31 (Python package for providing Mozilla's CA Bundle.)
+ * [click](https://pypi.org/project/click) 8.1.7 → 8.1.8 (Composable command line interface toolkit)
+ * [cloudpickle](https://pypi.org/project/cloudpickle) 3.0.0 → 3.1.1 (Pickler class to extend the standard pickle.Pickler functionality)
+ * [cvxpy](https://pypi.org/project/cvxpy) 1.6.0 → 1.6.4 (A domain-specific language for modeling convex optimization problems in Python.)
+ * [cython](https://pypi.org/project/cython) 3.0.11 → 3.0.12 (The Cython compiler for writing C extensions in the Python language.)
+ * [dask](https://pypi.org/project/dask) 2024.12.1 → 2025.3.0 (Parallel PyData with Task Scheduling)
+ * [datasette](https://pypi.org/project/datasette) 0.64.8 → 0.65.1 (An open source multi-tool for exploring and publishing data)
+ * [datashader](https://pypi.org/project/datashader) 0.16.3 → 0.17.0 (Data visualization toolchain based on aggregating into a grid)
+ * [diff-match-patch](https://pypi.org/project/diff-match-patch) 20230430 → 20241021 (Repackaging of Google's Diff Match and Patch libraries.)
+ * [distributed](https://pypi.org/project/distributed) 2024.12.1 → 2025.3.0 (Distributed scheduler for Dask)
+ * [docstring-to-markdown](https://pypi.org/project/docstring-to-markdown) 0.13 → 0.15 (On the fly conversion of Python docstrings to markdown)
+ * [duckdb](https://pypi.org/project/duckdb) 1.2.0 → 1.2.2 (DuckDB in-process database)
+ * [executing](https://pypi.org/project/executing) 2.0.1 → 2.2.0 (Get the currently executing AST node of a frame, and other information)
+ * [faker](https://pypi.org/project/faker) 33.3.1 → 36.1.1 (Faker is a Python package that generates fake data for you.)
+ * [fastapi](https://pypi.org/project/fastapi) 0.115.6 → 0.115.8 (FastAPI framework, high performance, easy to learn, fast to code, ready for production)
+ * [filelock](https://pypi.org/project/filelock) 3.14.0 → 3.17.0 (A platform independent file lock.)
+ * [folium](https://pypi.org/project/folium) 0.18.0 → 0.19.5 (Make beautiful maps with Leaflet.js & Python)
+ * [holoviews](https://pypi.org/project/holoviews) 1.20.0 → 1.20.2 (A high-level plotting API for the PyData ecosystem built on HoloViews.)
+ * [hpack](https://pypi.org/project/hpack) 4.0.0 → 4.1.0 (Pure-Python HPACK header encoding)
+ * [huggingface-hub](https://pypi.org/project/huggingface-hub) 0.28.1 → 0.29.3 (Client library to download and publish models, datasets and other repos on the huggingface.co hub)
+ * [hypercorn](https://pypi.org/project/hypercorn) 0.16.0 → 0.17.3 (A ASGI Server based on Hyper libraries and inspired by Gunicorn)
+ * [hyperframe](https://pypi.org/project/hyperframe) 6.0.1 → 6.1.0 (Pure-Python HTTP/2 framing)
+ * [hypothesis](https://pypi.org/project/hypothesis) 6.122.3 → 6.130.4 (A library for property-based testing)
+ * [idna](https://pypi.org/project/idna) 3.7 → 3.10 (Internationalized Domain Names in Applications (IDNA))
+ * [imageio](https://pypi.org/project/imageio) 2.33.1 → 2.37.0 (Library for reading and writing a wide range of image, video, scientific, and volumetric data formats.)
+ * [importlib-metadata](https://pypi.org/project/importlib-metadata) 7.1.0 → 8.6.1 (Read metadata from Python packages)
+ * [ipympl](https://pypi.org/project/ipympl) 0.9.6 → 0.9.7 (Matplotlib Jupyter Extension)
+ * [ipython](https://pypi.org/project/ipython) 8.32.0 → 8.34.0 (IPython: Productive Interactive Computing)
+ * [ipywidgets](https://pypi.org/project/ipywidgets) 8.1.5 → 8.1.6 (Jupyter interactive widgets)
+ * [jupyter-client](https://pypi.org/project/jupyter-client) 8.6.2 → 8.6.3 (Jupyter protocol implementation and client libraries)
+ * [jupyter-events](https://pypi.org/project/jupyter-events) 0.10.0 → 0.12.0 (Jupyter Event System library)
+ * [jupyterlab](https://pypi.org/project/jupyterlab) 4.3.5 → 4.4.1 (JupyterLab computational environment)
+ * [jupyterlab-widgets](https://pypi.org/project/jupyterlab-widgets) 3.0.13 → 3.0.14 (Jupyter interactive widgets for JupyterLab)
+ * [keras](https://pypi.org/project/keras) 3.8.0 → 3.9.2 (Multi-backend Keras)
+ * [langchain](https://pypi.org/project/langchain) 0.3.18 → 0.3.23 (Building applications with LLMs through composability)
+ * [langchain-core](https://pypi.org/project/langchain-core) 0.3.34 → 0.3.51 (Building applications with LLMs through composability)
+ * [langchain-text-splitters](https://pypi.org/project/langchain-text-splitters) 0.3.6 → 0.3.8 (LangChain text splitting utilities)
+ * [langsmith](https://pypi.org/project/langsmith) 0.2.11 → 0.3.24 (Client library to connect to the LangSmith LLM Tracing and Evaluation Platform.)
+ * [markdown](https://pypi.org/project/markdown) 3.5.1 → 3.7 (Python implementation of John Gruber's Markdown.)
+ * [matplotlib](https://pypi.org/project/matplotlib) 3.10.0 → 3.10.1 (Python plotting package)
+ * [mypy](https://pypi.org/project/mypy) 1.14.0 → 1.15.0 (Optional static typing for Python)
+ * [mysql-connector-python](https://pypi.org/project/mysql-connector-python) 8.0.21 → 9.2.0 (A self-contained Python driver for communicating with MySQL servers, using an API that is compliant with the Python Database API Specification v)
+ * [narwhals](https://pypi.org/project/narwhals) 1.21.1 → 1.30.0 (Extremely lightweight compatibility layer between dataframe libraries)
+ * [notebook](https://pypi.org/project/notebook) 7.3.1 → 7.4.0 (Jupyter Notebook - A web-based notebook environment for interactive computing)
+ * [numba](https://pypi.org/project/numba) 0.61.0 → 0.61.2 (compiling Python code using LLVM)
+ * [numpy](https://pypi.org/project/numpy) 2.1.3 → 2.2.4 (Fundamental package for array computing in Python)
+ * [openai](https://pypi.org/project/openai) 1.61.1 → 1.72.0 (The official Python library for the openai API)
+ * [opencv-python](https://pypi.org/project/opencv-python) 4.10.0.84 → 4.11.0.86 (Wrapper package for OpenCV python bindings.)
+ * [optree](https://pypi.org/project/optree) 0.13.1 → 0.14.0 (Optimized PyTree Utilities.)
+ * [panel](https://pypi.org/project/panel) 1.6.0 → 1.6.2 (The powerful data exploration & web app framework for Python.)
+ * [param](https://pypi.org/project/param) 2.1.1 → 2.2.0 (Make your Python code clearer and more reliable by declaring Parameters.)
+ * [pip](https://pypi.org/project/pip) 24.3.1 → 25.0.1 (The PyPA recommended tool for installing Python packages.)
+ * [platformdirs](https://pypi.org/project/platformdirs) 4.2.2 → 4.3.6 (A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`.)
+ * [plotly](https://pypi.org/project/plotly) 5.24.1 → 6.0.1 (An open-source interactive data visualization library for Python)
+ * [polars](https://pypi.org/project/polars) 1.22.0 → 1.27.1 (Blazingly fast DataFrame library)
+ * [prometheus-client](https://pypi.org/project/prometheus-client) 0.18.0 → 0.21.1 (Python client for the Prometheus monitoring system.)
+ * [prompt-toolkit](https://pypi.org/project/prompt-toolkit) 3.0.48 → 3.0.50 (Library for building powerful interactive command lines in Python)
+ * [pyarrow](https://pypi.org/project/pyarrow) 19.0.0 → 19.0.1 (Python library for Apache Arrow)
+ * [pyasn1](https://pypi.org/project/pyasn1) 0.4.8 → 0.6.1 (Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208))
+ * [pyasn1-modules](https://pypi.org/project/pyasn1-modules) 0.2.8 → 0.4.1 (A collection of ASN.1-based protocols modules)
+ * [pyjwt](https://pypi.org/project/pyjwt) 2.8.0 → 2.10.1 (JSON Web Token implementation in Python)
+ * [pympler](https://pypi.org/project/pympler) 1.0.1 → 1.1 (A development tool to measure, monitor and analyze the memory behavior of Python objects.)
+ * [pyomo](https://pypi.org/project/pyomo) 6.8.2 → 6.9.1 (Pyomo: Python Optimization Modeling Objects)
+ * [pypandoc](https://pypi.org/project/pypandoc) 1.5 → 1.15 (Thin wrapper for pandoc.)
+ * [pytest](https://pypi.org/project/pytest) 8.2.2 → 8.3.4 (pytest: simple powerful testing with Python)
+ * [Python](http://www.python.org/) 3.13.2 → 3.13.3 (Python programming language with standard library)
+ * [pytz](https://pypi.org/project/pytz) 2024.1 → 2024.2 (World timezone definitions, modern and historical)
+ * [pyzmq](https://pypi.org/project/pyzmq) 26.2.0 → 26.2.1 (Python bindings for 0MQ)
+ * [qtawesome](https://pypi.org/project/qtawesome) 1.3.1 → 1.4.0 (FontAwesome icons in PyQt and PySide applications)
+ * [qtconsole](https://pypi.org/project/qtconsole) 5.5.2 → 5.6.1 (Jupyter Qt console)
+ * [quart](https://pypi.org/project/quart) 0.19.4 → 0.20.0 (A Python ASGI web framework with the same API as Flask)
+ * [rtree](https://pypi.org/project/rtree) 1.1.0 → 1.3.0 (R-Tree spatial index for Python GIS)
+ * [rx](https://pypi.org/project/rx) 3.1.1 → 3.2.0 (Reactive Extensions (Rx) for Python)
+ * [scikit-image](https://pypi.org/project/scikit-image) 0.25.0 → 0.25.2 (Image processing in Python)
+ * [scipy](https://pypi.org/project/scipy) 1.15.1 → 1.15.2 (Fundamental algorithms for scientific computing in Python)
+ * [send2trash](https://pypi.org/project/send2trash) 1.8.2 → 1.8.3 (Send file to trash natively under Mac OS X, Windows and Linux)
+ * [setuptools](https://pypi.org/project/setuptools) 75.6.0 → 75.8.2 (Easily download, build, install, upgrade, and uninstall Python packages)
+ * [simpy](https://pypi.org/project/simpy) 4.0.1 → 4.1.1 (Event discrete, process based simulation for Python.)
+ * [spyder](https://pypi.org/project/spyder) 5.5.6 → 6.0.5 (The Scientific Python Development Environment)
+ * [spyder-kernels](https://pypi.org/project/spyder-kernels) 2.5.2 → 3.0.3 (Jupyter kernels for Spyder's console)
+ * [sqlalchemy](https://pypi.org/project/sqlalchemy) 2.0.35 → 2.0.38 (Database Abstraction Library)
+ * [starlette](https://pypi.org/project/starlette) 0.41.3 → 0.45.3 (The little ASGI library that shines.)
+ * [streamlit](https://pypi.org/project/streamlit) 1.41.1 → 1.44.0 (A faster way to build and share data apps)
+ * [tomli-w](https://pypi.org/project/tomli-w) 1.1.0 → 1.2.0 (A lil' TOML writer)
+ * [trio](https://pypi.org/project/trio) 0.28.0 → 0.29.0 (A friendly Python library for async concurrency and I/O)
+ * [twine](https://pypi.org/project/twine) 6.0.1 → 6.1.0 (Collection of utilities for publishing packages on PyPI)
+ * [widgetsnbextension](https://pypi.org/project/widgetsnbextension) 4.0.13 → 4.0.14 (Jupyter interactive widgets for Jupyter Notebook)
+ * [winpython](https://pypi.org/project/winpython) 13.1.20250222 → 15.3.20250425 (WinPython distribution tools, including WPPM)
+ * [xarray](https://pypi.org/project/xarray) 2025.1.1 → 2025.3.0 (N-D labeled arrays and datasets in Python)
+ * [yt-dlp](https://pypi.org/project/yt-dlp) 2023.7.6 → 2025.2.19 (A feature-rich command-line audio/video downloader)
+
+Removed packages:
+
+ * [bcrypt](https://pypi.org/project/bcrypt) 4.0.1 (Modern password hashing for your software and your servers)
+ * [dask_expr](https://pypi.org/project/dask_expr) 1.1.21 (High Level Expressions for Dask )
+ * [mutagen](https://pypi.org/project/mutagen) 1.47.0 (read and write audio tags for many formats)
+ * [paramiko](https://pypi.org/project/paramiko) 2.8.0 (SSH2 protocol library)
+ * [pint](https://pypi.org/project/pint) 0.23 (Physical quantities module)
+ * [pkginfo](https://pypi.org/project/pkginfo) 1.11.2 (Query metadata from sdists / bdists / installed packages.)
+ * [pycryptodomex](https://pypi.org/project/pycryptodomex) 3.20.0 (Cryptographic library for Python)
+ * [streamz](https://pypi.org/project/streamz) 0.6.3 (Streams)
+ * [websockets](https://pypi.org/project/websockets) 14.2 (An implementation of the WebSocket Protocol (RFC 6455 & 7692))
+
+
+
+* * *
diff --git a/changelogs/md5_sha1.txt b/changelogs/md5_sha1.txt
index a5e7b0a5..2ab82bcb 100644
--- a/changelogs/md5_sha1.txt
+++ b/changelogs/md5_sha1.txt
@@ -1,3 +1,21 @@
+### WinPython 2025-02 release (May 4th, 2025)
+
+MD5 | SHA-1 | SHA-256 | Binary | Size | blake2b-256
+---------------------------------|------------------------------------------|------------------------------------------------------------------|-----------------------------------|----------------------|-----------------------------------------------------------------
+b5a3814f26dade2f6ee9acf983427a8e | b3826baea1606868628128bab5527e3600342fbf | 517eff0673658da3f2da64a28afbd7733fb91807f7d074d3a9e9e6c0eb1dd523 | Winpython64-3.12.10.0dot.7z | 23 076 535 Bytes | e3e4b8b944e79cf20dea5e7691c24485a5b68bb70d34df6edfb181fd499b0335
+6f354778174e04fdb4c4c28c7a543f31 | 5d1bd5bd76dcb0e61df1a268e4ce47f56acbfe6f | b6d196d72ce443ccfbdb5a640f5645a7ade742092fddde65d73755c4eba89a1d | Winpython64-3.12.10.0dot.exe | 23 291 575 Bytes | 785f61ee3821d772bdbff037f86a4229d707c97d97f2508e8cdd03f81c1a8436
+431e37ef366ebe9ae30fa7fb162bb314 | 32965a55a8bc119b96875c542d9a08c6f4ed9b75 | bcb2a87ef210076ff4507a4a644bfb82daa0c2361e7945e5cb7de161450460f6 | Winpython64-3.12.10.0dot.zip | 38 334 544 Bytes | ab0b83f09299291a36298754a445429964f610f507b18e484f6bdf572880acbe
+6e7131129ff75dd4e8832f7be131dcc3 | 4d6c5b10d4b3044b9a70597fcd4fd34cd5e2b2a8 | 294c287c4a9918c730d26188e2dc34e97a92431c83515165f04c2ea8d33f0c1d | Winpython64-3.12.10.0slim.7z | 624 548 662 Bytes | f0b952a2fe29837783b69dcb4259369ee5c1d40a3220ad0b4dd82b1668322973
+3cefa92f927471aeef6c1fa3b7911166 | 1699db6b9c44b605c9f2031201bb58a46a260f0e | 3ea3368ca0e9182c03e8e0bc38b65c5398dd4f8aee7ccf5ab38d5a9226af6b9a | Winpython64-3.12.10.0slim.exe | 624 763 614 Bytes | 8584e33e01025ce04d0e429f2c08c7fe94576901aabf030341b21e0837ed5966
+95c7326579dbd4a16cb2f5b56a141673 | f28906c1e3e4ba073f0363a4e35326f9e8090ce1 | 13abddd4cc2702be56b672c868d0d1bc085e80748dc7d7e9315ab4080f157f3b | Winpython64-3.13.3.0dot.7z | 24 839 744 Bytes | 1cb42e1ea66116bc0e7804fb44bb519140d5a5acc9bb63c650ef38dc138ca0bb
+bccca9b3e79dfbc13d14837e3ba85c9b | 41c805812b778ccaf3781701264b23e6c9908069 | a6b1882b8eff8a44782bfeec7e5228826ebd38c4234e10ba38a52a85860c41d0 | Winpython64-3.13.3.0dot.exe | 25 054 784 Bytes | eea4a48f72d3ca7e784e86883ef2b46cd48a75fd68321c1088e9ed8c11ad47bd
+06a5eb8fa4f7447ad63b7e214b126fd7 | 80ab8340798a318c09ffe07a77a920d5d1eda2b7 | f7ef5a6cfe23b7f39f87c128594fc6c7f73f42f9516a0b828dac0da187af68ec | Winpython64-3.13.3.0dot.zip | 39 870 933 Bytes | a4f9a910f55c5efdbb843874384d5295ff3b3a45d27cab63f74d2f215f9d798c
+40e207597ddecd78f60e8474d8dc3c81 | 5e4df5e04424a754b33e4a63f4ae31d0a65eb0a5 | 0ee4eaa17ed86fbcb9e861989e08c85dcf13ca1ef067c52f4fc8b68dd7836c46 | Winpython64-3.13.3.0slim.7z | 629 049 097 Bytes | f275d41a374ff5f3b75decbe65cd52a54c1ac8157edeb62f377dfa2278201ccb
+312a8e7fa70b5b58e44f69c1066687a8 | 12e992d1e40fb04c0e7fe367a983dc27ef88b151 | 9abfd2a54ba314dfe573792a573ad31a43e63765f7b77c2002849086a1a47c5e | Winpython64-3.13.3.0slim.exe | 629 265 120 Bytes | 87fc9b086fae8a07b234fcced8b428cd0c3367a9ff068cd3c28b661813c94cab
+588e3a1a7ddc461f528270a302a3b667 | a77e443c86c1d21c9a177193e69847efa6346373 | 060046dc83030a639d50396f403a28556ebbd87495bb14045b21118e6cff4c4b | requirement_hash.WinPython64-3.12.10..0slim.txt | 78 133 Bytes | 54d3902407c7c935d83681796585e24d80478f68d22d62ff500d4072ab0385b0
+fb5edb31a3f1e0e7f43eb810f5dd4032 | c6c043e8ec9c7af50eb2d297a91c1b87d3bc7074 | 225500ba0b3aea779b24f7b17bece53250b24380390685bac5ae6c5804f68367 | requirement_hash.WinPython64-3.13.3.0slim.txt | 78 125 Bytes | 1b3d8ff70b049c368ae848d69331faf2ceb19ddc6821ec3549e5511ecac7f393
+
+
### WinPython 2025-01 release (March 1st, 2025)
MD5 | SHA-1 | SHA-256 | Binary | Size | blake2b-256
diff --git a/diff.py b/diff.py
index ec658aba..1fd01586 100644
--- a/diff.py
+++ b/diff.py
@@ -1,122 +1,82 @@
# -*- coding: utf-8 -*-
#
+# WinPython diff.py script
# Copyright © 2013 Pierre Raybaut
+# Copyright © 2014-2025+ The Winpython development team https://github.com/winpython/
# Licensed under the terms of the MIT License
# (see winpython/__init__.py for details)
-"""
-WinPython diff script
-
-Created on Tue Jan 29 11:56:54 2013
-"""
-
import os
from pathlib import Path
import re
import shutil
-
-# Local imports
+from packaging import version
from winpython import utils
+CHANGELOGS_DIR = Path(__file__).parent / "changelogs"
+assert CHANGELOGS_DIR.is_dir()
-CHANGELOGS_DIR = str(Path(__file__).parent / "changelogs")
-assert Path(CHANGELOGS_DIR).is_dir()
-
-
-class Package(object):
+class Package:
# SourceForge Wiki syntax:
PATTERN = r"\[([a-zA-Z\-\:\/\.\_0-9]*)\]\(([^\]\ ]*)\) \| ([^\|]*) \| ([^\|]*)"
# Google Code Wiki syntax:
PATTERN_OLD = r"\[([a-zA-Z\-\:\/\.\_0-9]*) ([^\]\ ]*)\] \| ([^\|]*) \| ([^\|]*)"
def __init__(self):
- self.name = None
- self.version = None
- self.description = None
- self.url = None
+ self.name = self.version = self.description = self.url = None
def __str__(self):
- text = f"{self.name} {self.version}"
- text += f"\r\n{self.description}\r\nWebsite: {self.url}"
- return text
+ return f"{self.name} {self.version}\r\n{self.description}\r\nWebsite: {self.url}"
def from_text(self, text):
- try:
- self.url, self.name, self.version, self.description = re.match(
- self.PATTERN_OLD, text
- ).groups()
- except AttributeError:
- self.name, self.url, self.version, self.description = re.match(
- self.PATTERN, text
- ).groups()
+ match = re.match(self.PATTERN_OLD, text) or re.match(self.PATTERN, text)
+ if not match:
+ raise ValueError("Text does not match expected pattern")
+ self.name, self.url, self.version, self.description = match.groups()
def to_wiki(self):
return f" * [{self.name}]({self.url}) {self.version} ({self.description})\r\n"
def upgrade_wiki(self, other):
- # wheel replace '-' per '_' in package name
- assert (
- self.name.replace("-", "_").lower() == other.name.replace("-", "_").lower()
- )
+ assert self.name.replace("-", "_").lower() == other.name.replace("-", "_").lower()
return f" * [{self.name}]({self.url}) {other.version} → {self.version} ({self.description})\r\n"
-
-class PackageIndex(object):
+class PackageIndex:
WINPYTHON_PATTERN = r"\#\# WinPython\-*[0-9b-t]* ([0-9\.a-zA-Z]*)"
TOOLS_LINE = "### Tools"
PYTHON_PACKAGES_LINE = "### Python packages"
HEADER_LINE1 = "Name | Version | Description"
HEADER_LINE2 = "-----|---------|------------"
- def __init__(
- self,
- version,
- basedir=None,
- flavor="",
- architecture=64,
- ):
+ def __init__(self, version, basedir=None, flavor="", architecture=64):
self.version = version
- self.other_packages = {}
- self.python_packages = {}
self.flavor = flavor
self.basedir = basedir
self.architecture = architecture
+ self.other_packages = {}
+ self.python_packages = {}
self.from_file(basedir)
def from_file(self, basedir):
- fname = str(
- Path(CHANGELOGS_DIR)
- / f"WinPython{self.flavor}-{self.architecture}bit-{self.version}.md"
- )
-
- try:
- with open(fname, "r", encoding = 'utf-8') as fdesc: # python3 doesn't like 'rb'
- text = fdesc.read()
- except:
- with open(fname, "r") as fdesc: # python3 doesn't like 'rb'
- text = fdesc.read()
- self.from_text(text)
+ fname = CHANGELOGS_DIR / f"WinPython{self.flavor}-{self.architecture}bit-{self.version}.md"
+ if not fname.exists():
+ raise FileNotFoundError(f"Changelog file not found: {fname}")
+ with open(fname, "r", encoding=utils.guess_encoding(fname)[0]) as fdesc:
+ self.from_text(fdesc.read())
def from_text(self, text):
version = re.match(self.WINPYTHON_PATTERN + self.flavor, text).groups()[0]
assert version == self.version
- tools_flag = False
- python_flag = False
+ tools_flag = python_flag = False
for line in text.splitlines():
if line:
if line == self.TOOLS_LINE:
- tools_flag = True
+ tools_flag, python_flag = True, False
continue
elif line == self.PYTHON_PACKAGES_LINE:
- tools_flag = False
- python_flag = True
+ tools_flag, python_flag = False, True
continue
- elif line in (
- self.HEADER_LINE1,
- self.HEADER_LINE2,
- "",
- " ",
- ):
+ elif line in (self.HEADER_LINE1, self.HEADER_LINE2, "", " "):
continue
if tools_flag or python_flag:
package = Package()
@@ -126,224 +86,83 @@ def from_text(self, text):
else:
self.python_packages[package.name] = package
+def diff_package_dicts(old_packages, new_packages):
+ """Return difference between package old and package new"""
-def diff_package_dicts(dict1_in, dict2_in):
- """Return difference between package dict1 and package dict2"""
- text = ""
# wheel replace '-' per '_' in key
- dict1 = {}
- dict2 = {}
- for key in dict1_in:
- dict1[key.replace("-", "_").lower()] = dict1_in[key]
- for key in dict2_in:
- dict2[key.replace("-", "_").lower()] = dict2_in[key]
- set1, set2 = set(dict1.keys()), set(dict2.keys())
- # New packages
- new = sorted(set2 - set1)
- if new:
- text += "New packages:\r\n\r\n"
- for name in new:
- package = dict2[name]
- text += package.to_wiki()
- text += "\r\n"
- # Upgraded packages
- upgraded_list = []
- for name in sorted(set1 & set2):
- package1 = dict1[name]
- package2 = dict2[name]
- if package1.version != package2.version:
- upgraded_list.append(package2.upgrade_wiki(package1))
- if upgraded_list:
- text += "Upgraded packages:\r\n\r\n" + f"{''.join(upgraded_list)}" + "\r\n"
- # Removed packages
- removed = sorted(set1 - set2)
- if removed:
- text += "Removed packages:\r\n\r\n"
- for name in removed:
- package = dict1[name]
- text += package.to_wiki()
- text += "\r\n"
- return text
+ old = {k.replace("-", "_").lower(): v for k, v in old_packages.items()}
+ new = {k.replace("-", "_").lower(): v for k, v in new_packages.items()}
+ text = ""
+
+ if new_keys := sorted(set(new) - set(old)):
+ text += "New packages:\r\n\r\n" + "".join(new[k].to_wiki() for k in new_keys) + "\r\n"
+ if upgraded := [new[k].upgrade_wiki(old[k]) for k in sorted(set(old) & set(new)) if old[k].version != new[k].version]:
+ text += "Upgraded packages:\r\n\r\n" + f"{''.join(upgraded)}" + "\r\n"
+
+ if removed_keys := sorted(set(old) - set(new)):
+ text += "Removed packages:\r\n\r\n" + "".join(old[k].to_wiki() for k in removed_keys) + "\r\n"
+ return text
def find_closer_version(version1, basedir=None, flavor="", architecture=64):
"""Find version which is the closest to `version`"""
- builddir = str(Path(basedir) / f"bu{flavor}")
- func = lambda name: re.match(
- r"WinPython%s-%sbit-([0-9\.]*)\.(txt|md)" % (flavor, architecture),
- name,
- )
- versions = [func(name).groups()[0] for name in os.listdir(builddir) if func(name)]
- # versions:['3.10.0.1', '3.10.10.0', '3.10.2.0'.... '3.10.8.1', '3.10.9.0']
- try:
- index = versions.index(version1)
- except ValueError:
+ builddir = Path(basedir) / f"bu{flavor}"
+ pattern = re.compile(rf"WinPython{flavor}-{architecture}bit-([0-9\.]*)\.(txt|md)")
+ versions = [pattern.match(name).groups()[0] for name in os.listdir(builddir) if pattern.match(name)]
+
+ if version1 not in versions:
raise ValueError(f"Unknown version {version1}")
- from packaging import version
version_below = '0.0.0.0'
for v in versions:
- if version.parse(v) > version.parse(version_below) and version.parse(v)",
- "",
- ]
- )
- pi1 = PackageIndex(
- version1,
- basedir=basedir,
- flavor=flavor1,
- architecture=architecture,
- )
- pi2 = PackageIndex(
- version2,
- basedir=basedir,
- flavor=flavor,
- architecture=architecture,
+ version1 = version1 if version1 else find_closer_version(version2, basedir, flavor, architecture)
+ flavor1 = flavor1 if flavor1 else flavor
+ pi1 = PackageIndex(version1, basedir, flavor1, architecture)
+ pi2 = PackageIndex(version2, basedir, flavor, architecture)
+
+ text = (
+ f"## History of changes for WinPython-{architecture}bit {version2 + flavor}\r\n\r\n"
+ f"The following changes were made to WinPython-{architecture}bit distribution since version {version1 + flavor1}.\r\n\r\n"
+ "\r\n\r\n"
)
+
tools_text = diff_package_dicts(pi1.other_packages, pi2.other_packages)
if tools_text:
text += PackageIndex.TOOLS_LINE + "\r\n\r\n" + tools_text
+
py_text = diff_package_dicts(pi1.python_packages, pi2.python_packages)
if py_text:
text += PackageIndex.PYTHON_PACKAGES_LINE + "\r\n\r\n" + py_text
+
text += "\r\n \r\n* * *\r\n"
return text
-
def _copy_all_changelogs(version, basedir, flavor="", architecture=64):
basever = ".".join(version.split(".")[:2])
+ pattern = re.compile(rf"WinPython{flavor}-{architecture}bit-{basever}([0-9\.]*)\.(txt|md)")
for name in os.listdir(CHANGELOGS_DIR):
- if re.match(
- r"WinPython%s-%sbit-%s([0-9\.]*)\.(txt|md)"
- % (flavor, architecture, basever),
- name,
- ):
- shutil.copyfile(
- str(Path(CHANGELOGS_DIR) / name),
- str(Path(basedir) / f"bu{flavor}" / name),
- )
-
+ if pattern.match(name):
+ shutil.copyfile(CHANGELOGS_DIR / name, Path(basedir) / f"bu{flavor}" / name)
-def write_changelog(
- version2,
- version1=None,
- basedir=None,
- flavor="",
- release_level="",
- architecture=64,
-):
+def write_changelog(version2, version1=None, basedir=None, flavor="", architecture=64):
"""Write changelog between version1 and version2 of WinPython"""
- _copy_all_changelogs(
- version2,
- basedir,
- flavor=flavor,
- architecture=architecture,
- )
- print(
- "comparing_package_indexes",
- version2,
- basedir,
- flavor,
- architecture,
- )
- text = compare_package_indexes(
- version2,
- version1,
- basedir=basedir,
- flavor=flavor,
- architecture=architecture,
- )
- fname = str(
- Path(basedir)
- / f"bu{flavor}"
- / f"WinPython{flavor}-{architecture}bit-{version2}_History.md"
- )
+ _copy_all_changelogs(version2, basedir, flavor, architecture)
+ print("comparing_package_indexes", version2, basedir, flavor, architecture)
+ changelog_text = compare_package_indexes(version2, version1, basedir, flavor, architecture=architecture)
+ output_file = Path(basedir) / f"bu{flavor}" / f"WinPython{flavor}-{architecture}bit-{version2}_History.md"
- with open(fname, "w", encoding="utf-8-sig") as fdesc: # python 3 need
- fdesc.write(text)
+ with open(output_file, "w", encoding="utf-8") as fdesc:
+ fdesc.write(changelog_text)
# Copy to winpython/changelogs
- shutil.copyfile(fname, str(Path(CHANGELOGS_DIR) / Path(fname).name))
-
-
-def test_parse_package_index_wiki(version, basedir=None, flavor="", architecture=64):
- """Parse the package index Wiki page"""
- pi = PackageIndex(
- version,
- basedir=basedir,
- flavor=flavor,
- architecture=architecture,
- )
- utils.print_box(f"WinPython {pi.version}:")
- utils.print_box("Tools:")
- for package in pi.other_packages.values():
- print(package)
- print("")
- utils.print_box("Python packages:")
- for package in pi.python_packages.values():
- print(package)
- print("")
-
-
-def test_compare(basedir, version2, version1, architecture=64):
- print(
- compare_package_indexes(
- basedir,
- version2,
- version1,
- architecture=architecture,
- )
- )
-
+ shutil.copyfile(output_file, CHANGELOGS_DIR / output_file.name)
if __name__ == "__main__":
- print(
- compare_package_indexes(
- version2="3.7.4.0",
- version1="3.7.2.0",
- basedir=r"C:\WinP\bd37",
- flavor="Zero",
- flavor1="Zero",
- architecture=32,
- )
- )
- write_changelog(
- version2="3.7.4.0",
- version1="3.7.2.0",
- basedir=r"C:\WinP\bd37",
- flavor="Ps2",
- architecture=64,
- )
- # test_parse_package_index_wiki('2.7.3.3')
- # print(compare_package_indexes('2.7.3.3', '2.7.3.1'))
- # write_changelog('2.7.4.1', '2.7.4.0')
- # write_changelog('3.3.0.0beta2', '3.3.0.0beta1')
+ print(compare_package_indexes("3.7.4.0", "3.7.2.0", r"C:\WinP\bd37", "Zero", architecture=32))
+ write_changelog("3.7.4.0", "3.7.2.0", r"C:\WinP\bd37", "Ps2", architecture=64)
diff --git a/generate_a_winpython_distro.bat b/generate_a_winpython_distro.bat
index f7e79be8..cdabf95e 100644
--- a/generate_a_winpython_distro.bat
+++ b/generate_a_winpython_distro.bat
@@ -26,8 +26,8 @@ if "%target_python_exe%"=="" set target_python_exe=python.exe
rem Set Python target release based on my_python_target
if %my_python_target%==311 set my_python_target_release=3119& set my_release=1
-if %my_python_target%==312 set my_python_target_release=3129& set my_release=1
-if %my_python_target%==313 set my_python_target_release=3132& set my_release=1
+if %my_python_target%==312 set my_python_target_release=31210& set my_release=1
+if %my_python_target%==313 set my_python_target_release=3133& set my_release=1
if %my_python_target%==314 set my_python_target_release=3140& set my_release=0
echo -------------------------------------- >>%my_archive_log%
@@ -99,6 +99,7 @@ echo -------------------------------------- >>%my_archive_log%
echo "(%date% %time%) Add requirement packages">>%my_archive_log%
echo -------------------------------------- >>%my_archive_log%
python -m pip install -r %my_requirements% -c %my_constraints% --pre --no-index --trusted-host=None --find-links=%my_find_links% >>%my_archive_log%
+python -c "from winpython import wppm;dist=wppm.Distribution(r'%WINPYDIR%');dist.patch_standard_packages('spyder', to_movable=True)"
REM Archive success
echo -------------------------------------- >>%my_archive_log%
@@ -111,7 +112,44 @@ echo "(%date% %time%) Generate changelog and binaries">>%my_archive_log%
set path=%my_original_path%
cd /D %~dp0
call %my_buildenv%\scripts\env.bat
-python.exe -c "from make import *;make_all(%my_release%, '%my_release_level%', pyver='%my_pyver%', basedir=r'%my_basedir%', verbose=True, architecture=%my_arch%, flavor='%my_flavor%', install_options=r'%my_install_options%', find_links=r'%my_find_links%', source_dirs=r'%my_source_dirs%', create_installer='%my_create_installer%', remove_existing=False, python_target_release='%my_python_target_release%')" >> %my_archive_log%
+python.exe -c "from make import *;make_all(%my_release%, '%my_release_level%', pyver='%my_pyver%', basedir=r'%my_basedir%', verbose=True, architecture=%my_arch%, flavor='%my_flavor%', install_options=r'%my_install_options%', find_links=r'%my_find_links%', source_dirs=r'%my_source_dirs%', create_installer='%my_create_installer%', rebuild=False, python_target_release='%my_python_target_release%')" >> %my_archive_log%
+
+echo -------------------------------------- >>%my_archive_log%
+echo "(%date% %time%) generate lock files">>%my_archive_log%
+echo -------------------------------------- >>%my_archive_log%
+
+set path=%my_original_path%
+call %my_WINPYDIRBASE%\scripts\env.bat
+
+rem generate pip freeze requirements
+echo %date% %time%
+set LOCKDIR=%WINPYDIRBASE%\notebooks\
+set req=%LOCKDIR%requirement_%WINPYVER%_raw.txt
+set wanted_req=%LOCKDIR%requirement_%WINPYVER%.txt
+set pip_lock_web=%LOCKDIR%pylock_%WINPYVER%.toml
+set pip_lock_local=%LOCKDIR%pylock_%WINPYVER%_local.toml
+set my_archive_lockfile=%my_archive_dir%\pylock_%WINPYVER%_%date:/=-%at_%my_time%.toml
+set my_archive_lockfile_local=%my_archive_dir%\pylock_%WINPYVER%_%date:/=-%at_%my_time%_local.tml
+set my_changelog_lockfile=%~dp0changelogs\pylock_%WINPYVER%.toml
+
+
+python.exe -m pip freeze>%req%
+findstr /v "winpython" %req% > %wanted_req%
+
+rem pip lock from pypi the local, from a frozen req
+python.exe -m pip lock --no-deps -c C:\WinP\constraints.txt -r %wanted_req%
+copy pylock.toml %pip_lock_web%
+python.exe -m pip lock --no-deps --no-index --trusted-host=None --find-links=C:\WinP\packages.srcreq -c C:\WinP\constraints.txt -r %wanted_req%
+copy pylock.toml %pip_lock_local%
+
+rem compare the two
+findstr /V /R "^url =$" %pip_lock_web% > %pip_lock_web%.no_url.txt
+findstr /V /R "^url =$" %pip_lock_local% > %pip_lock_local%.no_url.txt
+
+fc %pip_lock_web%.no_url.txt %pip_lock_local%.no_url.txt
+
+copy/Y %pip_lock_web% %my_archive_lockfile%
+copy/Y %pip_lock_web% %my_changelog_lockfile%
echo -------------------------------------- >>%my_archive_log%
echo "(%date% %time%) END OF CREATION">>%my_archive_log%
diff --git a/hash.py b/hash.py
index 525ab1f8..d16bff0a 100644
--- a/hash.py
+++ b/hash.py
@@ -1,66 +1,45 @@
# -*- coding: utf-8 -*-
-"""
-Created on Tue Jun 23 21:30:06 2015
+#
+# WinPython hash.py script
+# Copyright © 2014-2025+ The Winpython development team https://github.com/winpython/
+# Licensed under the terms of the MIT License
+# (see winpython/__init__.py for details)
-@author: famille
-"""
-
-import io
-import os
+from pathlib import Path
import sys
import hashlib
-
-def give_hash(file_in, with_this):
- with io.open(file_in, 'rb') as f:
- return with_this(f.read()).hexdigest()
-
-def give_hashblake(file_in, with_this):
- with io.open(file_in, 'rb') as f:
- return with_this(f.read(),digest_size=32).hexdigest()
-
-
-if __name__ == '__main__':
- if len(sys.argv) < 2:
- print(
- "Usage: "
- + sys.argv[0]
- + " github-user [github-project]"
- )
- exit(1)
- file = sys.argv[1]
-
- header = (
- " MD5"
- + " " * (32 - 4)
- + " | SHA-1"
- + " " * (40 - 5)
- + " | SHA-256"
- + " " * (64 - 7)
- + " | Binary"
- + " " * (33 - 5)
- + "| Size"
- + " " * (20 - 6)
- #+ " | SHA3-256"
- #+ " " * (64 - 8)
- + " | blake2b-256"
- + " " * (64 - 11)
- )
- line = "|".join(
- ["-" * len(i) for i in header.split("|")]
- )
+def compute_hash(file_path, hash_function, digest_size=None):
+ """Compute the hash of a file using the specified hash function."""
+ try:
+ with open(file_path, 'rb') as file:
+ if digest_size:
+ return hash_function(file.read(), digest_size=digest_size).hexdigest()
+ return hash_function(file.read()).hexdigest()
+ except IOError as e:
+ print(f"Error reading file {file_path}: {e}")
+ return None
+
+def print_hashes(files):
+ """Print the hashes of the given files."""
+ header = f"{'MD5':<32} | {'SHA-1':<40} | {'SHA-256':<64} | {'Binary':<33} | {'Size':<20} | {'blake2b-256':<64}"
+ line = "|".join(["-" * len(part) for part in header.split("|")])
print(header)
print(line)
- print(""+
- f"{give_hash(file, hashlib.md5)} | " +
- f"{give_hash(file, hashlib.sha1)} | " +
- f"{give_hash(file, hashlib.sha256)} | " +
- f"{os.path.basename(file):33} |"+
- f"{os.path.getsize(file):13,}".replace(",", " ") + ' Bytes | ' +
- # f" | {give_hash(file, hashlib.sha3_256)}"
- f"{give_hashblake(file, hashlib.blake2b)}")
+ for file in sorted(files):
+ md5 = compute_hash(file, hashlib.md5)
+ sha1 = compute_hash(file, hashlib.sha1)
+ sha256 = compute_hash(file, hashlib.sha256)
+ name = Path(file).name.ljust(33)
+ size = f"{Path(file).stat().st_size:,} Bytes".replace(",", " ").rjust(20)
+ blake2b = compute_hash(file, hashlib.blake2b, digest_size=32)
+ print(f"{md5} | {sha1} | {sha256} | {name} | {size} | {blake2b}")
-
-
+if __name__ == '__main__':
+ if len(sys.argv) < 2:
+ print("Usage: hash.py files_to_compute_hash")
+ sys.exit(1)
+ files = [file for file in sys.argv[1:] if file[-3:].lower() != ".py"]
+ print_hashes(files)
diff --git a/hash_launch.bat b/hash_launch.bat
index 08105e73..714925a4 100644
--- a/hash_launch.bat
+++ b/hash_launch.bat
@@ -3,6 +3,6 @@ call C:\WPy64-3890\scripts\env.bat
cd %~dp0
rem echo %date% %time%>>gdc_counting.txt
-python hash.py %1 >>hash_counting_%date:/=_%.txt
+python hash.py %* >>hash_counting_%date:/=_%.txt
start notepad.exe hash_counting_%date:/=_%.txt
diff --git a/make.py b/make.py
index 95eace37..c1a6a93a 100644
--- a/make.py
+++ b/make.py
@@ -1,203 +1,107 @@
# -*- coding: utf-8 -*-
#
+# WinPython build script
# Copyright © 2012 Pierre Raybaut
-# Copyright © 2014-2024+ The Winpython development team https://github.com/winpython/
+# Copyright © 2014-2025+ The Winpython development team https://github.com/winpython/
# Licensed under the terms of the MIT License
# (see winpython/__init__.py for details)
-"""
-WinPython build script
-Created on Sun Aug 12 11:17:50 2012
-"""
-
import os
import re
import shutil
import subprocess
import sys
from pathlib import Path
-
from winpython import wppm, utils
-# Local imports
+# Local import
import diff
-CHANGELOGS_DIR = Path(__file__).parent / "changelogs"
-PORTABLE_DIR = Path(__file__).parent / "portable"
-
-assert CHANGELOGS_DIR.is_dir(), f"Changelogs directory not found: {CHANGELOGS_DIR}"
-assert PORTABLE_DIR.is_dir(), f"Portable directory not found: {PORTABLE_DIR}"
+# Define constant paths for clarity
+CHANGELOGS_DIRECTORY = Path(__file__).parent / "changelogs"
+PORTABLE_DIRECTORY = Path(__file__).parent / "portable"
+NODEJS_RELATIVE_PATH = "n" # Relative path within WinPython dir
+# Ensure necessary directories exist at the start
+assert CHANGELOGS_DIRECTORY.is_dir(), f"Changelogs directory not found: {CHANGELOGS_DIRECTORY}"
+assert PORTABLE_DIRECTORY.is_dir(), f"Portable directory not found: {PORTABLE_DIRECTORY}"
def find_7zip_executable() -> str:
- "Locates the 7-Zip executable (7z.exe)"
- program_files_dirs = [
- Path(r"C:\Program Files"),
- Path(r"C:\Program Files (x86)"),
- Path(sys.prefix).parent.parent / "7-Zip",
- ]
- for base_dir in program_files_dirs:
- for subdir in [".", "App"]:
- exe_path = base_dir / subdir / "7-Zip" / "7z.exe"
- if exe_path.is_file():
- return str(exe_path)
+ """Locates the 7-Zip executable (7z.exe)."""
+ possible_program_files = [r"C:\Program Files", r"C:\Program Files (x86)", Path(sys.prefix).parent / "t"]
+ for base_dir in possible_program_files:
+ if (executable_path := Path(base_dir) / "7-Zip" / "7z.exe").is_file():
+ return str(executable_path)
raise RuntimeError("7ZIP is not installed on this computer.")
-
-def replace_lines_in_file(filepath: Path, replacements: list[tuple[str, str]]):
- """
- Replaces lines in a file that start with a given prefix.
-
- Args:
- filepath: Path to the file to modify.
- replacements: A list of tuples, where each tuple contains:
- - The prefix of the line to replace (str).
- - The new text for the line (str).
- """
- lines: list[str] = []
- with open(filepath, "r") as f:
- lines = f.readlines()
-
- updated_lines = list(lines) # Create a mutable copy
-
- for index, line in enumerate(lines):
- for prefix, new_text in replacements:
- start_prefix = prefix
- if prefix not in ("Icon", "OutFile") and not prefix.startswith("!"):
- start_prefix = "set " + prefix
- if line.startswith(start_prefix + "="):
- updated_lines[index] = f"{start_prefix}={new_text}\n"
-
- with open(filepath, "w") as f:
- f.writelines(updated_lines)
- print(f"Updated 7-zip script: {filepath}")
-
-
-def build_installer_7zip(
- script_template_path: Path, output_script_path: Path, replacements: list[tuple[str, str]]
-):
- """
- Creates a 7-Zip installer script by copying a template and applying text replacements.
-
- Args:
- script_template_path: Path to the template 7-Zip script (.bat file).
- output_script_path: Path to save the generated 7-Zip script.
- replacements: A list of tuples for text replacements (prefix, new_text).
- """
- sevenzip_exe = find_7zip_executable()
- shutil.copy(script_template_path, output_script_path)
-
- # Standard replacements for all 7zip scripts
- data = [
- ("PORTABLE_DIR", str(PORTABLE_DIR)),
- ("SEVENZIP_EXE", sevenzip_exe),
- ] + replacements
-
- replace_lines_in_file(output_script_path, data)
-
- try:
- # Execute the generated 7-Zip script
- command = f'"{output_script_path}"'
- print(f"Executing 7-Zip script: {command}")
- subprocess.run(
- command, shell=True, check=True, stderr=sys.stderr, stdout=sys.stderr
- # with stdout=sys.stdout, we would not see 7zip compressing
- ) # Use subprocess.run for better error handling
- except subprocess.CalledProcessError as e:
- print(f"Error executing 7-Zip script: {e}", file=sys.stderr)
-
-
-def _copy_items(source_dirs: list[Path], target_dir: Path, verbose: bool = False):
- "Copies items from source directories to the target directory."
-
- target_dir.mkdir(parents=True, exist_ok=True)
- for source_dir in source_dirs:
+def copy_items(source_directories: list[Path], target_directory: Path, verbose: bool = False):
+ """Copies items from source directories to the target directory."""
+ target_directory.mkdir(parents=True, exist_ok=True)
+ for source_dir in source_directories:
if not source_dir.is_dir():
print(f"Warning: Source directory not found: {source_dir}")
continue
- for item_name in os.listdir(source_dir):
- source_item = source_dir / item_name
- target_item = target_dir / item_name
- copy_func = shutil.copytree if source_item.is_dir() else shutil.copy2
+ for source_item in source_dir.iterdir():
+ target_item = target_directory / source_item.name
+ copy_function = shutil.copytree if source_item.is_dir() else shutil.copy2
try:
- copy_func(source_item, target_item)
+ copy_function(source_item, target_item)
if verbose:
- print(f" Copied: {source_item} -> {target_item}")
+ print(f"Copied: {source_item} -> {target_item}")
except Exception as e:
print(f"Error copying {source_item} to {target_item}: {e}")
+def parse_list_argument(argument_value: str | list[str], separator=" ") -> list[str]:
+ """Parse a separated list argument into a list of strings."""
+ if not argument_value:
+ return []
+ return argument_value.split(separator) if isinstance(argument_value, str) else list(argument_value)
+
class WinPythonDistributionBuilder:
- "Builds a WinPython distribution."
-
- NODEJS_PATH_REL = r"\n" # Relative path within WinPython dir
-
- def __init__(
- self,
- build_number: int,
- release_level: str,
- target_dir: Path,
- wheels_dir: Path,
- tools_dirs: list[Path] = None,
- docs_dirs: list[Path] = None,
- verbose: bool = False,
- base_dir: Path = None,
- install_options: list[str] = None,
- flavor: str = "",
- ):
+ """Builds a WinPython distribution."""
+
+ def __init__(self, build_number: int, release_level: str, target_directory: Path, wheels_directory: Path,
+ tools_directories: list[Path] = None, documentation_directories: list[Path] = None, verbose: bool = False,
+ base_directory: Path = None, install_options: list[str] = None, flavor: str = ""):
"""
Initializes the WinPythonDistributionBuilder.
-
Args:
build_number: The build number (integer).
release_level: The release level (e.g., "beta", "").
- target_dir: The base directory where WinPython will be created.
- wheels_dir: Directory containing wheel files for packages.
- tools_dirs: List of directories containing development tools to include.
- docs_dirs: List of directories containing documentation to include.
+ target_directory: The base directory where WinPython will be created.
+ wheels_directory: Directory containing wheel files for packages.
+ tools_directories: List of directories containing development tools to include.
+ documentation_directories: List of directories containing documentation to include.
verbose: Enable verbose output.
- base_dir: Base directory for building (optional, for relative paths).
+ base_directory: Base directory for building (optional, for relative paths).
install_options: Additional pip install options.
flavor: WinPython flavor (e.g., "Barebone").
"""
self.build_number = build_number
self.release_level = release_level
- self.target_dir = Path(target_dir) # Ensure Path object
- self.wheels_dir = Path(wheels_dir) # Ensure Path object
- self.tools_dirs = tools_dirs or []
- self.docs_dirs = docs_dirs or []
+ self.target_directory = Path(target_directory)
+ self.wheels_directory = Path(wheels_directory)
+ self.tools_directories = tools_directories or []
+ self.documentation_directories = documentation_directories or []
self.verbose = verbose
- self.winpy_dir: Path | None = None # Will be set during build
- self.distribution: wppm.Distribution | None = None # Will be set during build
- self.base_dir = base_dir
+ self.winpython_directory: Path | None = None
+ self.distribution: wppm.Distribution | None = None
+ self.base_directory = base_directory
self.install_options = install_options or []
self.flavor = flavor
self.python_zip_file: Path = self._get_python_zip_file()
- self.python_name = self.python_zip_file.stem # Filename without extension
- self.python_dir_name = "python" # Standardized Python directory name
+ self.python_name = self.python_zip_file.stem
+ self.python_directory_name = "python"
def _get_python_zip_file(self) -> Path:
- "Finds the Python .zip file in the wheels directory."
- patterns = [
- r"(pypy3|python-)([0-9]|[a-zA-Z]|.)*.zip", # PyPy pattern
- r"python-([0-9\.rcba]*)((\.|\-)amd64)?\.(zip|zip)", # Standard Python pattern
- ]
- for pattern in patterns:
- for filename in os.listdir(self.wheels_dir):
- if re.match(pattern, filename):
- return self.wheels_dir / filename
- raise RuntimeError(f"Could not find Python zip package in {self.wheels_dir}")
+ """Finds the Python .zip file in the wheels directory."""
+ for source_item in self.wheels_directory.iterdir():
+ if re.match(r"(pypy3|python-)([0-9]|[a-zA-Z]|.)*.zip", source_item.name):
+ return source_item
+ raise RuntimeError(f"Could not find Python zip package in {self.wheels_directory}")
@property
def package_index_markdown(self) -> str:
- """
- Generates a Markdown formatted package index page.
-
- Returns:
- str: Markdown content for the package index.
- """
- installed_tools_md = self._get_installed_tools_markdown()
- installed_packages_md = self._get_installed_packages_markdown()
- python_description = "Python programming language with standard library"
-
+ """Generates a Markdown formatted package index page."""
return f"""## WinPython {self.winpyver2 + self.flavor}
The following packages are included in WinPython-{self.architecture_bits}bit v{self.winpyver2 + self.flavor} {self.release_level}.
@@ -208,60 +112,16 @@ def package_index_markdown(self) -> str:
Name | Version | Description
-----|---------|------------
-{installed_tools_md}
+{utils.get_installed_tools_markdown(utils.get_python_executable(self.python_executable_directory))}
### Python packages
Name | Version | Description
-----|---------|------------
-[Python](http://www.python.org/) | {self.python_full_version} | {python_description}
-{installed_packages_md}
+{self.distribution.get_installed_packages_markdown()}
"""
- def _get_installed_tools_markdown(self) -> str:
- """Generates Markdown for installed tools section in package index."""
- installed_tools = []
-
- def get_tool_path(rel_path):
- path = self.winpy_dir / rel_path if self.winpy_dir else None
- return path if path and (path.is_file() or path.is_dir()) else None
-
- nodejs_path = get_tool_path(self.NODEJS_PATH_REL)
- if nodejs_path:
- node_version = utils.get_nodejs_version(str(nodejs_path))
- installed_tools.append(("Nodejs", node_version))
- npm_version = utils.get_npmjs_version(str(nodejs_path))
- installed_tools.append(("npmjs", npm_version))
-
- pandoc_exe = get_tool_path(r"\t\pandoc.exe")
- if pandoc_exe:
- pandoc_version = utils.get_pandoc_version(str(pandoc_exe.parent))
- installed_tools.append(("Pandoc", pandoc_version))
-
- vscode_exe = get_tool_path(r"\t\VSCode\Code.exe")
- if vscode_exe:
- vscode_version = utils.getFileProperties(str(vscode_exe))["FileVersion"]
- installed_tools.append(("VSCode", vscode_version))
-
- tool_lines = []
- for name, version in installed_tools:
- metadata = utils.get_package_metadata("tools.ini", name)
- url, description = metadata["url"], metadata["description"]
- tool_lines.append(f"[{name}]({url}) | {version} | {description}")
- return "\n".join(tool_lines)
-
- def _get_installed_packages_markdown(self) -> str:
- """Generates Markdown for installed packages section in package index."""
- if self.distribution is None:
- return "" # Distribution not initialized yet.
- self.installed_packages = self.distribution.get_installed_packages(update=True)
- package_lines = [
- f"[{pkg.name}]({pkg.url}) | {pkg.version} | {pkg.description}"
- for pkg in sorted(self.installed_packages, key=lambda p: p.name.lower())
- ]
- return "\n".join(package_lines)
-
@property
def winpython_version_name(self) -> str:
@@ -270,146 +130,38 @@ def winpython_version_name(self) -> str:
@property
def python_full_version(self) -> str:
- """
- Retrieves the Python full version string from the distribution.
- Will be set after _extract_python is called and distribution is initialized.
- """
- if self.distribution is None:
- return "0.0.0" # Placeholder before initialization
- return utils.get_python_long_version(self.distribution.target)
-
+ """Retrieves the Python full version string from the distribution."""
+ return utils.get_python_long_version(self.distribution.target) if self.distribution else "0.0.0"
@property
- def python_executable_dir(self) -> str:
+ def python_executable_directory(self) -> str:
"""Returns the directory containing the Python executable."""
- python_path_dir = self.winpy_dir / self.python_dir_name if self.winpy_dir else None
- if python_path_dir and python_path_dir.is_dir():
- return str(python_path_dir)
- else:
- python_path_exe = self.winpy_dir / self.python_name if self.winpy_dir else None # Fallback for older structure
- return str(python_path_exe) if python_path_exe else ""
+ if self.winpython_directory:
+ python_path_directory = self.winpython_directory / self.python_directory_name
+ return str(python_path_directory) if python_path_directory.is_dir() else str(self.winpython_directory / self.python_name)
+ return ""
@property
def architecture_bits(self) -> int:
"""Returns the architecture (32 or 64 bits) of the distribution."""
- if self.distribution:
- return self.distribution.architecture
- return 64 # Default to 64 if distribution is not initialized yet
-
- @property
- def pre_path_entries(self) -> list[str]:
- """Returns a list of PATH entries to prepend to the environment."""
- return [
- r"Lib\site-packages\PyQt5",
- "", # Python root directory
- "DLLs",
- "Scripts",
- r"..\t",
- r".." + self.NODEJS_PATH_REL,
- ]
-
- @property
- def post_path_entries(self) -> list[str]:
- """Returns a list of PATH entries to append to the environment."""
- return []
-
- @property
- def tools_directories(self) -> list[Path]:
- """Returns the list of tools directories to include."""
- return self.tools_dirs
-
- @property
- def docs_directories(self) -> list[Path]:
- """Returns the list of documentation directories to include."""
- default_docs_dir = Path(__file__).resolve().parent / "docs"
- if default_docs_dir.is_dir():
- return [default_docs_dir] + self.docs_dirs
- return self.docs_dirs
-
- def create_batch_script(self, name: str, contents: str, replacements: list[tuple[str, str]] = None):
- """
- Creates a batch script in the WinPython scripts directory.
-
- Args:
- name: The name of the batch script file.
- contents: The contents of the batch script.
- replacements: A list of tuples for text replacements in the content.
- """
- script_dir = self.winpy_dir / "scripts" if self.winpy_dir else None
- if not script_dir:
- print("Warning: WinPython directory not set, cannot create batch script.")
- return
- script_dir.mkdir(parents=True, exist_ok=True)
- final_contents = contents
- if replacements:
- for old_text, new_text in replacements:
- final_contents = final_contents.replace(old_text, new_text)
- script_path = script_dir / name
- with open(script_path, "w") as f:
- f.write(final_contents)
- print(f"Created batch script: {script_path}")
-
- def create_python_launcher_batch(
- self,
- name: str,
- script_name: str,
- working_dir: str = None,
- options: str = None,
- command: str = None,
- ):
- """
- Creates a batch file to launch a Python script within the WinPython environment.
-
- Args:
- name: The name of the batch file.
- script_name: The name of the Python script to execute.
- working_dir: Optional working directory for the script.
- options: Optional command-line options for the script.
- command: Optional command to execute python, defaults to python.exe or pythonw.exe
- """
- options_str = f" {options}" if options else ""
- if command is None:
- command = '"%WINPYDIR%\\pythonw.exe"' if script_name.endswith(".pyw") else '"%WINPYDIR%\\python.exe"'
- change_dir_cmd = f"cd /D {working_dir}\n" if working_dir else ""
- script_name_str = f" {script_name}" if script_name else ""
- batch_content = f"""@echo off
-call "%~dp0env_for_icons.bat"
-{change_dir_cmd}{command}{script_name_str}{options_str} %*"""
- self.create_batch_script(name, batch_content)
+ return self.distribution.architecture if self.distribution else 64
def create_installer_7zip(self, installer_type: str = ".exe"):
- """
- Creates a WinPython installer using 7-Zip.
-
- Args:
- installer_type: Type of installer to create (".exe", ".7z", ".zip").
- """
+ """Creates a WinPython installer using 7-Zip: ".exe", ".7z", ".zip")"""
self._print_action(f"Creating WinPython installer ({installer_type})")
- template_name = "installer_7zip.bat"
- output_name = "installer_7zip-tmp.bat" # temp file to avoid overwriting template
if installer_type not in [".exe", ".7z", ".zip"]:
- print(f"Warning: Unsupported installer type '{installer_type}'. Defaulting to .exe")
- installer_type = ".exe"
-
- replacements = [
- ("DISTDIR", str(self.winpy_dir)),
- ("ARCH", str(self.architecture_bits)),
- ("VERSION", f"{self.python_full_version}.{self.build_number}{self.flavor}"),
- (
- "VERSION_INSTALL",
- f'{self.python_full_version.replace(".", "")}{self.build_number}',
- ),
- ("RELEASELEVEL", self.release_level),
- ("INSTALLER_OPTION", installer_type), # Pass installer type as option to bat script
- ]
-
- build_installer_7zip(
- PORTABLE_DIR / template_name,
- PORTABLE_DIR / output_name,
- replacements
- )
- self._print_action_done()
-
+ raise RuntimeError("installer_type {installer_type} is undefined")
+ DISTDIR = self.winpython_directory
+ filename_stem = f"Winpython{self.architecture_bits}-{self.python_full_version}.{self.build_number}{self.flavor}{self.release_level}"
+ fullfilename = DISTDIR.parent / (filename_stem + installer_type)
+ sfx_option = "-sfx7z.sfx" if installer_type == ".exe" else ""
+ zip_option = "-tzip" if installer_type == ".zip" else ""
+ command = f'"{find_7zip_executable()}" {zip_option} -mx5 a "{fullfilename}" "{DISTDIR}" {sfx_option}'
+ print(f'Executing 7-Zip script: "{command}"')
+ try:
+ subprocess.run(command, shell=True, check=True, stderr=sys.stderr, stdout=sys.stderr)
+ except subprocess.CalledProcessError as e:
+ print(f"Error executing 7-Zip script: {e}", file=sys.stderr)
def _print_action(self, text: str):
"""Prints an action message with progress indicator."""
@@ -418,342 +170,177 @@ def _print_action(self, text: str):
else:
print(f"{text}... ", end="", flush=True)
- def _print_action_done(self):
- """Prints "OK" to indicate action completion."""
- if not self.verbose:
- print("OK")
-
def _extract_python_archive(self):
"""Extracts the Python zip archive to create the base Python environment."""
self._print_action("Extracting Python archive")
- utils.extract_archive(
- str(self.python_zip_file),
- targetdir=str(self.winpy_dir), # Extract directly to winpy_dir
- )
- self._print_action_done()
+ utils.extract_archive(self.python_zip_file, self.winpython_directory)
# Relocate to /python subfolder if needed (for newer structure) #2024-12-22 to /python
- python_target_dir = self.winpy_dir / self.python_dir_name
- if self.python_dir_name != self.python_name and not python_target_dir.is_dir():
- os.rename(self.winpy_dir / self.python_name, python_target_dir)
-
- def _copy_tools(self):
- """Copies development tools to the WinPython 't' directory."""
- tools_target_dir = self.winpy_dir / "t"
- self._print_action(f"Copying tools to {tools_target_dir}")
- _copy_items(self.tools_directories, tools_target_dir, self.verbose)
-
- # Special handling for Node.js to move it up one level
- nodejs_current_dir = tools_target_dir / "n"
- nodejs_target_dir = self.winpy_dir / self.NODEJS_PATH_REL
- if nodejs_current_dir != nodejs_target_dir and nodejs_current_dir.is_dir():
+ expected_python_directory = self.winpython_directory / self.python_directory_name
+ if self.python_directory_name != self.python_name and not expected_python_directory.is_dir():
+ os.rename(self.winpython_directory / self.python_name, expected_python_directory)
+
+ def _copy_essential_files(self):
+ """Copies pre-made objects"""
+ self._print_action("Copying default scripts")
+ copy_items([PORTABLE_DIRECTORY / "scripts"], self.winpython_directory / "scripts", self.verbose)
+
+ self._print_action("Copying launchers")
+ copy_items([PORTABLE_DIRECTORY / "launchers_final"], self.winpython_directory, self.verbose)
+
+ docs_target_directory = self.winpython_directory / "notebooks" / "docs"
+ self._print_action(f"Copying documentation to {docs_target_directory}")
+ copy_items(self.documentation_directories, docs_target_directory, self.verbose)
+
+ tools_target_directory = self.winpython_directory / "t"
+ self._print_action(f"Copying tools to {tools_target_directory}")
+ copy_items(self.tools_directories, tools_target_directory, self.verbose)
+
+ if (nodejs_current_directory := tools_target_directory / "n").is_dir():
+ self._print_action(f"Moving tools from {nodejs_current_directory} to {tools_target_directory.parent / NODEJS_RELATIVE_PATH}")
try:
- shutil.move(nodejs_current_dir, nodejs_target_dir)
+ shutil.move(nodejs_current_directory, tools_target_directory.parent / NODEJS_RELATIVE_PATH)
except Exception as e:
print(f"Error moving Node.js directory: {e}")
- self._print_action_done()
-
- def _copy_documentation(self):
- """Copies documentation files to the WinPython 'docs' directory."""
- docs_target_dir = self.winpy_dir / "notebooks" / "docs"
- self._print_action(f"Copying documentation to {docs_target_dir}")
- _copy_items(self.docs_directories, docs_target_dir, self.verbose)
- self._print_action_done()
-
- def _copy_launchers(self):
- """Copies pre-made launchers to the WinPython directory."""
- self._print_action("Creating launchers")
- launchers_source_dir = PORTABLE_DIR / "launchers_final"
- _copy_items([launchers_source_dir], self.winpy_dir, self.verbose)
- self._print_action_done()
-
- def _copy_default_scripts(self):
- """Copies launchers and defeult scripts."""
- self._print_action("copying pre-made scripts")
- origin = PORTABLE_DIR / "scripts"
- destination = self.winpy_dir / "scripts"
- _copy_items([origin], destination, self.verbose)
- self._print_action_done()
-
+
def _create_initial_batch_scripts(self):
"""Creates initial batch scripts, including environment setup."""
self._print_action("Creating initial batch scripts")
-
- path_entries_str = ";".join([rf"%WINPYDIR%\{pth}" for pth in self.pre_path_entries])
- full_path_env_var = f"{path_entries_str};%PATH%;" + ";".join([rf"%WINPYDIR%\{pth}" for pth in self.post_path_entries])
-
- path_entries_ps_str = ";".join([rf"$env:WINPYDIR\\{pth}" for pth in self.pre_path_entries])
- full_path_ps_env_var = f"{path_entries_ps_str};$env:path;" + ";".join([rf"$env:WINPYDIR\\{pth}" for pth in self.post_path_entries])
-
# Replacements for batch scripts (PyPy compatibility)
- exe_name = self.distribution.short_exe if self.distribution else "python.exe" # default to python.exe if distribution is not yet set
- batch_replacements = [
- (r"DIR%\\python.exe", rf"DIR%\\{exe_name}"),
- (r"DIR%\\PYTHON.EXE", rf"DIR%\\{exe_name}"),
- ]
- if self.distribution and (Path(self.distribution.target) / r"lib-python\3\idlelib").is_dir():
- batch_replacements.append((r"\Lib\idlelib", r"\lib-python\3\idlelib"))
-
- destination = self.winpy_dir / "scripts"
- for specials in ('env.bat', 'WinPython_PS_Prompt.ps1'):
- destspe=str(destination / specials)
- print('destspe:', destspe)
- utils.patch_sourcefile(destspe,'{self.python_dir_name}', self.python_dir_name)
- utils.patch_sourcefile(destspe,'{self.winpython_version_name}', self.winpython_version_name)
- utils.patch_sourcefile(destspe,'{full_path_env_var}', full_path_env_var)
- utils.patch_sourcefile(destspe,'{full_path_ps_env_var}', full_path_ps_env_var)
- self._print_action_done()
-
-
- def _create_standard_batch_scripts(self):
- """Creates standard WinPython batch scripts for various actions."""
- self._print_action("Creating standard batch scripts")
-
- exe_name = self.distribution.short_exe if self.distribution else "python.exe"
- batch_replacements = [
- (r"DIR%\\python.exe", rf"DIR%\\{exe_name}"),
- (r"DIR%\\PYTHON.EXE", rf"DIR%\\{exe_name}"),
- ]
- if self.distribution and (Path(self.distribution.target) / r"lib-python\3\idlelib").is_dir():
- batch_replacements.append((r"\Lib\idlelib", r"\lib-python\3\idlelib"))
-
- self._print_action_done()
-
-
- def build(self, remove_existing: bool = True, requirements=None, winpy_dirname: str = None):
- """Make WinPython distribution in target directory from the installers
- located in wheels_dir
-
- remove_existing=True: (default) install all from scratch
- remove_existing=False: for complementary purposes (create installers)
- requirements=file(s) of requirements (separated by space if several)"""
- python_zip_filename = self.python_zip_file.name
- print(f"Building WinPython with Python archive: {python_zip_filename}")
-
+ executable_name = self.distribution.short_exe if self.distribution else "python.exe" # default to python.exe if distribution is not yet set
+ init_variables = [('WINPYthon_exe', executable_name), ('WINPYthon_subdirectory_name', self.python_directory_name), ('WINPYVER', self.winpython_version_name)]
+ with open(self.winpython_directory / "scripts" / "env.ini", "w") as f:
+ f.writelines([f'{a}={b}\n' for a, b in init_variables])
+
+ def build(self, rebuild: bool = True, requirements_files_list=None, winpy_dirname: str = None):
+ """Make or finalise WinPython distribution in the target directory"""
+ print(f"Building WinPython with Python archive: {self.python_zip_file.name}")
if winpy_dirname is None:
- raise RuntimeError("WinPython base directory to create is undefined")
- else:
- self.winpy_dir = self.target_dir / winpy_dirname # Create/re-create the WinPython base directory
- self._print_action(f"Creating WinPython {self.winpy_dir} base directory")
- if self.winpy_dir.is_dir() and remove_existing:
- try:
- shutil.rmtree(self.winpy_dir, onexc=utils.onerror)
- except TypeError: # before 3.12
- shutil.rmtree(self.winpy_dir, onerror=utils.onerror)
- os.makedirs(self.winpy_dir, exist_ok=True)
- if remove_existing:
+ raise RuntimeError("WinPython base directory to create is undefined")
+ self.winpython_directory = self.target_directory / winpy_dirname
+
+ if rebuild:
+ self._print_action(f"Creating WinPython {self.winpython_directory} base directory")
+ if self.winpython_directory.is_dir():
+ try:
+ shutil.rmtree(self.winpython_directory, onexc=utils.onerror)
+ except TypeError: # before 3.12
+ shutil.rmtree(self.winpython_directory, onerror=utils.onerror)
+ os.makedirs(self.winpython_directory, exist_ok=True)
# preventive re-Creation of settings directory
- # (necessary if user is starting an application with a batch)
- (self.winpy_dir / "settings" / "AppData" / "Roaming").mkdir(parents=True, exist_ok=True) # Ensure settings dir exists
+ (self.winpython_directory / "settings" / "AppData" / "Roaming").mkdir(parents=True, exist_ok=True)
self._extract_python_archive()
- self._print_action_done()
- self.distribution = wppm.Distribution(
- self.python_executable_dir,
- verbose=self.verbose,
- indent=True,
- )
+ self.distribution = wppm.Distribution(self.python_executable_directory, verbose=self.verbose)
- if remove_existing:
- self._copy_default_scripts()
+ if rebuild:
+ self._copy_essential_files()
self._create_initial_batch_scripts()
- self._create_standard_batch_scripts()
- self._copy_launchers()
-
- utils.python_execmodule("ensurepip", self.distribution.target) # Ensure pip is installed for PyPy
+ utils.python_execmodule("ensurepip", self.distribution.target)
self.distribution.patch_standard_packages("pip")
- # Upgrade essential packages
essential_packages = ["pip", "setuptools", "wheel", "winpython"]
for package_name in essential_packages:
actions = ["install", "--upgrade", "--pre", package_name] + self.install_options
- print(f"Piping: {' '.join(actions)}")
self._print_action(f"Piping: {' '.join(actions)}")
self.distribution.do_pip_action(actions)
self.distribution.patch_standard_packages(package_name)
- self._copy_tools()
- self._copy_documentation()
-
- if requirements:
- if not list(requirements) == requirements:
- requirements = requirements.split()
- for req in requirements:
- actions = ["install", "-r", req]
- if self.install_options is not None:
- actions += self.install_options
- print(f"piping {' '.join(actions)}")
- self._print_action(f"piping {' '.join(actions)}")
- self.distribution.do_pip_action(actions)
-
+ if requirements_files_list:
+ for req in requirements_files_list:
+ actions = ["install", "-r", req] + (self.install_options or [])
+ self._print_action(f"Piping: {' '.join(actions)}")
+ self.distribution.do_pip_action(actions)
self.distribution.patch_standard_packages()
- self._print_action("Cleaning up distribution")
- self.distribution.clean_up()
- self._print_action_done()
- # Writing package index
self._print_action("Writing package index")
- # winpyver2 = the version without build part but with self.distribution.architecture
self.winpyver2 = f"{self.python_full_version}.{self.build_number}"
- fname = str(
- self.winpy_dir.parent
- / (
- f"WinPython{self.flavor}-"
- + f"{self.distribution.architecture}bit-"
- + f"{self.winpyver2}.md"
- )
- )
- open(fname, "w", encoding='utf-8').write(self.package_index_markdown)
- # Copy to winpython/changelogs
-
- shutil.copyfile(
- fname,
- str(Path(CHANGELOGS_DIR) / Path(fname).name),
- )
- self._print_action_done()
-
- # Writing changelog
- self._print_action("Writing changelog")
- diff.write_changelog(
- self.winpyver2,
- basedir=self.base_dir,
- flavor=self.flavor,
- release_level=self.release_level,
- architecture=self.distribution.architecture,
- )
- self._print_action_done()
+ output_markdown_filename = str(self.winpython_directory.parent / f"WinPython{self.flavor}-{self.distribution.architecture}bit-{self.winpyver2}.md")
+ with open(output_markdown_filename, "w", encoding='utf-8') as f:
+ f.write(self.package_index_markdown)
+ self._print_action("Writing changelog")
+ shutil.copyfile(output_markdown_filename, str(Path(CHANGELOGS_DIRECTORY) / Path(output_markdown_filename).name))
+ diff.write_changelog(self.winpyver2, None, self.base_directory, self.flavor, self.distribution.architecture)
-def rebuild_winpython_package(source_dir: Path, target_dir: Path, architecture: int = 64, verbose: bool = False):
+def rebuild_winpython_package(source_directory: Path, target_directory: Path, architecture: int = 64, verbose: bool = False):
"""Rebuilds the winpython package from source using flit."""
- for filename in os.listdir(target_dir):
- if filename.startswith("winpython-") and filename.endswith((".exe", ".whl", ".gz")):
- os.remove(Path(target_dir) / filename)
-
- utils.buildflit_wininst(
- str(source_dir),
- copy_to=str(target_dir),
- verbose=verbose,
- )
-
-
-def _parse_list_argument(arg_value: str | list[str]) -> list[str]:
- """Parses a string or list argument into a list of strings."""
- if arg_value is None:
- return []
- if isinstance(arg_value, str):
- return arg_value.split()
- return list(arg_value) # Ensure it's a list if already a list-like object
-
-
-def make_all(
- build_number: int,
- release_level: str,
- pyver: str,
- architecture: int,
- basedir: Path,
- verbose: bool = False,
- remove_existing: bool = True,
- create_installer: str = "True",
- install_options=["--no-index"],
- flavor: str = "",
- requirements: str | list[Path] = None,
- find_links: str | list[Path] = None,
- source_dirs: Path = None,
- toolsdirs: str | list[Path] = None,
- docsdirs: str | list[Path] = None,
- python_target_release: str = None, # e.g. "37101" for 3.7.10
+ for file in target_directory.glob("winpython-*"):
+ if file.suffix in (".exe", ".whl", ".gz"):
+ file.unlink()
+ utils.buildflit_wininst(source_directory, copy_to=target_directory, verbose=verbose)
+
+def make_all(build_number: int, release_level: str, pyver: str, architecture: int, basedir: Path,
+ verbose: bool = False, rebuild: bool = True, create_installer: str = "True", install_options=["--no-index"],
+ flavor: str = "", requirements: str | list[Path] = None, find_links: str | list[Path] = None,
+ source_dirs: Path = None, toolsdirs: str | list[Path] = None, docsdirs: str | list[Path] = None,
+ python_target_release: str = None, # e.g. "37101" for 3.7.10
):
- """Make WinPython distribution, for a given base directory and
- architecture:
- `build_number`: build number [int]
- `release_level`: release level (e.g. 'beta1', '') [str]
- `pyver`: python version ('3.4' or 3.5')
- `architecture`: [int] (32 or 64)
- `basedir`: where will be created tmp_wheel and Winpython build
- r'D:\Winpython\basedir34'.
- `requirements`: the package list for pip r'D:\requirements.txt',
- `install_options`: pip options r'--no-index --pre --trusted-host=None',
- `find_links`: package directories r'D:\Winpython\packages.srcreq',
- `source_dirs`: the python.zip + rebuilt winpython wheel package directory,
- `toolsdirs`: r'D:\WinPython\basedir34\t.Slim',
- `docsdirs`: r'D:\WinPython\basedir34\docs.Slim'"""
-
+ """
+ Make a WinPython distribution for a given set of parameters:
+ Args:
+ build_number: build number [int]
+ release_level: release level (e.g. 'beta1', '') [str]
+ pyver: python version ('3.4' or 3.5')
+ architecture: [int] (32 or 64)
+ basedir: where to create the build (r'D:\Winpython\basedir34')
+ verbose: Enable verbose output (bool).
+ rebuild: Whether to rebuild the distribution (bool).
+ create_installer: Type of installer to create (str).
+ install_options: pip options (r'--no-index --pre --trusted-host=None')
+ flavor: WinPython flavor (str).
+ requirements: package lists for pip (r'D:\requirements.txt')
+ find_links: package directories (r'D:\Winpython\packages.srcreq')
+ source_dirs: the python.zip + rebuilt winpython wheel package directory
+ toolsdirs: Directory with development tools r'D:\WinPython\basedir34\t.Slim'
+ docsdirs: Directory with documentation r'D:\WinPython\basedir34\docs.Slim'
+ python_target_release: Target Python release (str).
+ """
assert basedir is not None, "The *basedir* directory must be specified"
assert architecture in (32, 64)
- utils.print_box(
- f"Making WinPython {architecture}bits"
- + f" at {Path(basedir) / ('bu' + flavor)}"
- )
-
- # Create Build director, where Winpython will be constructed
- builddir = str(Path(basedir) / ("bu" + flavor))
- os.makedirs(Path(builddir), exist_ok=True)
- # use source_dirs as the directory to re-build Winpython wheel
- wheels_dir = source_dirs
-
- # Rebuild WinPython package
- winpython_source_dir = Path(__file__).resolve().parent
- rebuild_winpython_package(
- source_dir=winpython_source_dir,
- target_dir=wheels_dir,
- architecture=architecture,
- verbose=verbose,
- )
- # Parse list arguments
- tools_dirs_list = _parse_list_argument(toolsdirs)
- docs_dirs_list = _parse_list_argument(docsdirs)
- install_options_list = _parse_list_argument(install_options)
- find_links_dirs_list = _parse_list_argument(find_links)
- requirements_files_list = [Path(f) for f in _parse_list_argument(requirements) if f] # ensure Path objects
+ tools_dirs_list = parse_list_argument(toolsdirs, ",")
+ docs_dirs_list = parse_list_argument(docsdirs, ",")
+ install_options_list = parse_list_argument(install_options, " ")
+ find_links_dirs_list = parse_list_argument(find_links, ",")
+ requirements_files_list = [Path(f) for f in parse_list_argument(requirements, ",") if f]
+ find_links_options = [f"--find-links={link}" for link in find_links_dirs_list + [source_dirs]]
+ build_directory = Path(basedir) / ("bu" + flavor)
+
+ if rebuild:
+ utils.print_box(f"Making WinPython {architecture}bits at {Path(basedir) / ('bu' + flavor)}")
+ os.makedirs(build_directory, exist_ok=True)
+ # use source_dirs as the directory to re-build Winpython wheel
+ winpython_source_dir = Path(__file__).resolve().parent
+ rebuild_winpython_package(winpython_source_dir, Path(source_dirs), architecture, verbose)
- find_links_options = [f"--find-links={link}" for link in find_links_dirs_list + [wheels_dir]]
builder = WinPythonDistributionBuilder(
- build_number,
- release_level,
- builddir,
- wheels_dir=wheels_dir,
- tools_dirs=[Path(d) for d in tools_dirs_list],
- docs_dirs=[Path(d) for d in docs_dirs_list],
- verbose=verbose,
- base_dir=basedir,
+ build_number, release_level, build_directory, wheels_directory=source_dirs,
+ tools_directories=[Path(d) for d in tools_dirs_list],
+ documentation_directories=[Path(d) for d in docs_dirs_list],
+ verbose=verbose, base_directory=basedir,
install_options=install_options_list + find_links_options,
- flavor=flavor,
+ flavor=flavor
)
- # define a pre-defined winpydir, instead of having to guess
-
- # extract the python subversion to get WPy64-3671b1
- my_x = "".join(builder.python_name.replace(".amd64", "").split(".")[-2:-1])
- while not my_x.isdigit() and len(my_x) > 0:
- my_x = my_x[:-1]
+ # define the directory where to create the distro
+ python_minor_version_str = "".join(builder.python_name.replace(".amd64", "").split(".")[-2:-1])
+ while not python_minor_version_str.isdigit() and len(python_minor_version_str) > 0:
+ python_minor_version_str = python_minor_version_str[:-1]
# simplify for PyPy
- if not python_target_release == None:
- winpy_dirname = f"WPy{architecture}-{python_target_release}{build_number}{release_level}"
+ if python_target_release is not None:
+ winpython_dirname = f"WPy{architecture}-{python_target_release}{build_number}{release_level}"
else:
- winpy_dirname = f"WPy{architecture}-{pyver.replace('.', '')}{my_x}{build_number}{release_level}"
-
- builder.build(
- remove_existing=remove_existing,
- requirements=requirements_files_list,
- winpy_dirname=winpy_dirname,
- )
- if str(create_installer).lower() != "false":
- if ".zip" in str(create_installer).lower():
- builder.create_installer_7zip(".zip")
- if ".7z" in str(create_installer).lower():
- builder.create_installer_7zip(".7z")
- if "7zip" in str(create_installer).lower():
- builder.create_installer_7zip(".exe")
+ winpython_dirname = f"WPy{architecture}-{pyver.replace('.', '')}{python_minor_version_str}{build_number}{release_level}"
- return builder
+ builder.build(rebuild=rebuild, requirements_files_list=requirements_files_list, winpy_dirname=winpython_dirname)
+ for installer_type in [".zip", ".7z", ".exe"]:
+ if installer_type in create_installer.lower().replace("7zip",".exe"):
+ builder.create_installer_7zip(installer_type)
if __name__ == "__main__":
- # DO create only one version at a time
- # You may have to manually delete previous build\winpython-.. directory
-
+ # DO create only one Winpython distribution at a time
make_all(
- 1,
+ build_number=1,
release_level="build3",
pyver="3.4",
basedir=r"D:\Winpython\basedir34",
@@ -766,4 +353,4 @@ def make_all(
source_dirs=r"D:\WinPython\basedir34\packages.win-amd64",
toolsdirs=r"D:\WinPython\basedir34\t.Slim",
docsdirs=r"D:\WinPython\basedir34\docs.Slim",
- )
\ No newline at end of file
+ )
diff --git a/portable/build_my_launchers.bat b/portable/build_my_launchers.bat
index 04c4671e..579a96e6 100644
--- a/portable/build_my_launchers.bat
+++ b/portable/build_my_launchers.bat
@@ -7,21 +7,26 @@ set VCVARS_PATH="C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\
rem pick the right ones and rename them in launchers_final
set do_launcher=%~dp0launchers_src\build_one_launcher.bat
+set do_launcher_original=%~dp0launchers_src_original\build_one_launcher.bat
::WINDOWS launchers
-call %do_launcher% "powershell.ico" "cmd_ps.bat" "WinPython Powershell Prompt" WINDOWS
+rem call %do_launcher% "python.ico" "winidle.bat" "IDLE (Python GUI)" WINDOWS proposed
+echo displace this pause if you want to re-build more
pause
-call %do_launcher% "python.ico" "winidle.bat" "IDLE (Python GUI)" WINDOWS
-call %do_launcher% "spyder.ico" "winspyder.bat" "Spyder" WINDOWS
-call %do_launcher% "spyder_reset.ico" "spyder_reset.bat" "Spyder reset" WINDOWS
-call %do_launcher% "code.ico" "winvscode.bat" "VS Code" WINDOWS
+rem exit
+
+
+call %do_launcher% "powershell.ico" "cmd_ps.bat" "WinPython Powershell Prompt" WINDOWS proposed
+call %do_launcher% "spyder.ico" "winspyder.bat" "Spyder" WINDOWS proposed
+call %do_launcher% "spyder_reset.ico" "spyder_reset.bat" "Spyder reset" WINDOWS proposed
+call %do_launcher% "code.ico" "winvscode.bat" "VS Code" WINDOWS proposed
:: CONSOLE launchers
-call %do_launcher% "cmd.ico" "cmd.bat" "WinPython Command Prompt" CONSOLE
-call %do_launcher% "python.ico" "winpython.bat" "WinPython Interpreter" CONSOLE
-call %do_launcher% "jupyter.ico" "winipython_notebook.bat" "Jupyter Notebook" CONSOLE
-call %do_launcher% "jupyter.ico" "winjupyter_lab.bat" "Jupyter Lab" CONSOLE
-call %do_launcher% "winpython.ico" "wpcp.bat" "WinPython Control Panel" CONSOLE
+call %do_launcher% "cmd.ico" "cmd.bat" "WinPython Command Prompt" CONSOLE proposed
+call %do_launcher% "python.ico" "winpython.bat" "WinPython Interpreter" CONSOLE proposed
+call %do_launcher% "jupyter.ico" "winipython_notebook.bat" "Jupyter Notebook" CONSOLE proposed
+call %do_launcher% "jupyter.ico" "winjupyter_lab.bat" "Jupyter Lab" CONSOLE proposed
+call %do_launcher% "winpython.ico" "wpcp.bat" "WinPython Control Panel" CONSOLE proposed
pause
diff --git a/portable/installer_7zip.bat b/portable/installer_7zip.bat
deleted file mode 100644
index 0b308f2a..00000000
--- a/portable/installer_7zip.bat
+++ /dev/null
@@ -1,78 +0,0 @@
-rem Copyright @ 2018 WinPython team
-rem Licensed under the terms of the MIT License
-rem (see winpython/__init__.py for details)
-
-rem This is to do a 7-zip installer
-
-rem ================================================================
-rem These lines are automatically replaced when creating installer:
-rem (see winpython/make.py)
-set SEVENZIP_EXE=C:\Program Files (x86)\7-Zip\7z
-set DISTDIR=C:\WinP\bd36\buPs2\winp64-3.6.x.0
-set ARCH=64
-set VERSION=3.6.7.0Ps2
-
-rem 2018-04-04 need to minimize path length of installation further: remove flavor in install path
-
-set VERSION_INSTALL=3670
-
-set RELEASELEVEL=beta3
-set PORTABLE_DIR=C:\WinPython-64bit-3.4.3.7Qt5\winpython_github20181029\portable
-set INSTALLER_OPTION=.exe
-
-rem ================================================================
-rem these lines are static definitions
-set ID=Winpython
-set ID_INSTALL=WPy
-set FILE_DESCRIPTION=%ID% Installer
-set COMPANY=%ID%
-set BRANDING=%ID%, the portable Python Distribution for Scientists
-set COPYRIGHT=Copyright © 2018+ WinPython Team
-set MyAppPublisher=WinPython team
-set MyAppURL=https://winpython.github.io/
-
-rem OutputBaseFilename "{#ID}{#ARCH}-{#VERSION}{#RELEASELEVEL}"
-
-rem ================================================================ [Setup]
-rem OutFile "${DISTDIR}\..\${ID}${ARCH}-${VERSION}${RELEASELEVEL}.exe"
-set MyBinaryOutputDir=%DISTDIR%\..
-set OutputBaseFilename=%ID%%ARCH%-%VERSION%%RELEASELEVEL%
-
-rem 7-zip uncompress the directory compressed %DISTDIR% (no option to change it in gui)
-
-rem ================================================================
-rem 2019-03-16 copy license at source (due to 7zip limitations)
-
-REM 2025-01-05 done in make.py
-rem copy/Y %PORTABLE_DIR%\license.txt %DISTDIR%\license.txt
-
-rem ================================================================
-
-echo %time%
-
-rem compression + include auto_extract in GUI mode
-if "%INSTALLER_OPTION%"==".exe" (
- "%SEVENZIP_EXE%" -mx5 a "%MyBinaryOutputDir%\%OutputBaseFilename%.exe" %DISTDIR% -sfx7z.sfx
- echo autoextract using command line options
- echo "%MyBinaryOutputDir%\%OutputBaseFilename%.exe" -y -o%MyBinaryOutputDir%\zz > NUL
- )
-
-if "%INSTALLER_OPTION%"==".7z" (
- "%SEVENZIP_EXE%" -mx5 a "%MyBinaryOutputDir%\%OutputBaseFilename%.7z" %DISTDIR%
- echo no autoextract
- )
-if "%INSTALLER_OPTION%"==".zip" (
- "%SEVENZIP_EXE%" -tzip -mx5 a "%MyBinaryOutputDir%\%OutputBaseFilename%.zip" %DISTDIR%
- echo no autoextract
- )
-
-
-
-rem -mx1 = speed fastest
-rem -mx3 = speed fast
-rem -mx5 = speed normal
-rem -mx7 = compress maximum
-rem -mx9 = compress ultra
-
-rem -t7z = [by default] 7 zip compression , the only choice with auto-extract
-rem -tzip = Zip compatible compression.
diff --git a/portable/launchers_final/Jupyter Lab.exe b/portable/launchers_final/Jupyter Lab.exe
index 98bb8bde..93003fcb 100644
Binary files a/portable/launchers_final/Jupyter Lab.exe and b/portable/launchers_final/Jupyter Lab.exe differ
diff --git a/portable/launchers_final/Jupyter Notebook.exe b/portable/launchers_final/Jupyter Notebook.exe
index 5d59af59..28fdbea3 100644
Binary files a/portable/launchers_final/Jupyter Notebook.exe and b/portable/launchers_final/Jupyter Notebook.exe differ
diff --git a/portable/launchers_final_original/IDLE (Python GUI).exe b/portable/launchers_final_original/IDLE (Python GUI).exe
new file mode 100644
index 00000000..c1e0c9dc
Binary files /dev/null and b/portable/launchers_final_original/IDLE (Python GUI).exe differ
diff --git a/portable/launchers_final_original/Jupyter Lab.exe b/portable/launchers_final_original/Jupyter Lab.exe
new file mode 100644
index 00000000..98bb8bde
Binary files /dev/null and b/portable/launchers_final_original/Jupyter Lab.exe differ
diff --git a/portable/launchers_final_original/Jupyter Notebook.exe b/portable/launchers_final_original/Jupyter Notebook.exe
new file mode 100644
index 00000000..5d59af59
Binary files /dev/null and b/portable/launchers_final_original/Jupyter Notebook.exe differ
diff --git a/portable/launchers_final_original/Spyder reset.exe b/portable/launchers_final_original/Spyder reset.exe
new file mode 100644
index 00000000..21250f98
Binary files /dev/null and b/portable/launchers_final_original/Spyder reset.exe differ
diff --git a/portable/launchers_final_original/Spyder.exe b/portable/launchers_final_original/Spyder.exe
new file mode 100644
index 00000000..43874aa7
Binary files /dev/null and b/portable/launchers_final_original/Spyder.exe differ
diff --git a/portable/launchers_final_original/VS Code.exe b/portable/launchers_final_original/VS Code.exe
new file mode 100644
index 00000000..225decb4
Binary files /dev/null and b/portable/launchers_final_original/VS Code.exe differ
diff --git a/portable/launchers_final_original/WinPython Command Prompt.exe b/portable/launchers_final_original/WinPython Command Prompt.exe
new file mode 100644
index 00000000..e4e824c5
Binary files /dev/null and b/portable/launchers_final_original/WinPython Command Prompt.exe differ
diff --git a/portable/launchers_final_original/WinPython Control Panel.exe b/portable/launchers_final_original/WinPython Control Panel.exe
new file mode 100644
index 00000000..5795bf9c
Binary files /dev/null and b/portable/launchers_final_original/WinPython Control Panel.exe differ
diff --git a/portable/launchers_final_original/WinPython Interpreter.exe b/portable/launchers_final_original/WinPython Interpreter.exe
new file mode 100644
index 00000000..dfa43135
Binary files /dev/null and b/portable/launchers_final_original/WinPython Interpreter.exe differ
diff --git a/portable/launchers_final_original/WinPython Powershell Prompt.exe b/portable/launchers_final_original/WinPython Powershell Prompt.exe
new file mode 100644
index 00000000..d4512f32
Binary files /dev/null and b/portable/launchers_final_original/WinPython Powershell Prompt.exe differ
diff --git a/portable/launchers_final_proposed/IDLE (Python GUI).exe b/portable/launchers_final_proposed/IDLE (Python GUI).exe
new file mode 100644
index 00000000..01eed2ce
Binary files /dev/null and b/portable/launchers_final_proposed/IDLE (Python GUI).exe differ
diff --git a/portable/launchers_final_proposed/Jupyter Lab.exe b/portable/launchers_final_proposed/Jupyter Lab.exe
new file mode 100644
index 00000000..93003fcb
Binary files /dev/null and b/portable/launchers_final_proposed/Jupyter Lab.exe differ
diff --git a/portable/launchers_final_proposed/Jupyter Notebook.exe b/portable/launchers_final_proposed/Jupyter Notebook.exe
new file mode 100644
index 00000000..28fdbea3
Binary files /dev/null and b/portable/launchers_final_proposed/Jupyter Notebook.exe differ
diff --git a/portable/launchers_final_proposed/Spyder reset.exe b/portable/launchers_final_proposed/Spyder reset.exe
new file mode 100644
index 00000000..0baf4ab3
Binary files /dev/null and b/portable/launchers_final_proposed/Spyder reset.exe differ
diff --git a/portable/launchers_final_proposed/Spyder.exe b/portable/launchers_final_proposed/Spyder.exe
new file mode 100644
index 00000000..f3729201
Binary files /dev/null and b/portable/launchers_final_proposed/Spyder.exe differ
diff --git a/portable/launchers_final_proposed/VS Code.exe b/portable/launchers_final_proposed/VS Code.exe
new file mode 100644
index 00000000..731f5f52
Binary files /dev/null and b/portable/launchers_final_proposed/VS Code.exe differ
diff --git a/portable/launchers_final_proposed/WinPython Command Prompt.exe b/portable/launchers_final_proposed/WinPython Command Prompt.exe
new file mode 100644
index 00000000..6f1b6bd6
Binary files /dev/null and b/portable/launchers_final_proposed/WinPython Command Prompt.exe differ
diff --git a/portable/launchers_final_proposed/WinPython Control Panel.exe b/portable/launchers_final_proposed/WinPython Control Panel.exe
new file mode 100644
index 00000000..664ff576
Binary files /dev/null and b/portable/launchers_final_proposed/WinPython Control Panel.exe differ
diff --git a/portable/launchers_final_proposed/WinPython Interpreter.exe b/portable/launchers_final_proposed/WinPython Interpreter.exe
new file mode 100644
index 00000000..e0cdbe77
Binary files /dev/null and b/portable/launchers_final_proposed/WinPython Interpreter.exe differ
diff --git a/portable/launchers_final_proposed/WinPython Powershell Prompt.exe b/portable/launchers_final_proposed/WinPython Powershell Prompt.exe
new file mode 100644
index 00000000..c6bda2eb
Binary files /dev/null and b/portable/launchers_final_proposed/WinPython Powershell Prompt.exe differ
diff --git a/portable/launchers_final_proposed/license.txt b/portable/launchers_final_proposed/license.txt
new file mode 100644
index 00000000..da461f3b
--- /dev/null
+++ b/portable/launchers_final_proposed/license.txt
@@ -0,0 +1,37 @@
+Note
+----
+
+WinPython components are distributed as they were received from
+their copyright holder, under their own copyright and/or license,
+and without any linking with each other.
+
+WinPython software collection (i.e. the collection of software,
+libraries and documents) is licensed under the terms of the
+following license agreement.
+
+
+WinPython License Agreement (MIT License)
+-----------------------------------------
+
+Copyright (c) 2012 Pierre Raybaut, 2016+ WinPython team
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/portable/launchers_src/launcher_template_CONSOLE.cpp b/portable/launchers_src/launcher_template_CONSOLE.cpp
index 93614b9e..1008d7f0 100644
--- a/portable/launchers_src/launcher_template_CONSOLE.cpp
+++ b/portable/launchers_src/launcher_template_CONSOLE.cpp
@@ -19,6 +19,26 @@ int main() {
std::wstring exeDir = exePath;
exeDir = exeDir.substr(0, exeDir.find_last_of(L"\\/"));
+ // Get command line string and extract arguments
+ LPWSTR commandLine = GetCommandLineW();
+ std::wstring args;
+ // If executable path is double quoted, skip the entire quoted section
+ if (commandLine[0] == L'"') {
+ LPWSTR closingQuote = wcschr(commandLine + 1, L'"');
+ if (closingQuote) {
+ args = closingQuote + 1;
+ }
+ // For non-quoted path, skip to character after first space if it exists
+ } else {
+ LPWSTR spacePos = wcschr(commandLine, L' ');
+ if (spacePos) {
+ args = spacePos + 1;
+ }
+ }
+ // Strip leading whitespace
+ size_t args_start = args.find_first_not_of(L' ');
+ args = (args_start != std::wstring::npos) ? args.substr(args_start) : L"";
+
// Define the path to the "scripts" directory
std::wstring scriptsDir = exeDir + L"\\scripts";
@@ -37,8 +57,13 @@ int main() {
return 1;
}
- // Define the command to run
- std::wstring target = L"cmd.exe /c \"" LAUNCH_TARGET L"\"";
+ // Define the command to run and append arguments if present
+ std::wstring target;
+ if (!args.empty()) {
+ target = L"cmd.exe /c \"\"" LAUNCH_TARGET L"\" " + args + L"\"";
+ } else {
+ target = L"cmd.exe /c \"" LAUNCH_TARGET L"\"";
+ }
// Configure the process startup info
STARTUPINFO si = { sizeof(si) };
diff --git a/portable/launchers_src/launcher_template_WINDOWS.cpp b/portable/launchers_src/launcher_template_WINDOWS.cpp
index 9c878be6..6b10a89a 100644
--- a/portable/launchers_src/launcher_template_WINDOWS.cpp
+++ b/portable/launchers_src/launcher_template_WINDOWS.cpp
@@ -19,6 +19,26 @@ int WINAPI WinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPSTR /
std::wstring exeDir = exePath;
exeDir = exeDir.substr(0, exeDir.find_last_of(L"\\/"));
+ // Get command line string and extract arguments
+ LPWSTR commandLine = GetCommandLineW();
+ std::wstring args;
+ // If executable path is double quoted, skip the entire quoted section
+ if (commandLine[0] == L'"') {
+ LPWSTR closingQuote = wcschr(commandLine + 1, L'"');
+ if (closingQuote) {
+ args = closingQuote + 1;
+ }
+ // For non-quoted path, skip to character after first space if it exists
+ } else {
+ LPWSTR spacePos = wcschr(commandLine, L' ');
+ if (spacePos) {
+ args = spacePos + 1;
+ }
+ }
+ // Strip leading whitespace
+ size_t args_start = args.find_first_not_of(L" ");
+ args = (args_start != std::wstring::npos) ? args.substr(args_start) : L"";
+
// Define the path to the "scripts" directory
std::wstring scriptsDir = exeDir + L"\\scripts";
@@ -37,8 +57,13 @@ int WINAPI WinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPSTR /
return 1;
}
- // Define the command to run
- std::wstring target = L"cmd.exe /c \"" LAUNCH_TARGET L"\"";
+ // Define the command to run and append arguments if present
+ std::wstring target;
+ if (!args.empty()) {
+ target = L"cmd.exe /c \"\"" LAUNCH_TARGET L"\" " + args + L"\"";
+ } else {
+ target = L"cmd.exe /c \"" LAUNCH_TARGET L"\"";
+ }
// Configure the process startup info
STARTUPINFO si = { sizeof(si) };
diff --git a/portable/launchers_src_original/LICENSE b/portable/launchers_src_original/LICENSE
new file mode 100644
index 00000000..b3fb976c
--- /dev/null
+++ b/portable/launchers_src_original/LICENSE
@@ -0,0 +1,63 @@
+DataLab-WinPython license terms
+===============================
+
+DataLab-WinPython is a Python distribution for Windows:
+- Based on WinPython, a portable distribution of Python for Windows (see section I. below).
+- Including DataLab, an open-source platform for signal and image processing (see section II. below).
+
+I. - WinPython License Agreement (MIT License)
+----------------------------------------------
+
+Copyright (c) 2012 Pierre Raybaut, 2016+ WinPython team
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+II. - DataLab License Agreement (BSD 3-Clause License)
+------------------------------------------------------
+
+Copyright (c) 2023, DataLab Platform Developers.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/portable/launchers_src_original/build_one_launcher.bat b/portable/launchers_src_original/build_one_launcher.bat
new file mode 100644
index 00000000..233f68de
--- /dev/null
+++ b/portable/launchers_src_original/build_one_launcher.bat
@@ -0,0 +1,107 @@
+@echo on
+set icon_name=%1
+set LAUNCH_TARGET=%2
+set launcher_name=%3
+set subsystem=%4
+
+set icon_name=%icon_name:"=%
+set LAUNCH_TARGET=%LAUNCH_TARGET:"=%
+set launcher_name=%launcher_name:"=%
+set subsystem=%subsystem:"=%
+
+set ROOT_PATH=%~dp0..\
+set SCRIPT_PATH=%~dp0
+set TEMPO_PATH=%ROOT_PATH%launchers_temp
+set OUTPUT_DIR=%ROOT_PATH%launchers_final
+
+set "ICON_FILE=%ROOT_PATH%icons\%icon_name%"
+set LAUNCHER_EXE=%OUTPUT_DIR%\%launcher_name%.exe
+
+
+:: Paths to template WINDOWS or CONSOLE
+set SOURCE_FILE=%SCRIPT_PATH%launcher_template_%subsystem%.cpp
+echo SOURCE_FILE=%SOURCE_FILE%
+
+set "RESOURCE_FILE=%TEMPO_PATH%\%icon_name%.rc"
+set "RESOURCE_OBJ=%TEMPO_PATH%\%icon_name%.res"
+
+
+:: create pDirectory if needed
+if not exist "%OUTPUT_DIR%" mkdir "%OUTPUT_DIR%"
+if not exist "%TEMPO_PATH%" mkdir "%TEMPO_PATH%"
+
+cd/d %TEMPO_PATH%
+
+:: Check if MSVC environment is already initialized
+if not defined VSINSTALLDIR (
+ echo Initializing MSVC environment...
+ call %VCVARS_PATH%
+ if errorlevel 1 (
+ echo [ERROR] Failed to initialize MSVC environment.
+ exit /b 1
+ )
+)
+
+@echo on
+
+:: Walk through .bat files in the current directory
+ echo Processing %icon_name%..
+ :: Stonebig: Remove previous .exe file
+ echo launcher_exe_action del /q "%LAUNCHER_EXE%"
+ if exist "%LAUNCHER_EXE%" (
+ move "%LAUNCHER_EXE%" "%LAUNCHER_EXE%.old.exe"
+ del /q "%LAUNCHER_EXE%.old.exe"
+ )
+ :: Stonebig: Remove intermediate .res and.rc file
+ if exist "%RESOURCE_OBJ%" (
+ move "%RESOURCE_OBJ%" "%RESOURCE_OBJ%.old.exe"
+ del /q "%RESOURCE_OBJ%.old.exe"
+ )
+ if exist "%RESOURCE_FILE%" (
+ move "%RESOURCE_FILE%" "%RESOURCE_FILE%.old.exe"
+ del /q "%RESOURCE_FILE%.old.exe"
+ )
+ :: Remove intermediate .obj file
+ del /q "launcher_template_%subsystem%.obj"
+
+ :: Check if the icon exists
+ if exist "%ICON_FILE%" (
+ echo Icon found: "%ICON_FILE%"
+ ) else (
+ echo No icon found for "%ICON_FILE%" stoping
+ pause
+ exit
+ )
+
+
+ :: Create resource file
+ echo Creating resource file...
+ > "%RESOURCE_FILE%" echo IDI_ICON1 ICON "%ICON_FILE%"
+ :: Compile resource
+ echo Compiling resource...
+ rc /fo "%RESOURCE_OBJ%" "%RESOURCE_FILE%"
+
+ :: Compile the launcher executable
+ echo Compiling launcher executable...
+ cl /EHsc /O2 /DUNICODE /W4 "%SOURCE_FILE%" "%RESOURCE_OBJ%" ^
+ /Fe"%LAUNCHER_EXE%%" ^
+ /DLAUNCH_TARGET=\"%LAUNCH_TARGET%\" ^
+ User32.lib ^
+ /link /SUBSYSTEM:%subsystem%
+
+
+ if errorlevel 1 (
+ echo [ERROR] Failed to build launcher for %LAUNCH_TARGET%
+ exit /b 1
+ )
+
+ if exist "%LAUNCHER_EXE%" (
+ echo [SUCCESS] Launcher created: "%LAUNCHER_EXE%""
+ ) else (
+ echo [ERROR] Failed to build launcher "%LAUNCHER_EXE%" from "%icon_name%" to call "%LAUNCH_TARGET%"
+ exit /b 1
+ )
+
+echo All launchers processed.
+rem exit /b 0
+
diff --git a/portable/launchers_src_original/launcher_template_CONSOLE.cpp b/portable/launchers_src_original/launcher_template_CONSOLE.cpp
new file mode 100644
index 00000000..93614b9e
--- /dev/null
+++ b/portable/launchers_src_original/launcher_template_CONSOLE.cpp
@@ -0,0 +1,75 @@
+/*
+DataLab-WinPython launcher script
+---------------------------------
+
+Licensed under the terms of the BSD 3-Clause
+(see ./LICENSE for details)
+
+*/
+
+#include
+#include
+
+int main() {
+ // Get the path to the current executable
+ wchar_t exePath[MAX_PATH];
+ GetModuleFileNameW(NULL, exePath, MAX_PATH);
+
+ // Determine the directory of the executable
+ std::wstring exeDir = exePath;
+ exeDir = exeDir.substr(0, exeDir.find_last_of(L"\\/"));
+
+ // Define the path to the "scripts" directory
+ std::wstring scriptsDir = exeDir + L"\\scripts";
+
+ // Check if the "scripts" directory exists
+ DWORD attributes = GetFileAttributesW(scriptsDir.c_str());
+ if (attributes == INVALID_FILE_ATTRIBUTES || !(attributes & FILE_ATTRIBUTE_DIRECTORY)) {
+ MessageBoxW(NULL, L"The 'scripts' directory does not exist. Please ensure it is in the same folder as the launcher.",
+ L"Launcher Error", MB_ICONERROR);
+ return 1;
+ }
+
+ // Set the working directory to the "scripts" folder
+ if (!SetCurrentDirectoryW(scriptsDir.c_str())) {
+ MessageBoxW(NULL, L"Failed to set the working directory to 'scripts'.",
+ L"Launcher Error", MB_ICONERROR);
+ return 1;
+ }
+
+ // Define the command to run
+ std::wstring target = L"cmd.exe /c \"" LAUNCH_TARGET L"\"";
+
+ // Configure the process startup info
+ STARTUPINFO si = { sizeof(si) };
+ si.dwFlags = STARTF_USESHOWWINDOW; // Prevent the window from appearing
+ si.wShowWindow = SW_HIDE; // Hide the command window
+
+ PROCESS_INFORMATION pi = {};
+
+ // Start the process without CREATE_NO_WINDOW flag to show the command window
+ if (!CreateProcessW(
+ NULL, // Application name (NULL because we pass the command in the command line)
+ &target[0], // Command line
+ NULL, // Process security attributes
+ NULL, // Thread security attributes
+ FALSE, // Inherit handles
+ 0, // No special flags
+ NULL, // Environment block (NULL to inherit parent)
+ NULL, // Current directory (NULL to use the parent process's current directory)
+ &si, // Startup info
+ &pi // Process information
+ )) {
+ MessageBoxW(NULL, L"Failed to launch the script.", L"Launcher Error", MB_ICONERROR);
+ return 1;
+ }
+
+ // Wait for the script to finish
+ WaitForSingleObject(pi.hProcess, INFINITE);
+
+ // Cleanup
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+
+ return 0;
+}
diff --git a/portable/launchers_src_original/launcher_template_WINDOWS.cpp b/portable/launchers_src_original/launcher_template_WINDOWS.cpp
new file mode 100644
index 00000000..9c878be6
--- /dev/null
+++ b/portable/launchers_src_original/launcher_template_WINDOWS.cpp
@@ -0,0 +1,75 @@
+/*
+DataLab-WinPython launcher script
+---------------------------------
+
+Licensed under the terms of the BSD 3-Clause
+(see ./LICENSE for details)
+
+*/
+
+#include
+#include
+
+int WINAPI WinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int /*nShowCmd*/) {
+ // Get the path to the current executable
+ wchar_t exePath[MAX_PATH];
+ GetModuleFileNameW(NULL, exePath, MAX_PATH);
+
+ // Determine the directory of the executable
+ std::wstring exeDir = exePath;
+ exeDir = exeDir.substr(0, exeDir.find_last_of(L"\\/"));
+
+ // Define the path to the "scripts" directory
+ std::wstring scriptsDir = exeDir + L"\\scripts";
+
+ // Check if the "scripts" directory exists
+ DWORD attributes = GetFileAttributesW(scriptsDir.c_str());
+ if (attributes == INVALID_FILE_ATTRIBUTES || !(attributes & FILE_ATTRIBUTE_DIRECTORY)) {
+ MessageBoxW(NULL, L"The 'scripts' directory does not exist. Please ensure it is in the same folder as the launcher.",
+ L"Launcher Error", MB_ICONERROR);
+ return 1;
+ }
+
+ // Set the working directory to the "scripts" folder
+ if (!SetCurrentDirectoryW(scriptsDir.c_str())) {
+ MessageBoxW(NULL, L"Failed to set the working directory to 'scripts'.",
+ L"Launcher Error", MB_ICONERROR);
+ return 1;
+ }
+
+ // Define the command to run
+ std::wstring target = L"cmd.exe /c \"" LAUNCH_TARGET L"\"";
+
+ // Configure the process startup info
+ STARTUPINFO si = { sizeof(si) };
+ si.dwFlags = STARTF_USESHOWWINDOW; // Prevent the window from appearing
+ si.wShowWindow = SW_HIDE; // Hide the command window
+
+ PROCESS_INFORMATION pi = {};
+
+ // Start the process with CREATE_NO_WINDOW flag
+ if (!CreateProcessW(
+ NULL, // Application name (NULL because we pass the command in the command line)
+ &target[0], // Command line
+ NULL, // Process security attributes
+ NULL, // Thread security attributes
+ FALSE, // Inherit handles
+ CREATE_NO_WINDOW, // Flags to prevent creating a window
+ NULL, // Environment block (NULL to inherit parent)
+ NULL, // Current directory (NULL to use the parent process's current directory)
+ &si, // Startup info
+ &pi // Process information
+ )) {
+ MessageBoxW(NULL, L"Failed to launch the script.", L"Launcher Error", MB_ICONERROR);
+ return 1;
+ }
+
+ // Wait for the script to finish
+ WaitForSingleObject(pi.hProcess, INFINITE);
+
+ // Cleanup
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+
+ return 0;
+}
diff --git a/portable/scripts/WinPython_PS_Prompt.ps1 b/portable/scripts/WinPython_PS_Prompt.ps1
index c2f148b5..ecd2d6aa 100644
--- a/portable/scripts/WinPython_PS_Prompt.ps1
+++ b/portable/scripts/WinPython_PS_Prompt.ps1
@@ -1,6 +1,18 @@
### WinPython_PS_Prompt.ps1 ###
$0 = $myInvocation.MyCommand.Definition
$dp0 = [System.IO.Path]::GetDirectoryName($0)
+
+# default if env.cfg fails
+$env:WINPYthon_subdirectory_name = "python"
+$env:WINPYthon_exe = "python.exe"
+# Define the path to the config file
+Get-Content (${PSScriptRoot} +"\env.ini") | ForEach-Object {
+ $parts = $_ -split '=', 2
+ if ($parts.Count -eq 2) {
+ Set-Variable -Name ($parts[0]).Trim() -Value $parts[1].Trim() -Scope Global
+ }
+}
+
# $env:PYTHONUTF8 = 1 would create issues in "movable" patching
$env:WINPYDIRBASE = "$dp0\.."
# get a normalize path
@@ -9,12 +21,12 @@ $env:WINPYDIRBASE = [System.IO.Path]::GetFullPath( $env:WINPYDIRBASE )
# avoid double_init (will only resize screen)
if (-not ($env:WINPYDIR -eq [System.IO.Path]::GetFullPath( $env:WINPYDIRBASE+"\{self.python_dir_name}")) ) {
-$env:WINPYDIR = $env:WINPYDIRBASE+"\{self.python_dir_name}"
+$env:WINPYDIR = $env:WINPYDIRBASE+ "\" +$env:WINPYthon_subdirectory_name
# 2019-08-25 pyjulia needs absolutely a variable PYTHON=%WINPYDIR%python.exe
-$env:PYTHON = "%WINPYDIR%\python.exe"
+$env:PYTHON = $env:WINPYthon_exe
$env:PYTHONPATHz = "%WINPYDIR%;%WINPYDIR%\Lib;%WINPYDIR%\DLLs"
-$env:WINPYVER = '{self.winpython_version_name}'
+$env:WINPYVER = $env:WINPYVER
# rem 2023-02-12 try utf-8 on console
# rem see https://github.com/pypa/pip/issues/11798#issuecomment-1427069681
$env:PYTHONIOENCODING = "utf-8"
@@ -28,7 +40,8 @@ $env:WINPYDIRBASE = ""
$env:JUPYTER_DATA_DIR = "$env:HOME"
if (-not $env:PATH.ToLower().Contains(";"+ $env:WINPYDIR.ToLower()+ ";")) {
- $env:PATH = "{full_path_ps_env_var}" }
+ $env:PATH = "$env:WINPYDIR\\Lib\site-packages\PyQt5;$env:WINPYDIR\\;$env:WINPYDIR\\DLLs;$env:WINPYDIR\\Scripts;$env:WINPYDIR\\..\t;$env:WINPYDIR\\..\n;$env:path" }
+
#rem force default pyqt5 kit for Spyder if PyQt5 module is there
if (Test-Path "$env:WINPYDIR\Lib\site-packages\PyQt5\__init__.py") { $env:QT_API = "pyqt5" }
diff --git a/portable/scripts/env.bat b/portable/scripts/env.bat
index dc8aa552..316f6587 100644
--- a/portable/scripts/env.bat
+++ b/portable/scripts/env.bat
@@ -1,4 +1,11 @@
@echo off
+
+rem default if init fails
+set WINPYthon_subdirectory_name=python
+set WINPYthon_exe=python.exe
+rem read init variables
+FOR /F "usebackq tokens=1,2 delims==" %%G IN ("%~dp0env.ini") DO (set %%G=%%H)
+
set WINPYDIRBASE=%~dp0..
rem get a normalized path
@@ -9,11 +16,11 @@ if "%WINPYDIRBASE:~-1%"=="\" set WINPYDIRBASE=%WINPYDIRBASE:~0,-1%
set WINPYDIRBASETMP=
popd
-set WINPYDIR=%WINPYDIRBASE%\{self.python_dir_name}
+set WINPYDIR=%WINPYDIRBASE%\%WINpython_subdirectory_name%
rem 2019-08-25 pyjulia needs absolutely a variable PYTHON=%WINPYDIR%\python.exe
-set PYTHON=%WINPYDIR%\python.exe
+set PYTHON=%WINPYDIR%\%WINpython_exe%
set PYTHONPATHz=%WINPYDIR%;%WINPYDIR%\Lib;%WINPYDIR%\DLLs
-set WINPYVER={self.winpython_version_name}
+set WINPYVER=%WINPYVER%
rem 2023-02-12 utf-8 on console to avoid pip crash
rem see https://github.com/pypa/pip/issues/11798#issuecomment-1427069681
@@ -32,10 +39,14 @@ rem Remove all double quotes
set PATH_CLEANED=%PATH:"=%
echo ";%PATH_CLEANED%;" | %FINDDIR%\find.exe /C /I ";%WINPYDIR%\;" >nul
if %ERRORLEVEL% NEQ 0 (
- set "PATH={full_path_env_var}"
+ set "PATH=%WINPYDIR%\Lib\site-packages\PyQt5;%WINPYDIR%\;%WINPYDIR%\DLLs;%WINPYDIR%\Scripts;%WINPYDIR%\..\t;%WINPYDIR%\..\n;%PATH%"
cd .
)
set PATH_CLEANED=
rem force default pyqt5 kit for Spyder if PyQt5 module is there
if exist "%WINPYDIR%\Lib\site-packages\PyQt5\__init__.py" set QT_API=pyqt5
+
+rem modern Pandoc wheel need this
+if exist "%WINPYDIRBASE%\t\pandoc.exe" set PYPANDOC_PANDOC=%WINPYDIRBASE%\t\pandoc.exe
+
diff --git a/portable/scripts/make_winpython_fix.bat b/portable/scripts/make_winpython_fix.bat
deleted file mode 100644
index a0dddfa6..00000000
--- a/portable/scripts/make_winpython_fix.bat
+++ /dev/null
@@ -1,6 +0,0 @@
-@echo off
-call "%~dp0env.bat"
-echo patch pip and current launchers for non-move
-
-"%WINPYDIR%\python.exe" -c "from winpython import wppm;dist=wppm.Distribution(r'%WINPYDIR%');dist.patch_standard_packages('pip', to_movable=False)"
-pause
\ No newline at end of file
diff --git a/portable/scripts/make_winpython_movable.bat b/portable/scripts/make_winpython_movable.bat
deleted file mode 100644
index 2ae903f8..00000000
--- a/portable/scripts/make_winpython_movable.bat
+++ /dev/null
@@ -1,6 +0,0 @@
-@echo off
-call "%~dp0env.bat"
-echo patch pip and current launchers for move
-
-"%WINPYDIR%\python.exe" -c "from winpython import wppm;dist=wppm.Distribution(r'%WINPYDIR%');dist.patch_standard_packages('pip', to_movable=True)"
-pause
\ No newline at end of file
diff --git a/portable/scripts/register_python.bat b/portable/scripts/register_python.bat
deleted file mode 100644
index 945110e6..00000000
--- a/portable/scripts/register_python.bat
+++ /dev/null
@@ -1,4 +0,0 @@
-@echo off
-call "%~dp0env_for_icons.bat"
-cd /D "%WINPYDIR%\Scripts"
-"%WINPYDIR%\python.exe" "%WINPYDIR%\Lib\site-packages\winpython\register_python.py" %*
\ No newline at end of file
diff --git a/portable/scripts/register_python_for_all.bat b/portable/scripts/register_python_for_all.bat
index e9e4e7f9..9f45aa70 100644
--- a/portable/scripts/register_python_for_all.bat
+++ b/portable/scripts/register_python_for_all.bat
@@ -1,3 +1,3 @@
@echo off
call "%~dp0env.bat"
-call "%~dp0register_python.bat" --all
\ No newline at end of file
+"%WINPYDIR%\python.exe" "%WINPYDIR%\Lib\site-packages\winpython\associate.py" --all
diff --git a/portable/scripts/unregister_python.bat b/portable/scripts/unregister_python.bat
deleted file mode 100644
index 40a9b12e..00000000
--- a/portable/scripts/unregister_python.bat
+++ /dev/null
@@ -1,4 +0,0 @@
-@echo off
-call "%~dp0env_for_icons.bat"
-cd /D "%WINPYDIR%\Scripts"
-"%WINPYDIR%\python.exe" "%WINPYDIR%\Lib\site-packages\winpython\unregister_python.py" %*
\ No newline at end of file
diff --git a/portable/scripts/unregister_python_for_all.bat b/portable/scripts/unregister_python_for_all.bat
index 6aca706f..b1600226 100644
--- a/portable/scripts/unregister_python_for_all.bat
+++ b/portable/scripts/unregister_python_for_all.bat
@@ -1,3 +1,3 @@
@echo off
call "%~dp0env.bat"
-call "%~dp0unregister_python.bat" --all
\ No newline at end of file
+"%WINPYDIR%\python.exe" "%WINPYDIR%\Lib\site-packages\winpython\associate.py" --unregister --all
diff --git a/portable/scripts/winqtconsole.bat b/portable/scripts/winqtconsole.bat
deleted file mode 100644
index c05e36fa..00000000
--- a/portable/scripts/winqtconsole.bat
+++ /dev/null
@@ -1,3 +0,0 @@
-@echo off
-call "%~dp0env_for_icons.bat" %*
-"%WINPYDIR%\scripts\jupyter-qtconsole.exe" %*
\ No newline at end of file
diff --git a/portable/scripts/winvscode.bat b/portable/scripts/winvscode.bat
index efcb4208..d9697b89 100644
--- a/portable/scripts/winvscode.bat
+++ b/portable/scripts/winvscode.bat
@@ -6,5 +6,5 @@ if exist "%WINPYDIR%\..\t\vscode\code.exe" (
if exist "%LOCALAPPDATA%\Programs\Microsoft VS Code\code.exe" (
"%LOCALAPPDATA%\Programs\Microsoft VS Code\code.exe" %*
) else (
- "code.exe" %*
+ code %*
))
\ No newline at end of file
diff --git a/portable/scripts/wpcp.bat b/portable/scripts/wpcp.bat
index 069b3544..5edb982c 100644
--- a/portable/scripts/wpcp.bat
+++ b/portable/scripts/wpcp.bat
@@ -1,3 +1,3 @@
@echo off
call "%~dp0env_for_icons.bat" %*
-cmd.exe /k "echo wppm & wppm"
\ No newline at end of file
+cmd.exe /k "echo wppm & wppm %*"
\ No newline at end of file
diff --git a/pylock_to_requirements.py b/pylock_to_requirements.py
new file mode 100644
index 00000000..9438233f
--- /dev/null
+++ b/pylock_to_requirements.py
@@ -0,0 +1,76 @@
+import sys
+from pathlib import Path
+from collections import defaultdict
+
+# Use tomllib if available (Python 3.11+), otherwise fall back to tomli
+try:
+ import tomllib # Python 3.11+
+except ImportError:
+ try:
+ import tomli as tomllib # For older Python versions
+ except ImportError:
+ print("Please install tomli for Python < 3.11: pip install tomli")
+ sys.exit(1)
+
+
+
+def parse_pylock_toml(path):
+ with open(path, "rb") as f:
+ data = tomllib.load(f)
+
+ # This dictionary maps package names to (version, [hashes])
+ package_hashes = defaultdict(lambda: {"version": "", "hashes": []})
+
+ for entry in data.get("packages", []):
+ name = entry["name"]
+ version = entry["version"]
+ all_hashes = []
+
+ # Handle wheels
+ for wheel in entry.get("wheels", []):
+ sha256 = wheel.get("hashes", {}).get("sha256")
+ if sha256:
+ all_hashes.append(sha256)
+
+ # Handle sdist (if present)
+ sdist = entry.get("sdist")
+ if sdist and "hashes" in sdist:
+ sha256 = sdist["hashes"].get("sha256")
+ if sha256:
+ all_hashes.append(sha256)
+
+ package_hashes[name]["version"] = version
+ package_hashes[name]["hashes"].extend(all_hashes)
+
+ return package_hashes
+
+
+def write_requirements_txt(package_hashes, output_path="requirements.txt"):
+ with open(output_path, "w") as f:
+ for name, data in sorted(package_hashes.items()):
+ version = data["version"]
+ hashes = data["hashes"]
+
+ if hashes:
+ f.write(f"{name}=={version} \\\n")
+ for i, h in enumerate(hashes):
+ end = " \\\n" if i < len(hashes) - 1 else "\n"
+ f.write(f" --hash=sha256:{h}{end}")
+ else:
+ f.write(f"{name}=={version}\n")
+
+ print(f"✅ requirements.txt written to {output_path}")
+
+if __name__ == "__main__":
+ if len(sys.argv) != 2:
+ print("Usage: python pylock_to_requirements.py pylock.toml")
+ sys.exit(1)
+
+ path = Path(sys.argv[1])
+ if not path.exists():
+ print(f"❌ File not found: {path}")
+ sys.exit(1)
+
+ pkgs = parse_pylock_toml(path)
+ dest = path.parent / (path.stem.replace('pylock','requirement_with_hash')+ '.txt')
+ write_requirements_txt(pkgs, dest)
diff --git a/winpython/__init__.py b/winpython/__init__.py
index 3c47549c..c1fdd2f8 100644
--- a/winpython/__init__.py
+++ b/winpython/__init__.py
@@ -4,7 +4,7 @@
-----------------------------------------
Copyright (c) 2012-2013 Pierre Raybaut
-Copyright (c) 2014-2024+ The Winpython development team https://github.com/winpython/
+Copyright (c) 2014-2025+ The Winpython development team https://github.com/winpython/
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
@@ -28,6 +28,6 @@
OTHER DEALINGS IN THE SOFTWARE.
"""
-__version__ = '13.2.20250309'
+__version__ = '15.5.20250511'
__license__ = __doc__
__project_url__ = 'http://winpython.github.io/'
diff --git a/winpython/associate.py b/winpython/associate.py
index 8868ea7a..5d5854ff 100644
--- a/winpython/associate.py
+++ b/winpython/associate.py
@@ -1,430 +1,238 @@
# -*- coding: utf-8 -*-
#
+# associate.py = Register a Python distribution
# Copyright © 2012 Pierre Raybaut
# Licensed under the terms of the MIT License
# (see winpython/__init__.py for details)
-"""
-Register a Python distribution
-
-Created on Tue Aug 21 21:46:30 2012
-"""
-
import sys
import os
from pathlib import Path
-import platform
-import importlib
-
-# import subprocess
-
-
-# Local imports
+import importlib.util
import winreg
from winpython import utils
+from argparse import ArgumentParser
-KEY_C = r"Software\Classes\%s"
-KEY_C0 = KEY_C % r"Python.%sFile\shell"
-KEY_C1 = KEY_C % r"Python.%sFile\shell\%s"
-KEY_C2 = KEY_C1 + r"\command"
-KEY_DROP0 = KEY_C % r"Python.%sFile\shellex"
-KEY_DROP1 = KEY_C % r"Python.%sFile\shellex\DropHandler"
-KEY_I = KEY_C % r"Python.%sFile\DefaultIcon"
-KEY_D = KEY_C % r"Python.%sFile"
-EWI = "Edit with IDLE"
-EWS = "Edit with Spyder"
-
-KEY_S = r"Software\Python"
-KEY_S0 = KEY_S + r"\WinPython" # was PythonCore before PEP-0514
-KEY_S1 = KEY_S0 + r"\%s"
-
-def _remove_start_menu_folder(target, current=True):
- "remove menu Folder for target WinPython"
- import importlib.util
- win32com_exists = importlib.util.find_spec('win32com') is not None
-
- # we return nothing if no win32com package
- if win32com_exists:
- utils.remove_winpython_start_menu_folder(current=current)
-def _get_shortcut_data(target, current=True):
- "get windows menu access, if win32com exists otherwise nothing"
- import importlib.util
- win32com_exists = importlib.util.find_spec('win32com') is not None
+# --- Helper functions for Registry ---
+
+def _set_reg_value(root, key_path, name, value, reg_type=winreg.REG_SZ, verbose=False):
+ """Helper to create key and set a registry value using CreateKeyEx."""
+ rootkey_name = "HKEY_CURRENT_USER" if root == winreg.HKEY_CURRENT_USER else "HKEY_LOCAL_MACHINE"
+ if verbose:
+ print(f"{rootkey_name}\\{key_path}\\{name if name else ''}:{value}")
+ try:
+ # Use CreateKeyEx with context manager for automatic closing
+ with winreg.CreateKeyEx(root, key_path, 0, winreg.KEY_WRITE) as key:
+ winreg.SetValueEx(key, name, 0, reg_type, value)
+ except OSError as e:
+ print(f"Error creating/setting registry value {rootkey_name}\\{key_path}\\{name}: {e}", file=sys.stderr)
+
+def _delete_reg_key(root, key_path, verbose=False):
+ """Helper to delete a registry key, ignoring if not found."""
+ rootkey_name = "HKEY_CURRENT_USER" if root == winreg.HKEY_CURRENT_USER else "HKEY_LOCAL_MACHINE"
+ if verbose:
+ print(f"{rootkey_name}\\{key_path}")
+ try:
+ # DeleteKey can only delete keys with no subkeys.
+ # For keys with (still) subkeys, use DeleteKeyEx on the parent key if available
+ winreg.DeleteKey(root, key_path)
+ except FileNotFoundError:
+ if verbose:
+ print(f"Registry key not found (skipping deletion): {rootkey_name}\\{key_path}")
+ except OSError as e: # Catch other potential errors like key not empty
+ print(f"Error deleting registry key {rootkey_name}\\{key_path}: {e}", file=sys.stderr)
+
+
+# --- Helper functions for Start Menu Shortcuts ---
+
+def _has_pywin32():
+ """Check if pywin32 (pythoncom) is installed."""
+ return importlib.util.find_spec('pythoncom') is not None
+
+def _remove_start_menu_folder(target, current=True, has_pywin32=False):
+ "remove menu Folder for target WinPython if pywin32 exists"
+ if has_pywin32:
+ utils.remove_winpython_start_menu_folder(current=current)
+ else:
+ print("Skipping start menu removal as pywin32 package is not installed.")
- # we return nothing if no win32com package
- if not win32com_exists:
+def _get_shortcut_data(target, current=True, has_pywin32=False):
+ "get windows menu access data if pywin32 exists, otherwise empty list"
+ if not has_pywin32:
return []
- wpgroup = utils.create_winpython_start_menu_folder(current=current)
+
wpdir = str(Path(target).parent)
data = []
for name in os.listdir(wpdir):
bname, ext = Path(name).stem, Path(name).suffix
- if ext == ".exe":
+ if ext.lower() == ".exe":
+ # Path for the shortcut file in the start menu folder
+ shortcut_name = str(Path(utils.create_winpython_start_menu_folder(current=current)) / bname) + '.lnk'
data.append(
(
- str(Path(wpdir) / name),
- bname,
- str(Path(wpgroup) / bname),
+ str(Path(wpdir) / name), # Target executable path
+ bname, # Description/Name
+ shortcut_name, # Shortcut file path
)
)
return data
-
-def register(target, current=True, verbose=True):
- """Register a Python distribution in Windows registry"""
+# --- PythonCore entries (PEP-0514 and WinPython specific) ---
+
+
+def register_in_registery(target, current=True, reg_type=winreg.REG_SZ, verbose=True) -> tuple[list[any], ...]:
+ """Register in Windows (like regedit)"""
+
+ # --- Constants ---
+ DROP_HANDLER_CLSID = "{60254CA5-953B-11CF-8C96-00AA00B8708C}"
+
+ # --- CONFIG ---
+ target_path = Path(target).resolve()
+ python_exe = str(target_path / "python.exe")
+ pythonw_exe = str(target_path / "pythonw.exe")
+ spyder_exe = str(target_path.parent / "Spyder.exe")
+ icon_py = str(target_path / "DLLs" / "py.ico")
+ icon_pyc = str(target_path / "DLLs" / "pyc.ico")
+ idle_path = str(target_path / "Lib" / "idlelib" / "idle.pyw")
+ doc_path = str(target_path / "Doc" / "html" / "index.html")
+ python_infos = utils.get_python_infos(target) # ('3.11', 64)
+ short_version = python_infos[0] # e.g., '3.11'
+ version = utils.get_python_long_version(target) # e.g., '3.11.5'
+ arch = f'{python_infos[1]}bit' # e.g., '64bit'
+ display = f"Python {version} ({arch})"
+
+ permanent_entries = [] # key_path, name, value
+ dynamic_entries = [] # key_path, name, value
+ core_entries = [] # key_path, name, value
+ lost_entries = [] # intermediate keys to remove later
+ # --- File associations ---
+ ext_map = {".py": "Python.File", ".pyw": "Python.NoConFile", ".pyc": "Python.CompiledFile"}
+ ext_label = {".py": "Python File", ".pyw": "Python File (no console)", ".pyc": "Compiled Python File"}
+ for ext, ftype in ext_map.items():
+ permanent_entries.append((f"Software\\Classes\\{ext}", None, ftype))
+ if ext in (".py", ".pyw"):
+ permanent_entries.append((f"Software\\Classes\\{ext}", "Content Type", "text/plain"))
+
+ # --- Descriptions, Icons, DropHandlers ---
+ for ext, ftype in ext_map.items():
+ dynamic_entries.append((f"Software\\Classes\\{ftype}", None, ext_label[ext]))
+ dynamic_entries.append((f"Software\\Classes\\{ftype}\\DefaultIcon", None, icon_py if "Compiled" not in ftype else icon_pyc))
+ dynamic_entries.append((f"Software\\Classes\\{ftype}\\shellex\\DropHandler", None, DROP_HANDLER_CLSID))
+ lost_entries.append((f"Software\\Classes\\{ftype}\\shellex", None, None))
+
+ # --- Shell commands ---
+ for ext, ftype in ext_map.items():
+ dynamic_entries.append((f"Software\\Classes\\{ftype}\\shell\\open\\command", None, f'''"{pythonw_exe if ftype=='Python.NoConFile' else python_exe}" "%1" %*'''))
+ lost_entries.append((f"Software\\Classes\\{ftype}\\shell\\open", None, None))
+ lost_entries.append((f"Software\\Classes\\{ftype}\\shell", None, None))
+
+ dynamic_entries.append((rf"Software\Classes\Python.File\shell\Edit with IDLE\command", None, f'"{pythonw_exe}" "{idle_path}" -n -e "%1"'))
+ dynamic_entries.append((rf"Software\Classes\Python.NoConFile\shell\Edit with IDLE\command", None, f'"{pythonw_exe}" "{idle_path}" -n -e "%1"'))
+ lost_entries.append((rf"Software\Classes\Python.File\shell\Edit with IDLE", None, None))
+ lost_entries.append((rf"Software\Classes\Python.NoConFile\shell\Edit with IDLE", None, None))
+
+ if Path(spyder_exe).exists():
+ dynamic_entries.append((rf"Software\Classes\Python.File\shell\Edit with Spyder\command", None, f'"{spyder_exe}" "%1"'))
+ dynamic_entries.append((rf"Software\Classes\Python.NoConFile\shell\Edit with Spyder\command", None, f'"{spyder_exe}" "%1"'))
+ lost_entries.append((rf"Software\Classes\Python.File\shell\Edit with Spyder", None, None))
+ lost_entries.append((rf"Software\Classes\Python.NoConFile\shell\Edit with Spyder", None, None))
+
+ # --- WinPython Core registry entries (PEP 514 style) ---
+ base = f"Software\\Python\\WinPython\\{short_version}"
+ core_entries.append((base, "DisplayName", display))
+ core_entries.append((base, "SupportUrl", "https://winpython.github.io"))
+ core_entries.append((base, "SysVersion", short_version))
+ core_entries.append((base, "SysArchitecture", arch))
+ core_entries.append((base, "Version", version))
+
+ core_entries.append((f"{base}\\InstallPath", None, str(target)))
+ core_entries.append((f"{base}\\InstallPath", "ExecutablePath", python_exe))
+ core_entries.append((f"{base}\\InstallPath", "WindowedExecutablePath", pythonw_exe))
+ core_entries.append((f"{base}\\InstallPath\\InstallGroup", None, f"Python {short_version}"))
+
+ core_entries.append((f"{base}\\Modules", None, ""))
+ core_entries.append((f"{base}\\PythonPath", None, f"{target}\\Lib;{target}\\DLLs"))
+ core_entries.append((f"{base}\\Help\\Main Python Documentation", None, doc_path))
+ lost_entries.append((f"{base}\\Help", None, None))
+ lost_entries.append((f"Software\\Python\\WinPython", None, None))
+
+ return permanent_entries, dynamic_entries, core_entries, lost_entries
+
+# --- Main Register/Unregister Functions ---
+
+def register(target, current=True, reg_type=winreg.REG_SZ, verbose=True):
+ """Register a Python distribution in Windows registry and create Start Menu shortcuts"""
root = winreg.HKEY_CURRENT_USER if current else winreg.HKEY_LOCAL_MACHINE
+ has_pywin32 = _has_pywin32()
- # Creating Registry entries
if verbose:
print(f'Creating WinPython registry entries for {target}')
- # Extensions
- winreg.SetValueEx(
- winreg.CreateKey(root, KEY_C % ".py"),
- "",
- 0,
- winreg.REG_SZ,
- "Python.File",
- )
- winreg.SetValueEx(
- winreg.CreateKey(root, KEY_C % ".pyw"),
- "",
- 0,
- winreg.REG_SZ,
- "Python.NoConFile",
- )
- winreg.SetValueEx(
- winreg.CreateKey(root, KEY_C % ".pyc"),
- "",
- 0,
- winreg.REG_SZ,
- "Python.CompiledFile",
- )
- winreg.SetValueEx(
- winreg.CreateKey(root, KEY_C % ".pyo"),
- "",
- 0,
- winreg.REG_SZ,
- "Python.CompiledFile",
- )
-
- # MIME types
- winreg.SetValueEx(
- winreg.CreateKey(root, KEY_C % ".py"),
- "Content Type",
- 0,
- winreg.REG_SZ,
- "text/plain",
- )
- winreg.SetValueEx(
- winreg.CreateKey(root, KEY_C % ".pyw"),
- "Content Type",
- 0,
- winreg.REG_SZ,
- "text/plain",
- )
-
- # Verbs
- python = str((Path(target) / "python.exe").resolve())
- pythonw = str((Path(target) / "pythonw.exe").resolve())
- spyder = str((Path(target).parent / "Spyder.exe").resolve())
-
- if not Path(spyder).is_file():
- spyder = f'{pythonw}" "{target}\Scripts\spyder'
- winreg.SetValueEx(
- winreg.CreateKey(root, KEY_C2 % ("", "open")),
- "",
- 0,
- winreg.REG_SZ,
- '"%s" "%%1" %%*' % python,
- )
- winreg.SetValueEx(
- winreg.CreateKey(root, KEY_C2 % ("NoCon", "open")),
- "",
- 0,
- winreg.REG_SZ,
- '"%s" "%%1" %%*' % pythonw,
- )
- winreg.SetValueEx(
- winreg.CreateKey(root, KEY_C2 % ("Compiled", "open")),
- "",
- 0,
- winreg.REG_SZ,
- '"%s" "%%1" %%*' % python,
- )
- winreg.SetValueEx(
- winreg.CreateKey(root, KEY_C2 % ("", EWI)),
- "",
- 0,
- winreg.REG_SZ,
- '"%s" "%s\Lib\idlelib\idle.pyw" -n -e "%%1"' % (pythonw, target),
- )
- winreg.SetValueEx(
- winreg.CreateKey(root, KEY_C2 % ("NoCon", EWI)),
- "",
- 0,
- winreg.REG_SZ,
- '"%s" "%s\Lib\idlelib\idle.pyw" -n -e "%%1"' % (pythonw, target),
- )
- winreg.SetValueEx(
- winreg.CreateKey(root, KEY_C2 % ("", EWS)),
- "",
- 0,
- winreg.REG_SZ,
- '"%s" "%%1"' % spyder,
- )
- winreg.SetValueEx(
- winreg.CreateKey(root, KEY_C2 % ("NoCon", EWS)),
- "",
- 0,
- winreg.REG_SZ,
- '"%s" "%%1"' % spyder,
- )
-
- # Drop support
- handler = "{60254CA5-953B-11CF-8C96-00AA00B8708C}"
- for ftype in ("", "NoCon", "Compiled"):
- winreg.SetValueEx(
- winreg.CreateKey(root, KEY_DROP1 % ftype),
- "",
- 0,
- winreg.REG_SZ,
- handler,
- )
- # Icons
- dlls = str(Path(target) / "DLLs")
- winreg.SetValueEx(
- winreg.CreateKey(root, KEY_I % ""),
- "",
- 0,
- winreg.REG_SZ,
- r"%s\py.ico" % dlls,
- )
- winreg.SetValueEx(
- winreg.CreateKey(root, KEY_I % "NoCon"),
- "",
- 0,
- winreg.REG_SZ,
- r"%s\py.ico" % dlls,
- )
- winreg.SetValueEx(
- winreg.CreateKey(root, KEY_I % "Compiled"),
- "",
- 0,
- winreg.REG_SZ,
- r"%s\pyc.ico" % dlls,
- )
-
- # Descriptions
- winreg.SetValueEx(
- winreg.CreateKey(root, KEY_D % ""),
- "",
- 0,
- winreg.REG_SZ,
- "Python File",
- )
- winreg.SetValueEx(
- winreg.CreateKey(root, KEY_D % "NoCon"),
- "",
- 0,
- winreg.REG_SZ,
- "Python File (no console)",
- )
- winreg.SetValueEx(
- winreg.CreateKey(root, KEY_D % "Compiled"),
- "",
- 0,
- winreg.REG_SZ,
- "Compiled Python File",
- )
-
- # PythonCore entries
- python_infos = utils.get_python_infos(target) # ('3.11', 64)
- short_version = python_infos[0] # 3.11 from ('3.11', 64)
- long_version = utils.get_python_long_version(target) # 3.11.5
- key_core = (KEY_S1 % short_version) + r"\%s" # Winpython\3.11
-
- # PEP-0514 additions, with standard Python practice
- SupportUrl="https://winpython.github.io"
- SysArchitecture = platform.architecture()[0] # '64bit'
- SysVersion = '.'.join(platform.python_version_tuple()[:2]) # '3.11'
- Version = platform.python_version() # '3.11.5'
-
- # But keep consistent with past possibilities until more alignement
- SysArchitecture = f'{python_infos[1]}bit' # '64bit'
- SysVersion = short_version
- Version = long_version
-
- DisplayName = f'Python {Version} ({SysArchitecture})'
- key_short = (KEY_S1 % short_version) # WinPython\3.11
- key_keys={'DisplayName':DisplayName,
- 'SupportUrl':SupportUrl,
- 'SysVersion':SysVersion,
- 'SysArchitecture':SysArchitecture,
- 'Version':Version}
-
- regkey = winreg.CreateKey(root, key_short)
- # see https://www.programcreek.com/python/example/106949/winreg.CreateKey
- # winreg.SetValueEx(key, '', reg.REG_SZ, '')
- for k, v in key_keys.items():
- winreg.SetValueEx(
- regkey,
- k,
- 0,
- winreg.REG_SZ,
- v,
- )
- winreg.CloseKey(regkey)
-
- # pep-0514 additions at InstallPathLevel
- ExecutablePath = python
- WindowedExecutablePath = pythonw
-
- key_short = key_core % "InstallPath" # WinPython\3.11\InstallPath
- key_keys={'ExecutablePath':ExecutablePath,
- 'WindowedExecutablePath':WindowedExecutablePath}
-
- regkey = winreg.CreateKey(root, key_core % "InstallPath")
- winreg.SetValueEx(
- regkey,
- "",
- 0,
- winreg.REG_SZ,
- target + '\\',
- )
- for k, v in key_keys.items():
- winreg.SetValueEx(
- regkey,
- k,
- 0,
- winreg.REG_SZ,
- v,
- )
- winreg.CloseKey(regkey)
-
-
-
- winreg.SetValueEx(
- winreg.CreateKey(root, key_core % r"InstallPath\InstallGroup"),
- "",
- 0,
- winreg.REG_SZ,
- "Python %s" % short_version,
- )
- winreg.SetValueEx(
- winreg.CreateKey(root, key_core % "Modules"),
- "",
- 0,
- winreg.REG_SZ,
- "",
- )
- winreg.SetValueEx(
- winreg.CreateKey(root, key_core % "PythonPath"),
- "",
- 0,
- winreg.REG_SZ,
- r"%s\Lib;%s\DLLs" % (target, target),
- )
- winreg.SetValueEx(
- winreg.CreateKey(
- root,
- key_core % r"Help\Main Python Documentation",
- ),
- "",
- 0,
- winreg.REG_SZ,
- r"%s\Doc\python%s.chm" % (target, long_version),
- )
-
- # Create start menu entries for all WinPython launchers
- spec = importlib.util.find_spec('pythoncom')
- if verbose and spec is None:
- print(f"Can't create WinPython menu as pywin32 package is not installed")
- if verbose and spec is not None:
- print(f'Creating WinPython menu for all icons in {target}')
- for path, desc, fname in _get_shortcut_data(target, current=current):
- utils.create_shortcut(path, desc, fname, verbose=verbose)
+ permanent_entries, dynamic_entries, core_entries, lost_entries = register_in_registery(target)
+ # Set registry entries for given target
+ for key_path, name, value in permanent_entries + dynamic_entries + core_entries:
+ _set_reg_value(root, key_path, name, value, verbose=verbose)
+
+ # Create start menu entries
+ if has_pywin32:
+ if verbose:
+ print(f'Creating WinPython menu for all icons in {target.parent}')
+ for path, desc, fname in _get_shortcut_data(target, current=current, has_pywin32=True):
+ try:
+ utils.create_shortcut(path, desc, fname, verbose=verbose)
+ except Exception as e:
+ print(f"Error creating shortcut for {desc} at {fname}: {e}", file=sys.stderr)
+ else:
+ print("Skipping start menu shortcut creation as pywin32 package is needed.")
def unregister(target, current=True, verbose=True):
- """Unregister a Python distribution in Windows registry"""
- # Removing Registry entries
- if verbose:
- print(f'Removing WinPython registry entries for {target}')
+ """Unregister a Python distribution from Windows registry and remove Start Menu shortcuts"""
root = winreg.HKEY_CURRENT_USER if current else winreg.HKEY_LOCAL_MACHINE
- short_version = utils.get_python_infos(target)[0]
- key_core = (KEY_S1 % short_version) + r"\%s"
- for key in (
- # Drop support
- KEY_DROP1 % "",
- KEY_DROP1 % "NoCon",
- KEY_DROP1 % "Compiled",
- KEY_DROP0 % "",
- KEY_DROP0 % "NoCon",
- KEY_DROP0 % "Compiled",
- # Icons
- KEY_I % "NoCon",
- KEY_I % "Compiled",
- KEY_I % "",
- # Edit with IDLE
- KEY_C2 % ("", EWI),
- KEY_C2 % ("NoCon", EWI),
- KEY_C1 % ("", EWI),
- KEY_C1 % ("NoCon", EWI),
- # Edit with Spyder
- KEY_C2 % ("", EWS),
- KEY_C2 % ("NoCon", EWS),
- KEY_C1 % ("", EWS),
- KEY_C1 % ("NoCon", EWS),
- # Verbs
- KEY_C2 % ("", "open"),
- KEY_C2 % ("NoCon", "open"),
- KEY_C2 % ("Compiled", "open"),
- KEY_C1 % ("", "open"),
- KEY_C1 % ("NoCon", "open"),
- KEY_C1 % ("Compiled", "open"),
- KEY_C0 % "",
- KEY_C0 % "NoCon",
- KEY_C0 % "Compiled",
- # Descriptions
- KEY_D % "NoCon",
- KEY_D % "Compiled",
- KEY_D % "",
- # PythonCore
- key_core % r"InstallPath\InstallGroup",
- key_core % "InstallPath",
- key_core % "Modules",
- key_core % "PythonPath",
- key_core % r"Help\Main Python Documentation",
- key_core % "Help",
- KEY_S1 % short_version,
- KEY_S0,
- KEY_S,
- ):
- try:
- if verbose:
- print(key)
- winreg.DeleteKey(root, key)
- except WindowsError:
- rootkey = "HKEY_CURRENT_USER" if current else "HKEY_LOCAL_MACHINE"
- if verbose:
- print(
- r"Unable to remove %s\%s" % (rootkey, key),
- file=sys.stderr,
- )
- # remove menu shortcuts
- spec = importlib.util.find_spec('pythoncom')
- if verbose and spec is None:
- print(f"Can't remove WinPython menu as pywin32 package is not installed")
- if verbose and spec is not None:
- print(f'Removing WinPython menu for all icons in {target}')
- _remove_start_menu_folder(target, current=current)
-
- #for path, desc, fname in _get_shortcut_data(target, current=current):
- # if Path(fname).exists():
- # os.remove(fname)
+ has_pywin32 = _has_pywin32()
+
+ if verbose:
+ print(f'Removing WinPython registry entries for {target}')
+
+ permanent_entries, dynamic_entries, core_entries , lost_entries = register_in_registery(target)
+
+ # List of keys to attempt to delete, ordered from most specific to general
+ keys_to_delete = sorted(list(set(key_path for key_path , name, value in (dynamic_entries + core_entries + lost_entries))), key=len, reverse=True)
+
+ rootkey_name = "HKEY_CURRENT_USER" if root == winreg.HKEY_CURRENT_USER else "HKEY_LOCAL_MACHINE"
+ for key_path in keys_to_delete:
+ _delete_reg_key(root, key_path, verbose=verbose)
+
+ # Remove start menu shortcuts
+ if has_pywin32:
+ if verbose:
+ print(f'Removing WinPython menu for all icons in {target.parent}')
+ _remove_start_menu_folder(target, current=current, has_pywin32=True)
+ # The original code had commented out code to delete .lnk files individually.
+ else:
+ print("Skipping start menu removal as pywin32 package is needed.")
if __name__ == "__main__":
- register(sys.prefix)
- unregister(sys.prefix)
\ No newline at end of file
+ # Ensure we are running from the target WinPython environment
+ parser = ArgumentParser(description="Register or Un-register Python file extensions, icons "\
+ "and Windows explorer context menu to this "\
+ "Python distribution.")
+ parser.add_argument('--unregister', action="store_true",
+ help='register to all users, requiring administrative '\
+ 'privileges (default: register to current user only)')
+ parser.add_argument('--all', action="store_true",
+ help='action is to all users, requiring administrative '\
+ 'privileges (default: to current user only)')
+ args = parser.parse_args()
+ expected_target = Path(sys.prefix)
+ command = "unregister" if args.unregister else "register"
+ users = "all" if args.all else "user"
+ print(f"Attempting to {command} the Python environment for {users} at: {expected_target}")
+
+ target_dir = sys.prefix # Or get from arguments
+ is_current_user = True # Or get from arguments
+ if command == "register":
+ register(expected_target, current=not args.all)
+ else:
+ unregister(expected_target, current=not args.all)
diff --git a/winpython/data/tools.ini b/winpython/data/tools.ini
deleted file mode 100644
index 0f27fc8f..00000000
--- a/winpython/data/tools.ini
+++ /dev/null
@@ -1,47 +0,0 @@
-[gettext]
-description=GNU gettext Win32 porting - the GNU translation tool (useful tools for pygettext, a standard library module)
-url=https://sourceforge.net/projects/gettext
-
-[julia]
-description=The Julia Langage
-url=https://julialang.org/
-
-[mingw32]
-description=C/C++ and Fortran compilers (Mingwpy static toolchain version)
-url=https://github.com/numpy/numpy/wiki/Mingw-static-toolchain
-
-[pandoc]
-description=a universal document converter
-url=https://pandoc.org/
-
-[r]
-description=The R Project for Statistical Computing
-url=https://www.r-project.org
-
-[scite]
-description=SCIntilla based Text Editor - Multilanguage, powerful and light-weight text editor
-url=http://www.scintilla.org/SciTE.html
-
-[tortoisehg]
-description=Set of graphical tools and a shell extension for the Mercurial distributed revision control system
-url=https://tortoisehg.bitbucket.io/
-
-[winmerge]
-description=Open Source differencing and merging tool for Windows
-url=http://winmerge.org
-
-[nodejs]
-description=a JavaScript runtime built on Chrome's V8 JavaScript engine
-url=https://nodejs.org
-
-[npmjs]
-description=a package manager for JavaScript
-url=https://www.npmjs.com/
-
-[yarnpkg]
-description=a package manager for JavaScriptFast, reliable, and secure dependency management
-url=https://yarnpkg.com/lang/en/
-
-[ffmpeg]
-description=a collection of libraries and tools to process multimedia content such as audio, video, subtitles and related metadata
-url=https://ffmpeg.org
diff --git a/winpython/piptree.py b/winpython/piptree.py
index abb1a082..23a53ff3 100644
--- a/winpython/piptree.py
+++ b/winpython/piptree.py
@@ -119,7 +119,7 @@ def _get_requires(self, package: Distribution) -> List[Dict[str, str]]:
if package.requires:
for req in package.requires:
req_nameextra, req_marker = (req + ";").split(";")[:2]
- req_nameextra = self.normalize(re.split(r" |;|==|!|>|<", req_nameextra + ";")[0])
+ req_nameextra = self.normalize(re.split(r" |;|==|!|>|<|~=", req_nameextra + ";")[0])
req_key = self.normalize((req_nameextra + "[").split("[")[0])
req_key_extra = req_nameextra[len(req_key) + 1:].split("]")[0]
req_version = req[len(req_nameextra):].translate(replacements)
diff --git a/winpython/register_python.py b/winpython/register_python.py
deleted file mode 100644
index fc24c96b..00000000
--- a/winpython/register_python.py
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/env python
-import sys
-from winpython import associate, utils
-from argparse import ArgumentParser
-
-parser = ArgumentParser(description="Register Python file extensions, icons "\
- "and Windows explorer context menu to a target "\
- "Python distribution.")
-try:
- str_type = unicode
-except NameError:
- str_type = str
-parser.add_argument('--target', metavar='path', type=str,
- default=sys.prefix,
- help='path to the target Python distribution')
-parser.add_argument('--all', dest='all', action='store_const',
- const=True, default=False,
- help='register to all users, requiring administrative '\
- 'privileges (default: register to current user only)')
-args = parser.parse_args()
-
-print(args.target)
-if utils.is_python_distribution(args.target):
- associate.register(args.target, current=not args.all)
-else:
- raise WindowsError(f"Invalid Python distribution {args.target}")
diff --git a/winpython/unregister_python.py b/winpython/unregister_python.py
deleted file mode 100644
index f0bcefda..00000000
--- a/winpython/unregister_python.py
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/env python
-import sys
-from winpython import associate, utils
-from argparse import ArgumentParser
-
-parser = ArgumentParser(description="unRegister Python file extensions, icons "\
- "and Windows explorer context menu to a target "\
- "Python distribution.")
-try:
- str_type = unicode
-except NameError:
- str_type = str
-parser.add_argument('--target', metavar='path', type=str,
- default=sys.prefix,
- help='path to the target Python distribution')
-parser.add_argument('--all', dest='all', action='store_const',
- const=True, default=False,
- help='unregister to all users, requiring administrative '\
- 'privileges (default: register to current user only)')
-args = parser.parse_args()
-
-print(args.target)
-if utils.is_python_distribution(args.target):
- associate.unregister(args.target, current=not args.all)
-else:
- raise WindowsError(f"Invalid Python distribution {args.target}")
diff --git a/winpython/utils.py b/winpython/utils.py
index d1ff43d7..40961ec1 100644
--- a/winpython/utils.py
+++ b/winpython/utils.py
@@ -1,15 +1,11 @@
# -*- coding: utf-8 -*-
#
+# WinPython utilities
# Copyright © 2012 Pierre Raybaut
+# Copyright © 2014-2025+ The Winpython development team https://github.com/winpython/
# Licensed under the terms of the MIT License
# (see winpython/__init__.py for details)
-"""
-WinPython utilities
-
-Created on Tue Aug 14 14:08:40 2012
-"""
-
import os
import sys
import stat
@@ -23,117 +19,100 @@
import tarfile
import zipfile
import atexit
-import io
import winreg
+# SOURCE_PATTERN defines what an acceptable source package name is
+SOURCE_PATTERN = r'([a-zA-Z0-9\-\_\.]*)-([0-9\.\_]*[a-z]*[\-]?[0-9]*)(\.zip|\.tar\.gz|\-(py[2-7]*|py[2-7]*\.py[2-7]*)\-none\-any\.whl)'
+
+# WHEELBIN_PATTERN defines what an acceptable binary wheel package is
+WHEELBIN_PATTERN = r'([a-zA-Z0-9\-\_\.]*)-([0-9\.\_]*[a-z0-9\+]*[0-9]?)-cp([0-9]*)\-[0-9|c|o|n|e|p|m]*\-(win32|win\_amd64)\.whl'
+
def get_python_executable(path=None):
"""Return the path to the Python executable."""
- python_path = sys.executable if path is None else path
- base_dir = Path(python_path).parent if not Path(python_path).is_dir() else Path(python_path)
+ python_path = Path(path) if path else Path(sys.executable)
+ base_dir = python_path if python_path.is_dir() else python_path.parent
python_exe = base_dir / 'python.exe'
pypy_exe = base_dir / 'pypy3.exe' # For PyPy
return str(python_exe if python_exe.is_file() else pypy_exe)
def get_site_packages_path(path=None):
"""Return the path to the Python site-packages directory."""
- python_path = sys.executable if path is None else path
- base_dir = Path(python_path).parent if not Path(python_path).is_dir() else Path(python_path)
+ python_path = Path(path) if path else Path(sys.executable)
+ base_dir = python_path if python_path.is_dir() else python_path.parent
site_packages = base_dir / 'Lib' / 'site-packages'
pypy_site_packages = base_dir / 'site-packages' # For PyPy
return str(pypy_site_packages if pypy_site_packages.is_dir() else site_packages)
-def onerror(function, path, excinfo):
- """Error handler for `shutil.rmtree`.
+def get_installed_tools_markdown(path=None)-> str:
+ """Generates Markdown for installed tools section in package index."""
+ tool_lines = []
+ python_exe = Path(get_python_executable(path))
+ version = exec_shell_cmd(f'powershell (Get-Item {python_exe}).VersionInfo.FileVersion', python_exe.parent).splitlines()[0]
+ tool_lines.append(f"[Python](http://www.python.org/) | {version} | Python programming language with standard library")
+ if (node_exe := python_exe.parent.parent / "n" / "node.exe").exists():
+ version = exec_shell_cmd(f'powershell (Get-Item {node_exe}).VersionInfo.FileVersion', node_exe.parent).splitlines()[0]
+ tool_lines.append(f"[Nodejs](https://nodejs.org) | {version} | a JavaScript runtime built on Chrome's V8 JavaScript engine")
+
+ if (pandoc_exe := python_exe.parent.parent / "t" / "pandoc.exe").exists():
+ version = exec_shell_cmd("pandoc -v", pandoc_exe.parent).splitlines()[0].split(" ")[-1]
+ tool_lines.append(f"[Pandoc](https://pandoc.org) | {version} | an universal document converter")
+
+ if (vscode_exe := python_exe.parent.parent / "t" / "VSCode" / "Code.exe").exists():
+ version = exec_shell_cmd(f'powershell (Get-Item {vscode_exe}).VersionInfo.FileVersion', vscode_exe.parent).splitlines()[0]
+ tool_lines.append(f"[VSCode](https://code.visualstudio.com) | {version} | a source-code editor developed by Microsoft")
+ return "\n".join(tool_lines)
- If the error is due to an access error (read-only file), it
- attempts to add write permission and then retries.
- If the error is for another reason, it re-raises the error.
- Usage: `shutil.rmtree(path, onexc=onerror)"""
+def onerror(function, path, excinfo):
+ """Error handler for `shutil.rmtree`."""
if not os.access(path, os.W_OK):
- # Is the error an access error?
os.chmod(path, stat.S_IWUSR)
function(path)
else:
raise
-
def getFileProperties(fname):
- """
- Read all properties of the given file return them as a dictionary.
- """
- # from https://stackoverflow.com/questions/580924/how-to-access-a-files-properties-on-windows
+ """Read all properties of the given file return them as a dictionary."""
import win32api
- propNames = ('Comments', 'InternalName', 'ProductName',
- 'CompanyName', 'LegalCopyright', 'ProductVersion',
- 'FileDescription', 'LegalTrademarks', 'PrivateBuild',
- 'FileVersion', 'OriginalFilename', 'SpecialBuild')
-
+ prop_names = ('ProductName', 'ProductVersion', 'FileDescription', 'FileVersion')
props = {'FixedFileInfo': None, 'StringFileInfo': None, 'FileVersion': None}
try:
- # backslash as parm returns dictionary of numeric info corresponding to VS_FIXEDFILEINFO struc
- fixedInfo = win32api.GetFileVersionInfo(fname, '\\')
- props['FixedFileInfo'] = fixedInfo
- props['FileVersion'] = "%d.%d.%d.%d" % (fixedInfo['FileVersionMS'] / 65536,
- fixedInfo['FileVersionMS'] % 65536, fixedInfo['FileVersionLS'] / 65536,
- fixedInfo['FileVersionLS'] % 65536)
-
- # \VarFileInfo\Translation returns list of available (language, codepage)
- # pairs that can be used to retreive string info. We are using only the first pair.
+ fixed_info = win32api.GetFileVersionInfo(fname, '\\')
+ props['FixedFileInfo'] = fixed_info
+ props['FileVersion'] = "{}.{}.{}.{}".format(
+ fixed_info['FileVersionMS'] // 65536,
+ fixed_info['FileVersionMS'] % 65536,
+ fixed_info['FileVersionLS'] // 65536,
+ fixed_info['FileVersionLS'] % 65536
+ )
lang, codepage = win32api.GetFileVersionInfo(fname, '\\VarFileInfo\\Translation')[0]
-
- # any other must be of the form \StringfileInfo\%04X%04X\parm_name, middle
- # two are language/codepage pair returned from above
-
- strInfo = {}
- for propName in propNames:
- strInfoPath = u'\\StringFileInfo\\%04X%04X\\%s' % (lang, codepage, propName)
- ## print str_info
- strInfo[propName] = win32api.GetFileVersionInfo(fname, strInfoPath)
-
- props['StringFileInfo'] = strInfo
+ props['StringFileInfo'] = {
+ prop_name: win32api.GetFileVersionInfo(fname, f'\\StringFileInfo\\{lang:04X}{codepage:04X}\\{prop_name}')
+ for prop_name in prop_names
+ }
except:
pass
return props
-
def get_special_folder_path(path_name):
- """Return special folder path"""
+ """Return special folder path."""
from win32com.shell import shell, shellcon
-
- for maybe in """
- CSIDL_COMMON_STARTMENU CSIDL_STARTMENU CSIDL_COMMON_APPDATA
- CSIDL_LOCAL_APPDATA CSIDL_APPDATA CSIDL_COMMON_DESKTOPDIRECTORY
- CSIDL_DESKTOPDIRECTORY CSIDL_COMMON_STARTUP CSIDL_STARTUP
- CSIDL_COMMON_PROGRAMS CSIDL_PROGRAMS CSIDL_PROGRAM_FILES_COMMON
- CSIDL_PROGRAM_FILES CSIDL_FONTS""".split():
- if maybe == path_name:
- csidl = getattr(shellcon, maybe)
- return shell.SHGetSpecialFolderPath(
- 0, csidl, False
- )
- raise ValueError(
- f"{path_name} is an unknown path ID"
- )
-
+ try:
+ csidl = getattr(shellcon, path_name)
+ return shell.SHGetSpecialFolderPath(0, csidl, False)
+ except OSError:
+ print(f"{path_name} is an unknown path ID")
def get_winpython_start_menu_folder(current=True):
- """Return WinPython Start menu shortcuts folder"""
- if current:
- # non-admin install - always goes in this user's start menu.
- folder = get_special_folder_path("CSIDL_PROGRAMS")
- else:
+ """Return WinPython Start menu shortcuts folder."""
+ folder = get_special_folder_path("CSIDL_PROGRAMS")
+ if not current:
try:
- folder = get_special_folder_path(
- "CSIDL_COMMON_PROGRAMS"
- )
+ folder = get_special_folder_path("CSIDL_COMMON_PROGRAMS")
except OSError:
- # No CSIDL_COMMON_PROGRAMS on this platform
- folder = get_special_folder_path(
- "CSIDL_PROGRAMS"
- )
+ pass
return str(Path(folder) / 'WinPython')
def remove_winpython_start_menu_folder(current=True):
@@ -143,47 +122,24 @@ def remove_winpython_start_menu_folder(current=True):
try:
shutil.rmtree(path, onexc=onerror)
except WindowsError:
- print(
- f"Directory {path} could not be removed",
- file=sys.stderr,
- )
+ print(f"Directory {path} could not be removed", file=sys.stderr)
def create_winpython_start_menu_folder(current=True):
- """Create WinPython Start menu folder -- remove it if it already exists"""
+ """Create WinPython Start menu folder."""
path = get_winpython_start_menu_folder(current=current)
if Path(path).is_dir():
try:
shutil.rmtree(path, onexc=onerror)
except WindowsError:
- print(
- f"Directory {path} could not be removed",
- file=sys.stderr,
- )
- # create, or re-create !
+ print(f"Directory {path} could not be removed", file=sys.stderr)
Path(path).mkdir(parents=True, exist_ok=True)
return path
-
-def create_shortcut(
- path,
- description,
- filename,
- arguments="",
- workdir="",
- iconpath="",
- iconindex=0,
- verbose=True,
-):
- """Create Windows shortcut (.lnk file)"""
+def create_shortcut(path, description, filename, arguments="", workdir="", iconpath="", iconindex=0, verbose=True):
+ """Create Windows shortcut (.lnk file)."""
import pythoncom
from win32com.shell import shell
-
- ilink = pythoncom.CoCreateInstance(
- shell.CLSID_ShellLink,
- None,
- pythoncom.CLSCTX_INPROC_SERVER,
- shell.IID_IShellLink,
- )
+ ilink = pythoncom.CoCreateInstance(shell.CLSID_ShellLink, None, pythoncom.CLSCTX_INPROC_SERVER, shell.IID_IShellLink)
ilink.SetPath(path)
ilink.SetDescription(description)
if arguments:
@@ -201,135 +157,61 @@ def create_shortcut(
try:
ipf.Save(filename, 0)
except:
- print ("a fail !")
- pass
-
+ print("a fail !")
def print_box(text):
"""Print text in a box"""
line0 = "+" + ("-" * (len(text) + 2)) + "+"
line1 = "| " + text + " |"
- print(
- ("\n\n" + "\n".join([line0, line1, line0]) + "\n")
- )
-
+ print("\n\n" + "\n".join([line0, line1, line0]) + "\n")
def is_python_distribution(path):
- """Return True if path is a Python distribution"""
- # XXX: This test could be improved but it seems to be sufficient
+ """Return True if path is a Python distribution."""
has_exec = Path(get_python_executable(path)).is_file()
- has_site = Path(get_site_packages_path(path)).is_dir()
+ has_site = Path(get_site_packages_path(path)).is_dir()
return has_exec and has_site
-
def decode_fs_string(string):
- """Convert string from file system charset to unicode"""
- charset = sys.getfilesystemencoding()
- if charset is None:
- charset = locale.getpreferredencoding()
+ """Convert string from file system charset to unicode."""
+ charset = sys.getfilesystemencoding() or locale.getpreferredencoding()
return string.decode(charset)
-
def exec_shell_cmd(args, path):
- """Execute shell command (*args* is a list of arguments) in *path*"""
- # print " ".join(args)
- process = subprocess.Popen(
- args,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- cwd=path,
- shell=True
- )
+ """Execute shell command (*args* is a list of arguments) in *path*."""
+ process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=path, shell=True)
return decode_fs_string(process.stdout.read())
def exec_run_cmd(args, path=None):
- """run a single command (*args* is a list of arguments) in optional *path*"""
- # only applicable to Python-3.5+
- # python-3.7+ allows to replace "stdout and stderr ", per "capture_output=True"
- if path:
- process = subprocess.run(args,
- capture_output=True,
- cwd=path, text=True)
- #return decode_fs_string(process.stdout)
- return process.stdout
- else:
- process = subprocess.run(args,
- capture_output=True,
- cwd=path, text=True)
- #return decode_fs_string(process.stdout)
- return process.stdout
-
-
-def get_nodejs_version(path):
- """Return version of the Nodejs installed in *path*"""
- return exec_shell_cmd("node -v", path).splitlines()[0]
-
-
-def get_npmjs_version(path):
- """Return version of the Nodejs installed in *path*"""
- return exec_shell_cmd("npm -v", path).splitlines()[0]
-
-
-def get_pandoc_version(path):
- """Return version of the Pandoc executable in *path*"""
- return exec_shell_cmd("pandoc -v", path).splitlines()[0].split(" ")[-1]
-
+ """Run a single command (*args* is a list of arguments) in optional *path*."""
+ process = subprocess.run(args, capture_output=True, cwd=path, text=True)
+ return process.stdout
def python_query(cmd, path):
- """Execute Python command using the Python interpreter located in *path*"""
+ """Execute Python command using the Python interpreter located in *path*."""
the_exe = get_python_executable(path)
- # debug2021-09-12
- # print(f'"{the_exe}" -c "{cmd}"', ' * ', path)
-
return exec_shell_cmd(f'"{the_exe}" -c "{cmd}"', path).splitlines()[0]
-
def python_execmodule(cmd, path):
- """Execute Python command using the Python interpreter located in *path*"""
+ """Execute Python command using the Python interpreter located in *path*."""
the_exe = get_python_executable(path)
exec_shell_cmd(f'{the_exe} -m {cmd}', path)
-
def get_python_infos(path):
- """Return (version, architecture) for the Python distribution located in
- *path*. The version number is limited to MAJOR.MINOR, the architecture is
- an integer: 32 or 64"""
+ """Return (version, architecture) for the Python distribution located in *path*."""
is_64 = python_query("import sys; print(sys.maxsize > 2**32)", path)
arch = {"True": 64, "False": 32}.get(is_64, None)
- ver = python_query(
- "import sys;print(f'{sys.version_info.major}.{sys.version_info.minor}')",
- path,
- )
- if re.match(r"([0-9]*)\.([0-9]*)", ver) is None:
- ver = None
+ ver = python_query("import sys;print(f'{sys.version_info.major}.{sys.version_info.minor}')", path)
return ver, arch
-
def get_python_long_version(path):
- """Return long version (X.Y.Z) for the Python distribution located in
- *path*"""
- ver = python_query(
- "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}')",
- path,
- )
- if re.match(r"([0-9]*)\.([0-9]*)\.([0-9]*)", ver) is None:
- ver = None
- return ver
-
+ """Return long version (X.Y.Z) for the Python distribution located in *path*."""
+ ver = python_query("import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}')", path)
+ return ver if re.match(r"([0-9]*)\.([0-9]*)\.([0-9]*)", ver) else None
def patch_shebang_line(fname, pad=b" ", to_movable=True, targetdir=""):
- """Remove absolute path to python.exe in shebang lines in binary files, or re-add it"""
-
- import re
- import sys
- import os
-
- target_dir = targetdir # movable option
- if to_movable == False:
- target_dir = os.path.abspath(os.path.dirname(fname))
- target_dir = os.path.abspath(os.path.join(target_dir, r"..")) + "\\"
+ """Remove absolute path to python.exe in shebang lines in binary files, or re-add it."""
+ target_dir = targetdir if to_movable else os.path.abspath(os.path.join(os.path.dirname(fname), r"..")) + "\\"
executable = sys.executable
-
shebang_line = re.compile(rb"""(#!.*pythonw?\.exe)"?""") # Python3+
if "pypy3" in sys.executable:
shebang_line = re.compile(rb"""(#!.*pypy3w?\.exe)"?""") # Pypy3+
@@ -337,8 +219,6 @@ def patch_shebang_line(fname, pad=b" ", to_movable=True, targetdir=""):
with open(fname, "rb") as fh:
initial_content = fh.read()
- fh.close
- fh = None
content = shebang_line.split(initial_content, maxsplit=1)
if len(content) != 3:
return
@@ -350,437 +230,168 @@ def patch_shebang_line(fname, pad=b" ", to_movable=True, targetdir=""):
try:
with open(fname, "wb") as fo:
fo.write(final_content)
- fo.close
- fo = None
print("patched", fname)
except Exception:
print("failed to patch", fname)
-
def patch_shebang_line_py(fname, to_movable=True, targetdir=""):
- """Changes shebang line in '.py' file to relative or absolue path"""
"""Changes shebang line in '.py' file to relative or absolue path"""
import fileinput
- import re
- import sys
-
- if to_movable:
- exec_path = r'#!.\python.exe'
- if 'pypy3' in sys.executable: # PyPy !
- exec_path = r'#!.\pypy3.exe'
- else:
- exec_path = '#!' + sys.executable
+ exec_path = r'#!.\python.exe' if to_movable else '#!' + sys.executable
+ if 'pypy3' in sys.executable:
+ exec_path = r'#!.\pypy3.exe' if to_movable else exec_path
for line in fileinput.input(fname, inplace=True):
- if re.match(r'^#\!.*python\.exe$', line) is not None:
+ if re.match(r'^#\!.*python\.exe$', line) or re.match(r'^#\!.*pypy3\.exe$', line):
print(exec_path)
- elif re.match(r'^#\!.*pypy3\.exe$', line) is not None:# PyPy !
- print(exec_path)
else:
print(line, end='')
-
def guess_encoding(csv_file):
"""guess the encoding of the given file"""
- # UTF_8_BOM = "\xEF\xBB\xBF"
- # Python behavior on UTF-16 not great on write, so we drop it
- with io.open(csv_file, "rb") as f:
+ with open(csv_file, "rb") as f:
data = f.read(5)
if data.startswith(b"\xEF\xBB\xBF"): # UTF-8 with a "BOM" (normally no BOM in utf-8)
return ["utf-8-sig"]
- else: # in Windows, guessing utf-8 doesn't work, so we have to try
- try:
- with io.open(csv_file, encoding="utf-8") as f:
- preview = f.read(222222)
- return ["utf-8"]
- except:
- return [locale.getdefaultlocale()[1], "utf-8"]
-
+ try:
+ with open(csv_file, encoding="utf-8") as f:
+ preview = f.read(222222)
+ return ["utf-8"]
+ except:
+ return [locale.getdefaultlocale()[1], "utf-8"]
-def patch_sourcefile(fname, in_text, out_text, silent_mode=False):
- """Replace a string in a source file"""
- import io
-
- if Path(fname).is_file() and not in_text == out_text:
- the_encoding = guess_encoding(fname)[0]
- with io.open(fname, 'r', encoding=the_encoding) as fh:
- content = fh.read()
- new_content = content.replace(in_text, out_text)
- if not new_content == content:
- if not silent_mode:
- print(
- "patching ",
- fname,
- "from",
- in_text,
- "to",
- out_text,
- )
- with io.open(fname, 'wt', encoding=the_encoding) as fh:
- fh.write(new_content)
-
-
-def patch_sourcelines(
- fname,
- in_line_start,
- out_line,
- endline="\n",
- silent_mode=False,
-):
- """Replace the middle of lines between in_line_start and endline"""
- import io
-
- if Path(fname).is_file():
- the_encoding = guess_encoding(fname)[0]
- with io.open(fname, "r", encoding=the_encoding) as fh:
- contents = fh.readlines()
- content = "".join(contents)
- for l in range(len(contents)):
- if contents[l].startswith(in_line_start):
- begining, middle = (
- in_line_start,
- contents[l][len(in_line_start) :],
- )
- ending = ""
- if middle.find(endline) > 0:
- ending = endline + endline.join(middle.split(endline)[1:])
- middle = middle.split(endline)[0]
- middle = out_line
- new_line = begining + middle + ending
- if not new_line == contents[l]:
- if not silent_mode:
- print(
- "patching ",
- fname,
- " from\n",
- contents[l],
- "\nto\n",
- new_line,
- )
- contents[l] = new_line
- new_content = "".join(contents)
- if not new_content == content:
- # if not silent_mode:
- # print("patching ", fname, "from", content, "to", new_content)
-
- with io.open(fname, "wt", encoding=the_encoding) as fh:
- try:
- fh.write(new_content)
- except:
- print(
- "impossible to patch",
- fname,
- "from",
- content,
- "to",
- new_content,
- )
-
-
-def _create_temp_dir():
- """Create a temporary directory and remove it at exit"""
- tmpdir = tempfile.mkdtemp(prefix='wppm_')
- atexit.register(
- lambda path: shutil.rmtree(path, onexc=onerror),
- tmpdir,
- )
- return tmpdir
+def replace_in_file(filepath: Path, replacements: list[tuple[str, str]], filedest: Path = None, verbose=False):
+ """
+ Replaces strings in a file
+ Args:
+ filepath: Path to the file to modify.
+ replacements: A list of tuples of ('old string 'new string')
+ filedest: optional output file, otherwise will be filepath
+ """
+ the_encoding = guess_encoding(filepath)[0]
+ with open(filepath, "r", encoding=the_encoding) as f:
+ content = f.read()
+ new_content = content
+ for old_text, new_text in replacements:
+ new_content = new_content.replace(old_text, new_text)
+ outfile = filedest if filedest else filepath
+ if new_content != content or str(outfile) != str(filepath):
+ with open(outfile, "w", encoding=the_encoding) as f:
+ f.write(new_content)
+ if verbose:
+ print(f"patched from {Path(filepath).name} into {outfile} !")
+def patch_sourcefile(fname, in_text, out_text, silent_mode=False):
+ """Replace a string in a source file."""
+ if not silent_mode:
+ print(f"patching {fname} from {in_text} to {out_text}")
+ if Path(fname).is_file() and in_text != out_text:
+ replace_in_file(Path(fname), [(in_text, out_text)])
def extract_archive(fname, targetdir=None, verbose=False):
- """Extract .zip, .exe (considered to be a zip archive) or .tar.gz archive
- to a temporary directory (if targetdir is None).
+ """Extract .zip, .exe or .tar.gz archive to a temporary directory.
Return the temporary directory path"""
- if targetdir is None:
- targetdir = _create_temp_dir()
- else:
- try:
- Path(targetdir).mkdir(parents=True, exist_ok=True)
- except:
- pass
+ targetdir = targetdir or create_temp_dir()
+ Path(targetdir).mkdir(parents=True, exist_ok=True)
if Path(fname).suffix in ('.zip', '.exe'):
obj = zipfile.ZipFile(fname, mode="r")
elif fname.endswith('.tar.gz'):
obj = tarfile.open(fname, mode='r:gz')
else:
- raise RuntimeError(
- f"Unsupported archive filename {fname}"
- )
+ raise RuntimeError(f"Unsupported archive filename {fname}")
obj.extractall(path=targetdir)
return targetdir
-# SOURCE_PATTERN defines what an acceptable source package name is
-# As of 2014-09-08 :
-# - the wheel package format is accepte in source directory
-# - the tricky regexp is tuned also to support the odd jolib naming :
-# . joblib-0.8.3_r1-py2.py3-none-any.whl,
-# . joblib-0.8.3-r1.tar.gz
-
-SOURCE_PATTERN = r'([a-zA-Z0-9\-\_\.]*)-([0-9\.\_]*[a-z]*[\-]?[0-9]*)(\.zip|\.tar\.gz|\-(py[2-7]*|py[2-7]*\.py[2-7]*)\-none\-any\.whl)'
-
-# WHEELBIN_PATTERN defines what an acceptable binary wheel package is
-# "cp([0-9]*)" to replace per cp(34) for python3.4
-# "win32|win\_amd64" to replace per "win\_amd64" for 64bit
-WHEELBIN_PATTERN = r'([a-zA-Z0-9\-\_\.]*)-([0-9\.\_]*[a-z0-9\+]*[0-9]?)-cp([0-9]*)\-[0-9|c|o|n|e|p|m]*\-(win32|win\_amd64)\.whl'
-
-
def get_source_package_infos(fname):
- """Return a tuple (name, version) of the Python source package"""
- if fname[-4:] == '.whl':
+ """Return a tuple (name, version) of the Python source package."""
+ if fname.endswith('.whl'):
return Path(fname).name.split("-")[:2]
match = re.match(SOURCE_PATTERN, Path(fname).name)
- if match is not None:
- return match.groups()[:2]
-
-
-def buildflit_wininst(
- root,
- python_exe=None,
- copy_to=None,
- verbose=False,
-):
- """Build Wheel from Python package located in *root*
- with flit"""
- if python_exe is None:
- python_exe = sys.executable
- assert Path(python_exe).is_file()
- cmd = [python_exe, '-m' ,'flit', 'build']
-
- # root = a tmp dir in windows\tmp,
+ return match.groups()[:2] if match else None
+
+def buildflit_wininst(root, python_exe=None, copy_to=None, verbose=False):
+ """Build Wheel from Python package located in *root* with flit."""
+ python_exe = python_exe or sys.executable
+ cmd = [python_exe, '-m', 'flit', 'build']
if verbose:
subprocess.call(cmd, cwd=root)
else:
- p = subprocess.Popen(
- cmd,
- cwd=root,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- )
- p.communicate()
- p.stdout.close()
- p.stderr.close()
- distdir = str(Path(root) / 'dist')
- if not Path(distdir).is_dir():
+ subprocess.Popen(cmd, cwd=root, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
+ distdir = Path(root) / 'dist'
+ if not distdir.is_dir():
raise RuntimeError(
- "Build failed: see package README file for further"
- " details regarding installation requirements.\n\n"
- "For more concrete debugging infos, please try to build "
- "the package from the command line:\n"
+ "Build failed: see package README file for further details regarding installation requirements.\n\n"
+ "For more concrete debugging infos, please try to build the package from the command line:\n"
"1. Open a WinPython command prompt\n"
"2. Change working directory to the appropriate folder\n"
- "3. Type `python -m filt build`"
+ "3. Type `python -m flit build`"
)
-
for distname in os.listdir(distdir):
- # for wheels (winpython here)
- match = re.match(SOURCE_PATTERN, distname)
- if match is not None:
- break
- match = re.match(WHEELBIN_PATTERN, distname)
- if match is not None:
+ if re.match(SOURCE_PATTERN, distname) or re.match(WHEELBIN_PATTERN, distname):
break
else:
- raise RuntimeError(
- f"Build failed: not a pure Python package? {distdir}"
- )
- src_fname = str(Path(distdir) / distname)
- if copy_to is None:
- return src_fname
- else:
- dst_fname = str(Path(copy_to) / distname)
+ raise RuntimeError(f"Build failed: not a pure Python package? {distdir}")
+
+ src_fname = distdir / distname
+ if copy_to:
+ dst_fname = Path(copy_to) / distname
shutil.move(src_fname, dst_fname)
if verbose:
- print(
- (
- f"Move: {src_fname} --> {dst_fname}"
- )
- )
- # remove tempo dir 'root' no more needed
- #try:
- # shutil.rmtree(root, onexc=onerror)
- #except TypeError: # before 3.12
- # shutil.rmtree(root, onerror=onerror)
- return dst_fname
-
-
-def direct_pip_install(
- fname,
- python_exe=None,
- verbose=False,
- install_options=None,
-):
- """Direct install via python -m pip !"""
- copy_to = str(Path(fname).parent)
-
- if python_exe is None:
- python_exe = sys.executable
- assert Path(python_exe).is_file()
- myroot = str(Path(python_exe).parent)
-
- cmd = [python_exe, "-m", "pip", "install"]
- if install_options:
- cmd += install_options # typically ['--no-deps']
- print("python -m pip install_options", install_options)
- cmd += [fname]
-
- if verbose:
- subprocess.call(cmd, cwd=myroot)
- else:
- p = subprocess.Popen(
- cmd,
- cwd=myroot,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- )
- stdout, stderr = p.communicate()
- the_log = f"{stdout}" + f"\n {stderr}"
+ print(f"Move: {src_fname} --> {dst_fname}")
+def direct_pip_install(fname, python_exe=None, verbose=False, install_options=None):
+ """Direct install via python -m pip !"""
+ python_exe = python_exe or sys.executable
+ myroot = Path(python_exe).parent
+ cmd = [python_exe, "-m", "pip", "install"] + (install_options or []) + [fname]
+ if not verbose:
+ process = subprocess.Popen(cmd, cwd=myroot, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ stdout, stderr = process.communicate()
+ the_log = f"{stdout}\n {stderr}"
if " not find " in the_log or " not found " in the_log:
- print(f"Failed to Install: \n {fname} \n")
- print(f"msg: {the_log}")
+ print(f"Failed to Install: \n {fname} \n msg: {the_log}")
raise RuntimeError
- p.stdout.close()
- p.stderr.close()
- src_fname = fname
- if copy_to is None:
- return src_fname
+ process.stdout.close()
+ process.stderr.close()
else:
- if verbose:
- print(f"Installed {src_fname}")
- return src_fname
-
-
-def do_script(
- this_script,
- python_exe=None,
- copy_to=None,
- verbose=False,
- install_options=None,
-):
- """Execute a script (get-pip typically)"""
- if python_exe is None:
- python_exe = sys.executable
- myroot = os.path.dirname(python_exe)
+ subprocess.call(cmd, cwd=myroot)
+ print(f"Installed {fname} via {' '.join(cmd)}")
+ return fname
+def do_script(this_script, python_exe=None, copy_to=None, verbose=False, install_options=None):
+ """Execute a script (get-pip typically)."""
+ python_exe = python_exe or sys.executable
+ myroot = Path(python_exe).parent
# cmd = [python_exe, myroot + r'\Scripts\pip-script.py', 'install']
- cmd = [python_exe]
- if install_options:
- cmd += install_options # typically ['--no-deps']
- print('script install_options', install_options)
- if this_script:
- cmd += [this_script]
- # print('build_wheel', myroot, cmd)
+ cmd = [python_exe] + (install_options or []) + ([this_script] if this_script else [])
print("Executing ", cmd)
-
- if verbose:
- subprocess.call(cmd, cwd=myroot)
+ if not verbose:
+ subprocess.Popen(cmd, cwd=myroot, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
else:
- p = subprocess.Popen(
- cmd,
- cwd=myroot,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- )
- p.communicate()
- p.stdout.close()
- p.stderr.close()
- if verbose:
- print("Executed " , cmd)
+ subprocess.call(cmd, cwd=myroot)
+ print("Executed ", cmd)
return 'ok'
def columns_width(list_of_lists):
- """return the maximum string length of each column of a list of list"""
- if not isinstance(list_of_lists, list):
- return [0]
-
- # Transpose the list of lists using zip
- transposed_lists = list(zip(*list_of_lists))
- # Calculate the maximum width for each column
- column_widths = [max(len(str(item)) for item in sublist) for sublist in transposed_lists]
- return column_widths
+ """Return the maximum string length of each column of a list of lists."""
+ if not isinstance(list_of_lists, list):
+ return [0]
+ return [max(len(str(item)) for item in sublist) for sublist in zip(*list_of_lists)]
def formatted_list(list_of_list, full=False, max_width=70):
- """format a list_of_list to fix length columns"""
- columns_size = columns_width(list_of_list)
- nb_columns = len(columns_size)
-
- # normalize each columns to columns_size[col] width, in the limit of max_width
-
- zz = [
- list(
- line[col].ljust(columns_size[col])[:max_width] for col in range(nb_columns)
- )
- for line in list_of_list
- ]
- return zz
+ """Format a list_of_list to fixed length columns."""
+ columns_size = columns_width(list_of_list)
+ columns = range(len(columns_size))
+ return [list(line[col].ljust(columns_size[col])[:max_width] for col in columns) for line in list_of_list]
def normalize(this):
- """apply https://peps.python.org/pep-0503/#normalized-names"""
+ """Apply PEP 503 normalization to the string."""
return re.sub(r"[-_.]+", "-", this).lower()
-def get_package_metadata(database, name, update=False, suggested_summary=None):
- """Extract infos (description, url) from the local database"""
- # for package.ini safety belt
- # Note: we could use the PyPI database but this has been written on
- # machine which is not connected to the internet
- # we store only normalized names now (PEP 503)
- DATA_PATH = str(Path(sys.modules['winpython'].__file__).parent /'data')
- db = cp.ConfigParser()
- filepath = Path(database) if Path(database).is_absolute() else Path(DATA_PATH) / database
- try:
- db.read_file(open(str(filepath), encoding = 'utf-8'))
- except:
- db.read_file(open(str(filepath)))
- my_metadata = dict(
- description="",
- url="https://pypi.org/project/" + name,
- )
- for key in my_metadata:
- # wheel replace '-' per '_' in key
- for name2 in (name, normalize(name)):
- try:
- my_metadata[key] = db.get(name2, key)
- break
- except (cp.NoSectionError, cp.NoOptionError):
- pass
- db_desc = my_metadata.get("description")
-
- if my_metadata.get("description") == "" and suggested_summary:
- # nothing in package.ini, we look in our installed packages
- try:
- my_metadata["description"] = (
- suggested_summary + "\n"
- ).splitlines()[0]
- except:
- pass
-
- if update == True and db_desc == "" and my_metadata["description"] != "":
- # we add new findings in our packgages.ini list, if it's required
- try:
- db[normalize(name)] = {}
- db[normalize(name)]["description"] = my_metadata["description"]
- with open(str(Path(DATA_PATH) / database), "w", encoding='UTF-8') as configfile:
- db.write(configfile)
- except:
- pass
- return my_metadata
-
-
if __name__ == '__main__':
-
print_box("Test")
dname = sys.prefix
print((dname + ':', '\n', get_python_infos(dname)))
- # dname = r'E:\winpython\sandbox\python-2.7.3'
- # print dname+':', '\n', get_python_infos(dname)
tmpdir = r'D:\Tests\winpython_tests'
- Path(tmpdir).mkdir(parents=True, exist_ok=True)
- print(
- (
- extract_archive(
- str(Path(r'D:\WinP\bd37') / 'packages.win-amd64' /
- 'python-3.7.3.amd64.zip'),
- tmpdir,
- )
- )
- )
+ Path(tmpdir).mkdir(parents=True, exist_ok=True)
+ print(extract_archive(str(Path(r'D:\WinP\bd37') / 'packages.win-amd64' / 'python-3.7.3.amd64.zip'), tmpdir))
diff --git a/winpython/wppm.py b/winpython/wppm.py
index e825d9c8..659d14a4 100644
--- a/winpython/wppm.py
+++ b/winpython/wppm.py
@@ -1,247 +1,133 @@
# -*- coding: utf-8 -*-
#
+# WinPython Package Manager
# Copyright © 2012 Pierre Raybaut
+# Copyright © 2014-2025+ The Winpython development team https://github.com/winpython/
# Licensed under the terms of the MIT License
# (see winpython/__init__.py for details)
-"""
-WinPython Package Manager
-
-Created on Fri Aug 03 14:32:26 2012
-"""
-
import os
-from pathlib import Path
-import shutil
import re
import sys
+import shutil
import subprocess
import json
+from pathlib import Path
from argparse import ArgumentParser, RawTextHelpFormatter
-
-# Local imports
-from winpython import utils, piptree
-
+from winpython import utils, piptree, associate
# Workaround for installing PyVISA on Windows from source:
os.environ["HOME"] = os.environ["USERPROFILE"]
class Package:
- "standardize a Package from filename or pip list"
- def __init__(self, fname, suggested_summary=None):
+ """Standardize a Package from filename or pip list."""
+ def __init__(self, fname: str, suggested_summary: str = None):
self.fname = fname
- self.description = piptree.sum_up(suggested_summary) if suggested_summary else ""
- self.name = None
- self.version = None
- if fname.endswith((".zip", ".tar.gz", ".whl")):
- bname = Path(self.fname).name #wheel style name like "sqlite_bro-1.0.0..."
+ self.description = piptree.sum_up(suggested_summary) if suggested_summary else ""
+ self.name, self.version = None, None
+ if fname.lower().endswith((".zip", ".tar.gz", ".whl")):
+ bname = Path(self.fname).name # e.g., "sqlite_bro-1.0.0..."
infos = utils.get_source_package_infos(bname) # get name, version
- if infos is not None:
- self.name, self.version = infos
- self.name = utils.normalize(self.name)
- self.url = None
+ if infos:
+ self.name, self.version = utils.normalize(infos[0]), infos[1]
+ self.url = f"https://pypi.org/project/{self.name}"
self.files = []
- setattr(self,'url',"https://pypi.org/project/" + self.name)
-
def __str__(self):
return f"{self.name} {self.version}\r\n{self.description}\r\nWebsite: {self.url}"
-
+
class Distribution:
- def __init__(self, target=None, verbose=False, indent=False):
- # if no target path given, take the current python interpreter one
- self.target = target or os.path.dirname(sys.executable)
+ """Handles operations on a WinPython distribution."""
+ def __init__(self, target: str = None, verbose: bool = False):
+ self.target = target or str(Path(sys.executable).parent) # Default target more explicit
self.verbose = verbose
- self.indent = indent
self.pip = None
- self.to_be_removed = [] # list of directories to be removed later
- self.version, self.architecture = utils.get_python_infos(target)
- # name of the exe (python.exe or pypy3.exe)
- self.short_exe = Path(utils.get_python_executable(self.target)).name
-
- def clean_up(self):
- """Remove directories which couldn't be removed when building"""
- for path in self.to_be_removed:
- try:
- shutil.rmtree(path, onexc=utils.onerror)
- except WindowsError:
- print(f"Directory {path} could not be removed", file=sys.stderr)
-
- def remove_directory(self, path):
- """Try to remove directory -- on WindowsError, remove it later"""
- try:
- shutil.rmtree(path)
- except WindowsError:
- self.to_be_removed.append(path)
-
- def copy_files(
- self,
- package,
- targetdir,
- srcdir,
- dstdir,
- create_bat_files=False,
- ):
- """Add copy task"""
- srcdir = str(Path(targetdir) / srcdir)
- if not Path(srcdir).is_dir():
- return
- offset = len(srcdir) + len(os.pathsep)
- for dirpath, dirnames, filenames in os.walk(srcdir):
- for dname in dirnames:
- t_dname = str(Path(dirpath) / dname)[offset:]
- src = str(Path(srcdir) / t_dname)
- dst = str(Path(dstdir) / t_dname)
- if self.verbose:
- print(f"mkdir: {dst}")
- full_dst = str(Path(self.target) / dst)
- if not Path(full_dst).exists():
- os.mkdir(full_dst)
- package.files.append(dst)
- for fname in filenames:
- t_fname = str(Path(dirpath) / fname)[offset:]
- src = str(Path(srcdir) / t_fname)
- dst = fname if dirpath.endswith("_system32") else str(Path(dstdir) / t_fname)
- if self.verbose:
- print(f"file: {dst}")
- full_dst = str(Path(self.target) / dst)
- shutil.move(src, full_dst)
- package.files.append(dst)
- name, ext = Path(dst).stem, Path(dst).suffix
- if create_bat_files and ext in ("", ".py"):
- dst = name + ".bat"
- if self.verbose:
- print(f"file: {dst}")
- full_dst = str(Path(self.target) / dst)
- fd = open(full_dst, "w")
- fd.write(f"""@echo off\npython "%~dpn0{ext}" %*""")
- fd.close()
- package.files.append(dst)
+ self.to_be_removed = []
+ self.version, self.architecture = utils.get_python_infos(self.target)
+ self.python_exe = utils.get_python_executable(self.target)
+ self.short_exe = Path(self.python_exe).name
def create_file(self, package, name, dstdir, contents):
"""Generate data file -- path is relative to distribution root dir"""
- dst = str(Path(dstdir) / name)
+ dst = Path(dstdir) / name
if self.verbose:
print(f"create: {dst}")
- full_dst = str(Path(self.target) / dst)
+ full_dst = Path(self.target) / dst
with open(full_dst, "w") as fd:
fd.write(contents)
- package.files.append(dst)
-
- def get_installed_packages(self, update=False):
- """Return installed packages"""
+ package.files.append(str(dst))
- # Include package installed via pip (not via WPPM)
- wppm = []
+ def get_installed_packages(self, update: bool = False) -> list[Package]:
+ """Return installed packages."""
if str(Path(sys.executable).parent) == self.target:
self.pip = piptree.PipData()
else:
self.pip = piptree.PipData(utils.get_python_executable(self.target))
- pip_list = self.pip.pip_list()
-
- # create pip package list
- wppm = [
- Package(
- f"{i[0].replace('-', '_').lower()}-{i[1]}-py3-none-any.whl", #faking wheel
- suggested_summary=self.pip.summary(i[0]) if self.pip else None
- )
- for i in pip_list
+ pip_list = self.pip.pip_list(full=True)
+ return [Package(f"{i[0].replace('-', '_').lower()}-{i[1]}-py3-none-any.whl", suggested_summary=i[2]) for i in pip_list]
+
+ def get_installed_packages_markdown(self) -> str:
+ """Generates Markdown for installed packages section in package index."""
+ package_lines = [
+ f"[{pkg.name}]({pkg.url}) | {pkg.version} | {pkg.description}"
+ for pkg in sorted(self.get_installed_packages(), key=lambda p: p.name.lower())
]
- return sorted(wppm, key=lambda tup: tup.name.lower())
+ return "\n".join(package_lines)
- def find_package(self, name):
- """Find installed package"""
+ def find_package(self, name: str) -> Package | None:
+ """Find installed package by name."""
for pack in self.get_installed_packages():
if utils.normalize(pack.name) == utils.normalize(name):
return pack
- def patch_all_shebang(
- self,
- to_movable=True,
- max_exe_size=999999,
- targetdir="",
- ):
- """make all python launchers relatives"""
- import glob
-
- for ffname in glob.glob(r"%s\Scripts\*.exe" % self.target):
- size = os.path.getsize(ffname)
- if size <= max_exe_size:
- utils.patch_shebang_line(
- ffname,
- to_movable=to_movable,
- targetdir=targetdir,
- )
- for ffname in glob.glob(r"%s\Scripts\*.py" % self.target):
- utils.patch_shebang_line_py(
- ffname,
- to_movable=to_movable,
- targetdir=targetdir,
- )
-
- def install(self, package, install_options=None):
- """Install package in distribution"""
- # wheel addition
- if package.fname.endswith((".whl", ".tar.gz", ".zip")):
+ def patch_all_shebang(self, to_movable: bool = True, max_exe_size: int = 999999, targetdir: str = ""):
+ """Make all python launchers relative."""
+ for ffname in Path(self.target).glob("Scripts/*.exe"):
+ if ffname.stat().st_size <= max_exe_size:
+ utils.patch_shebang_line(ffname, to_movable=to_movable, targetdir=targetdir)
+ for ffname in Path(self.target).glob("Scripts/*.py"):
+ utils.patch_shebang_line_py(ffname, to_movable=to_movable, targetdir=targetdir)
+
+ def install(self, package: Package, install_options: list[str] = None):
+ """Install package in distribution."""
+ if package.fname.endswith((".whl", ".tar.gz", ".zip")): # Check extension with tuple
self.install_bdist_direct(package, install_options=install_options)
self.handle_specific_packages(package)
# minimal post-install actions
self.patch_standard_packages(package.name)
- def do_pip_action(self, actions=None, install_options=None):
- """Do pip action in a distribution"""
+ def do_pip_action(self, actions: list[str] = None, install_options: list[str] = None):
+ """Execute pip action in the distribution."""
my_list = install_options or []
my_actions = actions or []
executing = str(Path(self.target).parent / "scripts" / "env.bat")
if Path(executing).is_file():
- complement = [
- r"&&",
- "cd",
- "/D",
- self.target,
- r"&&",
- utils.get_python_executable(self.target),
- # Before PyPy: osp.join(self.target, 'python.exe')
- ]
- complement += ["-m", "pip"]
+ complement = [r"&&", "cd", "/D", self.target, r"&&", utils.get_python_executable(self.target), "-m", "pip"]
else:
executing = utils.get_python_executable(self.target)
- # Before PyPy: osp.join(self.target, 'python.exe')
complement = ["-m", "pip"]
try:
- fname = utils.do_script(
- this_script=None,
- python_exe=executing,
- verbose=self.verbose,
- install_options=complement + my_actions + my_list,
- )
- except RuntimeError:
+ fname = utils.do_script(this_script=None, python_exe=executing, verbose=self.verbose, install_options=complement + my_actions + my_list)
+ except RuntimeError as e:
if not self.verbose:
print("Failed!")
raise
+ else:
+ print(f"Pip action failed with error: {e}") # Print error if verbose
def patch_standard_packages(self, package_name="", to_movable=True):
"""patch Winpython packages in need"""
import filecmp
- # Adpating to PyPy
- if "pypy3" in Path(utils.get_python_executable(self.target)).name:
- site_package_place = "\\site-packages\\"
- else:
- site_package_place = "\\Lib\\site-packages\\"
# 'pywin32' minimal post-install (pywin32_postinstall.py do too much)
- if package_name.lower() == "pywin32" or package_name == "":
- origin = self.target + site_package_place + "pywin32_system32"
-
- destin = self.target
- if Path(origin).is_dir():
+ if package_name.lower() in ("", "pywin32"):
+ origin = Path(self.target) / "site-packages" / "pywin32_system32"
+ destin = Path(self.target)
+ if origin.is_dir():
for name in os.listdir(origin):
- here, there = (
- str(Path(origin) / name),
- str(Path(destin) / name),
- )
- if not Path(there).exists() or not filecmp.cmp(here, there):
+ here, there = origin / name, destin / name
+ if not there.exists() or not filecmp.cmp(here, there):
shutil.copyfile(here, there)
# 'pip' to do movable launchers (around line 100) !!!!
# rational: https://github.com/pypa/pip/issues/2328
@@ -249,96 +135,36 @@ def patch_standard_packages(self, package_name="", to_movable=True):
# ensure pip will create movable launchers
# sheb_mov1 = classic way up to WinPython 2016-01
# sheb_mov2 = tried way, but doesn't work for pip (at least)
+ the_place = Path(self.target) / "lib" / "site-packages" / "pip" / "_vendor" / "distlib" / "scripts.py"
sheb_fix = " executable = get_executable()"
sheb_mov1 = " executable = os.path.join(os.path.basename(get_executable()))"
- sheb_mov2 = (
- " executable = os.path.join('..',os.path.basename(get_executable()))"
- )
-
- # Adpating to PyPy
- the_place = site_package_place + r"pip\_vendor\distlib\scripts.py"
- print(the_place)
+ sheb_mov2 = " executable = os.path.join('..',os.path.basename(get_executable()))"
if to_movable:
- utils.patch_sourcefile(self.target + the_place, sheb_fix, sheb_mov1)
- utils.patch_sourcefile(self.target + the_place, sheb_mov2, sheb_mov1)
+ utils.patch_sourcefile(the_place, sheb_fix, sheb_mov1)
+ utils.patch_sourcefile(the_place, sheb_mov2, sheb_mov1)
else:
- utils.patch_sourcefile(self.target + the_place, sheb_mov1, sheb_fix)
- utils.patch_sourcefile(self.target + the_place, sheb_mov2, sheb_fix)
+ utils.patch_sourcefile(the_place, sheb_mov1, sheb_fix)
+ utils.patch_sourcefile(the_place, sheb_mov2, sheb_fix)
# create movable launchers for previous package installations
self.patch_all_shebang(to_movable=to_movable)
- if package_name.lower() == "spyder" or package_name == "":
+ if package_name.lower() in ("", "spyder"):
# spyder don't goes on internet without I ask
utils.patch_sourcefile(
- self.target + (site_package_place + r"spyderlib\config\main.py"),
- "'check_updates_on_startup': True,",
- "'check_updates_on_startup': False,",
- )
- utils.patch_sourcefile(
- self.target + (site_package_place + r"spyder\config\main.py"),
+ Path(self.target) / "lib" / "site-packages" / "spyder" / "config" / "main.py",
"'check_updates_on_startup': True,",
"'check_updates_on_startup': False,",
)
- # workaround bad installers
- if package_name.lower() == "numba":
- self.create_pybat(["numba"])
- else:
- self.create_pybat(package_name.lower())
-
- def create_pybat(
- self,
- names="",
- contents=r"""@echo off
-..\python "%~dpn0" %*""",
- ):
- """Create launcher batch script when missing"""
- scriptpy = str(Path(self.target) / "Scripts") # std Scripts of python
-
- # PyPy has no initial Scipts directory
- if not Path(scriptpy).is_dir():
- os.mkdir(scriptpy)
- if not list(names) == names:
- my_list = [f for f in os.listdir(scriptpy) if "." not in f and f.startswith(names)]
- else:
- my_list = names
- for name in my_list:
- if Path(scriptpy).is_dir() and (Path(scriptpy) / name).is_file():
- if (
- not (Path(scriptpy) / (name + ".exe")).is_file()
- and not (Path(scriptpy) / (name + ".bat")).is_file()
- ):
- with open(Path(scriptpy) / (name + ".bat"), "w") as fd:
- fd.write(contents)
- fd.close()
def handle_specific_packages(self, package):
"""Packages requiring additional configuration"""
- if package.name.lower() in (
- "pyqt4",
- "pyqt5",
- "pyside2",
- ):
+ if package.name.lower() in ("pyqt4", "pyqt5", "pyside2"):
# Qt configuration file (where to find Qt)
name = "qt.conf"
- contents = """[Paths]
-Prefix = .
-Binaries = ."""
- self.create_file(
- package,
- name,
- str(Path("Lib") / "site-packages" / package.name),
- contents,
- )
- self.create_file(
- package,
- name,
- ".",
- contents.replace(
- ".",
- f"./Lib/site-packages/{package.name}",
- ),
- )
+ contents = """[Paths]\nPrefix = .\nBinaries = ."""
+ self.create_file(package, name, str(Path("Lib") / "site-packages" / package.name), contents)
+ self.create_file(package, name, ".", contents.replace(".", f"./Lib/site-packages/{package.name}"))
# pyuic script
if package.name.lower() == "pyqt5":
# see http://code.activestate.com/lists/python-list/666469/
@@ -351,34 +177,20 @@ def handle_specific_packages(self, package):
"%WINPYDIR%\python.exe" "%WINPYDIR%\Lib\site-packages\package.name\uic\pyuic.py" %1 %2 %3 %4 %5 %6 %7 %8 %9"""
# PyPy adaption: python.exe or pypy3.exe
my_exec = Path(utils.get_python_executable(self.target)).name
- tmp_string = tmp_string.replace("python.exe", my_exec)
-
- self.create_file(
- package,
- f"pyuic{package.name[-1]}.bat",
- "Scripts",
- tmp_string.replace("package.name", package.name),
- )
+ tmp_string = tmp_string.replace("python.exe", my_exec).replace("package.name", package.name)
+ self.create_file(package, f"pyuic{package.name[-1]}.bat", "Scripts", tmp_string)
# Adding missing __init__.py files (fixes Issue 8)
uic_path = str(Path("Lib") / "site-packages" / package.name / "uic")
for dirname in ("Loader", "port_v2", "port_v3"):
- self.create_file(
- package,
- "__init__.py",
- str(Path(uic_path) / dirname),
- "",
- )
+ self.create_file(package, "__init__.py", str(Path(uic_path) / dirname), "")
- def _print(self, package, action):
- """Print package-related action text (e.g. 'Installing')
- indicating progress"""
- text = " ".join([action, package.name, package.version])
+ def _print(self, package: Package, action: str):
+ """Print package-related action text."""
+ text = f"{action} {package.name} {package.version}"
if self.verbose:
utils.print_box(text)
else:
- if self.indent:
- text = (" " * 4) + text
- print(text + "...", end=" ")
+ print(f" {text}...", end=" ")
def _print_done(self):
"""Print OK at the end of a process"""
@@ -388,7 +200,7 @@ def _print_done(self):
def uninstall(self, package):
"""Uninstall package from distribution"""
self._print(package, "Uninstalling")
- if not package.name == "pip":
+ if package.name != "pip":
# trick to get true target (if not current)
this_exec = utils.get_python_executable(self.target) # PyPy !
subprocess.call([this_exec, "-m", "pip", "uninstall", package.name, "-y"], cwd=self.target)
@@ -411,232 +223,126 @@ def install_bdist_direct(self, package, install_options=None):
package = Package(fname)
self._print_done()
- def install_script(self, script, install_options=None):
- try:
- fname = utils.do_script(
- script,
- python_exe=utils.get_python_executable(self.target), # PyPy3 !
- verbose=self.verbose,
- install_options=install_options,
- )
- except RuntimeError:
- if not self.verbose:
- print("Failed!")
- raise
-
-
def main(test=False):
- if test:
- sbdir = str(Path(__file__).parents[0].parent.parent.parent / "sandbox")
- tmpdir = str(Path(sbdir) / "tobedeleted")
- fname = str(Path(sbdir) / "VTK-5.10.0-Qt-4.7.4.win32-py2.7.exe")
- print(Package(fname))
+ registerWinPythonHelp = f"Register WinPython: associate file extensions, icons and context menu with this WinPython"
+ unregisterWinPythonHelp = f"Unregister WinPython: de-associate file extensions, icons and context menu from this WinPython"
+ parser = ArgumentParser(
+ description="WinPython Package Manager: handle a WinPython Distribution and its packages",
+ formatter_class=RawTextHelpFormatter,
+ )
+ parser.add_argument("fname", metavar="package", nargs="?", default="", type=str, help="optional package name or package wheel")
+ parser.add_argument("-v", "--verbose", action="store_true", help="show more details on packages and actions")
+ parser.add_argument( "--register", dest="registerWinPython", action="store_true", help=registerWinPythonHelp)
+ # parser.add_argument( "--register_forall", action="store_true", help="Register distribution for all users")
+ parser.add_argument("--unregister", dest="unregisterWinPython", action="store_true", help=unregisterWinPythonHelp)
+ # parser.add_argument( "--unregister_forall", action="store_true", help="un-Register distribution for all users")
+ parser.add_argument("--fix", action="store_true", help="make WinPython fix")
+ parser.add_argument("--movable", action="store_true", help="make WinPython movable")
+ parser.add_argument("-ls", "--list", action="store_true", help="list installed packages matching the given [optional] package expression: wppm -ls, wppm -ls pand")
+ parser.add_argument("-lsa", dest="all", action="store_true",help=f"list details of package names matching given regular expression: wppm -lsa pandas -l1")
+ parser.add_argument("-p",dest="pipdown",action="store_true",help="show Package dependencies of the given package[option]: wppm -p pandas[test]")
+ parser.add_argument("-r", dest="pipup", action="store_true", help=f"show Reverse dependancies of the given package[option]: wppm -r pytest[test]")
+ parser.add_argument("-l", "--levels", type=int, default=2, help="show 'LEVELS' levels of dependencies (with -p, -r), default is 2: wppm -p pandas -l1")
+ parser.add_argument("-t", "--target", default=sys.prefix, help=f'path to target Python distribution (default: "{sys.prefix}")')
+ parser.add_argument("-i", "--install", action="store_true", help="install a given package wheel (use pip for more features)")
+ parser.add_argument("-u", "--uninstall", action="store_true", help="uninstall package (use pip for more features)")
+
+
+ args = parser.parse_args()
+ targetpython = None
+ if args.target and args.target != sys.prefix:
+ targetpython = args.target if args.target.lower().endswith('.exe') else str(Path(args.target) / 'python.exe')
+ if args.install and args.uninstall:
+ raise RuntimeError("Incompatible arguments: --install and --uninstall")
+ if args.registerWinPython and args.unregisterWinPython:
+ raise RuntimeError("Incompatible arguments: --install and --uninstall")
+ if args.pipdown:
+ pip = piptree.PipData(targetpython)
+ pack, extra, *other = (args.fname + "[").replace("]", "[").split("[")
+ print(pip.down(pack, extra, args.levels, verbose=args.verbose))
sys.exit()
- target = str(
- Path(utils.BASE_DIR) / "build" / "winpython-2.7.3" / "python-2.7.3"
- )
- fname = str(Path(utils.BASE_DIR) / "packages.src" / "docutils-0.9.1.tar.gz")
-
- dist = Distribution(target, verbose=True)
- pack = Package(fname)
- print(pack.description)
- # dist.install(pack)
- # dist.uninstall(pack)
- else:
- registerWinPythonHelp = f"Register distribution: associate file extensions, icons and context menu with this WinPython"
- unregisterWinPythonHelp = f"Unregister distribution: de-associate file extensions, icons and context menu from this WinPython"
- parser = ArgumentParser(
- description="WinPython Package Manager: handle a WinPython Distribution and its packages",
- formatter_class=RawTextHelpFormatter,
- )
- parser.add_argument(
- "fname",
- metavar="package",
- nargs="?",
- default="",
- type=str,
- help="optional package name or package wheel",
- )
- parser.add_argument(
- "--register",
- dest="registerWinPython",
- action="store_const",
- const=True,
- default=False,
- help=registerWinPythonHelp,
- )
- parser.add_argument(
- "--unregister",
- dest="unregisterWinPython",
- action="store_const",
- const=True,
- default=False,
- help=unregisterWinPythonHelp,
- )
- parser.add_argument(
- "-v",
- "--verbose",
- dest="verbose",
- action="store_const",
- const=True,
- default=False,
- help="show more details on packages and actions",
- )
- parser.add_argument(
- "-ls",
- "--list",
- dest="list",
- action="store_const",
- const=True,
- default=False,
- help=f"list packages matching the given [optionnal] package expression: wppm -ls, wppm -ls pand",
- )
- parser.add_argument(
- "-p",
- dest="pipdown",
- action="store_const",
- const=True,
- default=False,
- help=f"show Package dependancies of the given package[option]: wppm -p pandas[test]",
- )
- parser.add_argument(
- "-r",
- dest="pipup",
- action="store_const",
- const=True,
- default=False,
- help=f"show Reverse dependancies of the given package[option]: wppm -r pytest[test]",
- )
- parser.add_argument(
- "-l",
- dest="levels",
- type=int,
- default=2,
- help=f"show 'LEVELS' levels of dependancies of the package, default is 2: wppm -p pandas -l1",
- )
- parser.add_argument(
- "-lsa",
- dest="all",
- action="store_const",
- const=True,
- default=False,
- help=f"list details of package names matching given regular expression: wppm -lsa pandas -l1",
- )
- parser.add_argument(
- "-t",
- dest="target",
- default=sys.prefix,
- help=f'path to target Python distribution (default: "{sys.prefix}")',
- )
- parser.add_argument(
- "-i",
- "--install",
- dest="install",
- action="store_const",
- const=True,
- default=False,
- help="install a given package wheel (use pip for more features)",
- )
- parser.add_argument(
- "-u",
- "--uninstall",
- dest="uninstall",
- action="store_const",
- const=True,
- default=False,
- help="uninstall package (use pip for more features)",
- )
- args = parser.parse_args()
- targetpython = None
- if args.target and not args.target==sys.prefix:
- targetpython = args.target if args.target[-4:] == '.exe' else str(Path(args.target) / 'python.exe')
- # print(targetpython.resolve() to check)
- if args.install and args.uninstall:
- raise RuntimeError("Incompatible arguments: --install and --uninstall")
- if args.registerWinPython and args.unregisterWinPython:
- raise RuntimeError("Incompatible arguments: --install and --uninstall")
- if args.pipdown:
- pip = piptree.PipData(targetpython)
- pack, extra, *other = (args.fname + "[").replace("]", "[").split("[")
- print(pip.down(pack, extra, args.levels, verbose=args.verbose))
+ elif args.pipup:
+ pip = piptree.PipData(targetpython)
+ pack, extra, *other = (args.fname + "[").replace("]", "[").split("[")
+ print(pip.up(pack, extra, args.levels, verbose=args.verbose))
+ sys.exit()
+ elif args.list:
+ pip = piptree.PipData(targetpython)
+ todo = [l for l in pip.pip_list(full=True) if bool(re.search(args.fname, l[0]))]
+ titles = [['Package', 'Version', 'Summary'], ['_' * max(x, 6) for x in utils.columns_width(todo)]]
+ listed = utils.formatted_list(titles + todo, max_width=70)
+ for p in listed:
+ print(*p)
+ sys.exit()
+ elif args.all:
+ pip = piptree.PipData(targetpython)
+ todo = [l for l in pip.pip_list(full=True) if bool(re.search(args.fname, l[0]))]
+ for l in todo:
+ # print(pip.distro[l[0]])
+ title = f"** Package: {l[0]} **"
+ print("\n" + "*" * len(title), f"\n{title}", "\n" + "*" * len(title))
+ for key, value in pip.raw[l[0]].items():
+ rawtext = json.dumps(value, indent=2, ensure_ascii=False)
+ lines = [l for l in rawtext.split(r"\n") if len(l.strip()) > 2]
+ if key.lower() != 'description' or args.verbose:
+ print(f"{key}: ", "\n".join(lines).replace('"', ""))
+ sys.exit()
+ if args.registerWinPython:
+ print(registerWinPythonHelp)
+ if utils.is_python_distribution(args.target):
+ dist = Distribution(args.target)
+ else:
+ raise OSError(f"Invalid Python distribution {args.target}")
+ print(f"registering {args.target}")
+ print("continue ? Y/N")
+ theAnswer = input()
+ if theAnswer == "Y":
+ associate.register(dist.target, verbose=args.verbose)
sys.exit()
- elif args.pipup:
- pip = piptree.PipData(targetpython)
- pack, extra, *other = (args.fname + "[").replace("]", "[").split("[")
- print(pip.up(pack, extra, args.levels, verbose=args.verbose))
+ if args.unregisterWinPython:
+ print(unregisterWinPythonHelp)
+ if utils.is_python_distribution(args.target):
+ dist = Distribution(args.target)
+ else:
+ raise OSError(f"Invalid Python distribution {args.target}")
+ print(f"unregistering {args.target}")
+ print("continue ? Y/N")
+ theAnswer = input()
+ if theAnswer == "Y":
+ associate.unregister(dist.target, verbose=args.verbose)
sys.exit()
- elif args.list:
- pip = piptree.PipData(targetpython)
- todo = [l for l in pip.pip_list(full=True) if bool(re.search(args.fname, l[0])) ]
- titles = [['Package', 'Version', 'Summary'],['_' * max(x, 6) for x in utils.columns_width(todo)]]
- listed = utils.formatted_list(titles + todo, max_width=70)
- for p in listed:
- print(*p)
+ if utils.is_python_distribution(args.target):
+ dist = Distribution(args.target, verbose=True)
+ cmd_fix = rf"from winpython import wppm;dist=wppm.Distribution(r'{dist.target}');dist.patch_standard_packages('pip', to_movable=False)"
+ cmd_mov = rf"from winpython import wppm;dist=wppm.Distribution(r'{dist.target}');dist.patch_standard_packages('pip', to_movable=True)"
+ if args.fix:
+ # dist.patch_standard_packages('pip', to_movable=False) # would fail on wppm.exe
+ p = subprocess.Popen(["start", "cmd", "/k",dist.python_exe, "-c" , cmd_fix], shell = True, cwd=dist.target)
sys.exit()
- elif args.all:
- pip = piptree.PipData(targetpython)
- todo = [l for l in pip.pip_list(full=True) if bool(re.search(args.fname, l[0])) ]
- for l in todo:
- # print(pip.distro[l[0]])
- title = f"** Package: {l[0]} **"
- print("\n"+"*"*len(title), f"\n{title}", "\n"+"*"*len(title) )
- for key, value in pip.raw[l[0]].items():
- rawtext=json.dumps(value, indent=2, ensure_ascii=False)
- lines = [l for l in rawtext.split(r"\n") if len(l.strip()) > 2]
- if key.lower() != 'description' or args.verbose==True:
- print(f"{key}: ", "\n".join(lines).replace('"', ""))
- sys.exit()
- if args.registerWinPython:
- print(registerWinPythonHelp)
- if utils.is_python_distribution(args.target):
- dist = Distribution(args.target)
- else:
- raise WindowsError(f"Invalid Python distribution {args.target}")
- print(f"registering {args.target}")
- print("continue ? Y/N")
- theAnswer = input()
- if theAnswer == "Y":
- from winpython import associate
-
- associate.register(dist.target, verbose=args.verbose)
- sys.exit()
- if args.unregisterWinPython:
- print(unregisterWinPythonHelp)
- if utils.is_python_distribution(args.target):
- dist = Distribution(args.target)
- else:
- raise WindowsError(f"Invalid Python distribution {args.target}")
- print(f"unregistering {args.target}")
- print("continue ? Y/N")
- theAnswer = input()
- if theAnswer == "Y":
- from winpython import associate
-
- associate.unregister(dist.target, verbose=args.verbose)
- sys.exit()
- elif not args.install and not args.uninstall:
+ if args.movable:
+ p = subprocess.Popen(["start", "cmd", "/k",dist.python_exe, "-c" , cmd_mov], shell = True, cwd=dist.target)
+ sys.exit()
+ if not args.install and not args.uninstall:
args.install = True
if not Path(args.fname).is_file() and args.install:
if args.fname == "":
parser.print_help()
sys.exit()
else:
- raise IOError(f"File not found: {args.fname}")
- if utils.is_python_distribution(args.target):
- dist = Distribution(args.target, verbose=True)
+ raise FileNotFoundError(f"File not found: {args.fname}")
try:
if args.uninstall:
package = dist.find_package(args.fname)
dist.uninstall(package)
- else:
+ elif args.install:
package = Package(args.fname)
if args.install:
dist.install(package)
except NotImplementedError:
raise RuntimeError("Package is not (yet) supported by WPPM")
- else:
- raise WindowsError(f"Invalid Python distribution {args.target}")
+ else:
+ raise OSError(f"Invalid Python distribution {args.target}")
if __name__ == "__main__":