diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 3937d85e0..60c94dbeb 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -7,19 +7,29 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + - name: Set up Python + uses: astral-sh/setup-uv@v7 + with: + python-version: "3.12" + cache-python: true + activate-environment: true + enable-cache: true + - name: Doxygen Action uses: mattnotmitt/doxygen-action@1.12.0 with: working-directory: "doc/" + - name: Synchronize the virtual environment + run: uv sync --managed-python --no-dev --group doc + - name: Build Sphinx documentation run: | - pip install -r doc/requirements.txt - sphinx-build doc/source/ ./doc/build/html/ + uv run sphinx-build doc/source/ ./doc/build/html/ - name: Upload artifact # Automatically uploads an artifact from the './_site' directory by default - uses: actions/upload-pages-artifact@v4 + uses: actions/upload-pages-artifact@v5 with: path: doc/build/html/ @@ -37,4 +47,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 53c0934ae..7b1bee82c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,56 +10,87 @@ jobs: build-test: name: Build and Test runs-on: ${{ matrix.os.instance }} - timeout-minutes: 15 + timeout-minutes: 30 strategy: fail-fast: false matrix: os: - # Disabled for now, will require some work (#2653) - # - # - category: windows - # platform: x86 - # instance: windows-latest + - category: windows + platform: x86 + instance: windows-latest + suffix: -windows-x86-none - category: windows platform: x64 instance: windows-latest + suffix: -windows-x86_64-none - category: ubuntu platform: x64 instance: ubuntu-22.04 + suffix: "" - category: ubuntu platform: arm64 instance: ubuntu-22.04-arm + suffix: "" - category: macos platform: x64 - instance: macos-13 - - python: ["3.10", "3.11", "3.12", "3.13"] + instance: macos-15 + suffix: -macos-x86_64-none + - category: macos + platform: arm64 + instance: macos-15 + suffix: -macos-aarch64-none + + python: ["3.10", "3.11", "3.12", "3.13", "3.14"] + + exclude: + # Fails with initfs_encoding error + - os: + category: windows + platform: x86 + python: "3.10" + + # Fails to find pytest + - os: + category: windows + platform: x64 + python: '3.10' + + # fails to call mono methods + - os: + category: windows + platform: x86 + python: '3.13' + + env: + NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages steps: - - name: Set Environment on macOS - uses: maxim-lobanov/setup-xamarin@v1 - if: ${{ matrix.os.category == 'macos' }} - with: - mono-version: latest - - name: Checkout code uses: actions/checkout@v6 + # Use main until support for architecture lands - name: Setup .NET - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@main + with: + dotnet-version: '10.0' + architecture: ${{ matrix.os.platform }} + + - run: dotnet restore + + - name: Install Mono + uses: pythonnet/clr-loader/.github/actions/install-mono@main with: - dotnet-version: '8.0.x' + arch: ${{ matrix.os.platform }} - name: Set up Python ${{ matrix.python }} uses: astral-sh/setup-uv@v7 with: - architecture: ${{ matrix.os.platform }} - python-version: ${{ matrix.python }} + python-version: cpython-${{ matrix.python }}${{ matrix.os.suffix }} cache-python: true activate-environment: true enable-cache: true @@ -67,33 +98,27 @@ jobs: - name: Synchronize the virtual environment run: uv sync --managed-python - - name: Show pyvenv.cfg - run: cat .venv/pyvenv.cfg - - name: Embedding tests (Mono/.NET Framework) - run: dotnet test --runtime any-${{ matrix.os.platform }} --framework net472 --logger "console;verbosity=detailed" src/embed_tests/ if: always() + run: dotnet test --runtime any-${{ matrix.os.platform }} --framework net472 --logger "console;verbosity=detailed" src/embed_tests/ env: MONO_THREADS_SUSPEND: preemptive # https://github.com/mono/mono/issues/21466 - name: Embedding tests (.NET Core) - run: dotnet test --runtime any-${{ matrix.os.platform }} --framework net8.0 --logger "console;verbosity=detailed" src/embed_tests/ if: always() - - - name: Python Tests (Mono) - if: ${{ matrix.os.category != 'windows' }} - run: pytest --runtime mono + run: dotnet test --runtime any-${{ matrix.os.platform }} --framework net10.0 --logger "console;verbosity=detailed" src/embed_tests/ - name: Python Tests (.NET Core) + if: always() run: pytest --runtime coreclr + - name: Python Tests (Mono) + if: always() + run: pytest --runtime mono + - name: Python Tests (.NET Framework) if: ${{ matrix.os.category == 'windows' }} run: pytest --runtime netfx - name: Python tests run from .NET - # For some reason, it won't find pytest on the Windows + 3.10 - # combination, which hints that it does not handle the venv properly in - # this combination. - if: ${{ matrix.os.category != 'windows' || matrix.python != '3.10' }} run: dotnet test --runtime any-${{ matrix.os.platform }} src/python_tests_runner/ diff --git a/AUTHORS.md b/AUTHORS.md index 7ea639059..723520f3c 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -9,7 +9,7 @@ - Barton Cline ([@BartonCline](https://github.com/BartonCline)) - Brian Lloyd ([@brianlloyd](https://github.com/brianlloyd)) - David Anthoff ([@davidanthoff](https://github.com/davidanthoff)) -- Denis Akhiyarov ([@denfromufa](https://github.com/denfromufa)) +- Denis Akhiyarov ([@den-run-ai](https://github.com/den-run-ai)) - Tony Roberts ([@tonyroberts](https://github.com/tonyroberts)) - Victor Uriarte ([@vmuriart](https://github.com/vmuriart)) @@ -89,3 +89,4 @@ - Ehsan Iran-Nejad ([@eirannejad](https://github.com/eirannejad)) - ([@legomanww](https://github.com/legomanww)) - ([@gertdreyer](https://github.com/gertdreyer)) +- Kerbiter ([@Metadorius](https://github.com/Metadorius)) diff --git a/CHANGELOG.md b/CHANGELOG.md index df68fbb39..43e8c4f8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,211 +9,235 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ### Added -- Support `del obj[...]` for types derived from `IList` and `IDictionary` +### Changed + +### Fixed + +## 3.1.0 - 2026-05-23 + +### Added + +- Support `del obj[...]` for types derived from `IList` and `IDictionary` (#2533) +- Support for .NET Framework 4.6.1 (#2701) +- Context manager protocol for .NET `IDisposable` types, allowing use of `with` statements + for `IDisposable` objects (#2568) +- Support for Python 3.14 (#2611) +- Implement support for DLR get/set (#2706) ### Changed + +- Derive Python (virtual) environment information (#2652) + ### Fixed -- Fixed crash when trying to `del clrObj[...]` for non-arrays +- Fixed crash when trying to `del clrObj[...]` for non-arrays - ci: properly exclude job (#2542) +- Properly detect availability of `BinaryFormatter` (#2639) +- Support for .NET 10 SDK (#2684) +- Fix wheel tags (#2716) +- Name missing from `__all__` on re-import (#2717) + +### Removed + +- Support for Python 3.7 to 3.9 (#2639) ## [3.0.5](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.5) - 2024-12-13 ### Added -- Support for Python 3.13 (#2454) - +- Support for Python 3.13 (#2454) ## [3.0.4](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.4) - 2024-09-19 ### Added -- Added `ToPythonAs()` extension method to allow for explicit conversion +- Added `ToPythonAs()` extension method to allow for explicit conversion using a specific type. ([#2311][i2311]) -- Added `IComparable` and `IEquatable` implementations to `PyInt`, `PyFloat`, +- Added `IComparable` and `IEquatable` implementations to `PyInt`, `PyFloat`, and `PyString` to compare with primitive .NET types like `long`. ### Changed -- Added a `FormatterFactory` member in RuntimeData to create formatters with +- Added a `FormatterFactory` member in RuntimeData to create formatters with parameters. For compatibility, the `FormatterType` member is still present and has precedence when defining both `FormatterFactory` and `FormatterType` -- Added a post-serialization and a pre-deserialization step callbacks to +- Added a post-serialization and a pre-deserialization step callbacks to extend (de)serialization process -- Added an API to stash serialized data on Python capsules +- Added an API to stash serialized data on Python capsules ### Fixed -- Fixed RecursionError for reverse operators on C# operable types from python. See #2240 -- Fixed crash when .NET event has no `AddMethod` -- Fixed probing for assemblies in `sys.path` failing when a path in `sys.path` +- Fixed RecursionError for reverse operators on C# operable types from python. See #2240 +- Fixed crash when .NET event has no `AddMethod` +- Fixed probing for assemblies in `sys.path` failing when a path in `sys.path` has invalid characters. See #2376 -- Fixed possible access violation exception on shutdown. See ([#1977][i1977]) +- Fixed possible access violation exception on shutdown. See ([#1977][i1977]) ## [3.0.3](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.3) - 2023-10-11 ### Added -- Support for Python 3.12 +- Support for Python 3.12 ### Changed -- Use enum name in `repr` +- Use enum name in `repr` ## [3.0.2](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.2) - 2023-08-29 ### Fixed -- Fixed error occuring when inheriting a class containing a virtual generic method -- Make a second call to `pythonnet.load` a no-op, as it was intended -- Added support for multiple inheritance when inheriting from a class and/or multiple interfaces -- Fixed error occuring when calling `GetBuffer` for anything other than `PyBUF.SIMPLE` -- Bumped `clr_loader` dependency to incorporate patches +- Fixed error occuring when inheriting a class containing a virtual generic method +- Make a second call to `pythonnet.load` a no-op, as it was intended +- Added support for multiple inheritance when inheriting from a class and/or multiple interfaces +- Fixed error occuring when calling `GetBuffer` for anything other than `PyBUF.SIMPLE` +- Bumped `clr_loader` dependency to incorporate patches ## [3.0.1](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.1) - 2022-11-03 ### Added -- Support for Python 3.11 +- Support for Python 3.11 ### Changed -- Allow decoders to override conversion of types derived from primitive types +- Allow decoders to override conversion of types derived from primitive types ### Fixed -- Fixed objects leaking when Python attached event handlers to them even if they were later removed -- Fixed `PyInt` conversion to `BigInteger` and `System.String` produced incorrect result for values between 128 and 255. -- Fixed implementing a generic interface with a Python class - +- Fixed objects leaking when Python attached event handlers to them even if they were later removed +- Fixed `PyInt` conversion to `BigInteger` and `System.String` produced incorrect result for values between 128 and 255. +- Fixed implementing a generic interface with a Python class ## [3.0.0](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.0) - 2022-09-29 ### Added -- Ability to instantiate new .NET arrays using `Array[T](dim1, dim2, ...)` syntax -- Python operator method will call C# operator method for supported binary and unary operators ([#1324][p1324]). -- Add GetPythonThreadID and Interrupt methods in PythonEngine -- Ability to implement delegates with `ref` and `out` parameters in Python, by returning the modified parameter values in a tuple. ([#1355][i1355]) -- Ability to override .NET methods that have `out` or `ref` in Python by returning the modified parameter values in a tuple. ([#1481][i1481]) -- `PyType` - a wrapper for Python type objects, that also permits creating new heap types from `TypeSpec` -- Improved exception handling: - * exceptions can now be converted with codecs - * `InnerException` and `__cause__` are propagated properly -- `__name__` and `__signature__` to reflected .NET methods -- .NET collection types now implement standard Python collection interfaces from `collections.abc`. +- Ability to instantiate new .NET arrays using `Array[T](dim1, dim2, ...)` syntax +- Python operator method will call C# operator method for supported binary and unary operators ([#1324][p1324]). +- Add GetPythonThreadID and Interrupt methods in PythonEngine +- Ability to implement delegates with `ref` and `out` parameters in Python, by returning the modified parameter values in a tuple. ([#1355][i1355]) +- Ability to override .NET methods that have `out` or `ref` in Python by returning the modified parameter values in a tuple. ([#1481][i1481]) +- `PyType` - a wrapper for Python type objects, that also permits creating new heap types from `TypeSpec` +- Improved exception handling: + +- exceptions can now be converted with codecs +- `InnerException` and `__cause__` are propagated properly + +- `__name__` and `__signature__` to reflected .NET methods +- .NET collection types now implement standard Python collection interfaces from `collections.abc`. See [Mixins/collections.py](src/runtime/Mixins/collections.py). -- you can cast objects to generic .NET interfaces without specifying generic arguments as long as there is no ambiguity. -- .NET arrays implement Python buffer protocol -- Python integer interoperability with `System.Numerics.BigInteger` -- Python.NET will correctly resolve .NET methods, that accept `PyList`, `PyInt`, +- you can cast objects to generic .NET interfaces without specifying generic arguments as long as there is no ambiguity. +- .NET arrays implement Python buffer protocol +- Python integer interoperability with `System.Numerics.BigInteger` +- Python.NET will correctly resolve .NET methods, that accept `PyList`, `PyInt`, and other `PyObject` derived types when called from Python. -- .NET classes, that have `__call__` method are callable from Python -- `PyIterable` type, that wraps any iterable object in Python -- `PythonEngine` properties for supported Python versions: `MinSupportedVersion`, `MaxSupportedVersion`, and `IsSupportedVersion` -- The runtime that is loaded on `import clr` can now be configured via environment variables - +- .NET classes, that have `__call__` method are callable from Python +- `PyIterable` type, that wraps any iterable object in Python +- `PythonEngine` properties for supported Python versions: `MinSupportedVersion`, `MaxSupportedVersion`, and `IsSupportedVersion` +- The runtime that is loaded on `import clr` can now be configured via environment variables ### Changed -- Drop support for Python 2, 3.4, 3.5, and 3.6 -- `wchar_t` size aka `Runtime.UCS` is now determined at runtime -- `clr.AddReference` may now throw errors besides `FileNotFoundException`, that provide more + +- Drop support for Python 2, 3.4, 3.5, and 3.6 +- `wchar_t` size aka `Runtime.UCS` is now determined at runtime +- `clr.AddReference` may now throw errors besides `FileNotFoundException`, that provide more details about the cause of the failure -- `clr.AddReference` no longer adds ".dll" implicitly -- `PyIter(PyObject)` constructor replaced with static `PyIter.GetIter(PyObject)` method -- Python runtime can no longer be shut down if the Python error indicator is set, as it would have unpredictable behavior -- BREAKING: Return values from .NET methods that return an interface are now automatically +- `clr.AddReference` no longer adds ".dll" implicitly +- `PyIter(PyObject)` constructor replaced with static `PyIter.GetIter(PyObject)` method +- Python runtime can no longer be shut down if the Python error indicator is set, as it would have unpredictable behavior +- BREAKING: Return values from .NET methods that return an interface are now automatically wrapped in that interface. This is a breaking change for users that rely on being able to access members that are part of the implementation class, but not the interface. Use the new `__implementation__` or `__raw_implementation__` properties to if you need to "downcast" to the implementation class. -- BREAKING: `==` and `!=` operators on `PyObject` instances now use Python comparison +- BREAKING: `==` and `!=` operators on `PyObject` instances now use Python comparison (previously was equivalent to `object.ReferenceEquals(,)`) -- BREAKING: Parameters marked with `ParameterAttributes.Out` are no longer returned in addition +- BREAKING: Parameters marked with `ParameterAttributes.Out` are no longer returned in addition to the regular method return value (unless they are passed with `ref` or `out` keyword). -- BREAKING: Drop support for the long-deprecated CLR.* prefix. -- `PyObject` now implements `IEnumerable` in addition to `IEnumerable` -- floating point values passed from Python are no longer silently truncated +- BREAKING: Drop support for the long-deprecated CLR.* prefix. +- `PyObject` now implements `IEnumerable` in addition to `IEnumerable` +- floating point values passed from Python are no longer silently truncated when .NET expects an integer [#1342][i1342] -- More specific error messages for method argument mismatch -- members of `PyObject` inherited from `System.Object and `DynamicObject` now autoacquire GIL -- BREAKING: when inheriting from .NET types in Python if you override `__init__` you +- More specific error messages for method argument mismatch +- members of `PyObject` inherited from `System.Object and`DynamicObject` now autoacquire GIL +- BREAKING: when inheriting from .NET types in Python if you override `__init__` you must explicitly call base constructor using `super().__init__(.....)`. Not doing so will lead to undefined behavior. -- BREAKING: most `PyScope` methods will never return `null`. Instead, `PyObject` `None` will be returned. -- BREAKING: `PyScope` was renamed to `PyModule` -- BREAKING: Methods with `ref` or `out` parameters and void return type return a tuple of only the `ref` and `out` parameters. -- BREAKING: to call Python from .NET `Runtime.PythonDLL` property must be set to Python DLL name +- BREAKING: most `PyScope` methods will never return `null`. Instead, `PyObject` `None` will be returned. +- BREAKING: `PyScope` was renamed to `PyModule` +- BREAKING: Methods with `ref` or `out` parameters and void return type return a tuple of only the `ref` and `out` parameters. +- BREAKING: to call Python from .NET `Runtime.PythonDLL` property must be set to Python DLL name or the DLL must be loaded in advance. This must be done before calling any other Python.NET functions. -- BREAKING: `PyObject.Length()` now raises a `PythonException` when object does not support a concept of length. -- BREAKING: disabled implicit conversion from C# enums to Python `int` and back. +- BREAKING: `PyObject.Length()` now raises a `PythonException` when object does not support a concept of length. +- BREAKING: disabled implicit conversion from C# enums to Python `int` and back. One must now either use enum members (e.g. `MyEnum.Option`), or use enum constructor (e.g. `MyEnum(42)` or `MyEnum(42, True)` when `MyEnum` does not have a member with value 42). -- BREAKING: disabled implicit conversion from Python objects implementing sequence protocol to +- BREAKING: disabled implicit conversion from Python objects implementing sequence protocol to .NET arrays when the target .NET type is `System.Object`. The conversion is still attempted when the target type is a `System.Array`. -- Sign Runtime DLL with a strong name -- Implement loading through `clr_loader` instead of the included `ClrModule`, enables +- Sign Runtime DLL with a strong name +- Implement loading through `clr_loader` instead of the included `ClrModule`, enables support for .NET Core -- BREAKING: .NET and Python exceptions are preserved when crossing Python/.NET boundary -- BREAKING: custom encoders are no longer called for instances of `System.Type` -- `PythonException.Restore` no longer clears `PythonException` instance. -- Replaced the old `__import__` hook hack with a PEP302-style Meta Path Loader -- BREAKING: Names of .NET types (e.g. `str(__class__)`) changed to better support generic types -- BREAKING: overload resolution will no longer prefer basic types. Instead, first matching overload will +- BREAKING: .NET and Python exceptions are preserved when crossing Python/.NET boundary +- BREAKING: custom encoders are no longer called for instances of `System.Type` +- `PythonException.Restore` no longer clears `PythonException` instance. +- Replaced the old `__import__` hook hack with a PEP302-style Meta Path Loader +- BREAKING: Names of .NET types (e.g. `str(__class__)`) changed to better support generic types +- BREAKING: overload resolution will no longer prefer basic types. Instead, first matching overload will be chosen. -- BREAKING: acquiring GIL using `Py.GIL` no longer forces `PythonEngine` to initialize -- BREAKING: `Exec` and `Eval` from `PythonEngine` no longer accept raw pointers. -- BREAKING: .NET collections and arrays are no longer automatically converted to +- BREAKING: acquiring GIL using `Py.GIL` no longer forces `PythonEngine` to initialize +- BREAKING: `Exec` and `Eval` from `PythonEngine` no longer accept raw pointers. +- BREAKING: .NET collections and arrays are no longer automatically converted to Python collections. Instead, they implement standard Python collection interfaces from `collections.abc`. See [Mixins/collections.py](src/runtime/Mixins/collections.py). -- BREAKING: When trying to convert Python `int` to `System.Object`, result will +- BREAKING: When trying to convert Python `int` to `System.Object`, result will be of type `PyInt` instead of `System.Int32` due to possible loss of information. Python `float` will continue to be converted to `System.Double`. -- BREAKING: Python.NET will no longer implicitly convert types like `numpy.float64`, that implement `__float__` to +- BREAKING: Python.NET will no longer implicitly convert types like `numpy.float64`, that implement `__float__` to `System.Single` and `System.Double`. An explicit conversion is required on Python or .NET side. -- BREAKING: `PyObject.GetHashCode` can fail. -- BREAKING: Python.NET will no longer implicitly convert any Python object to `System.Boolean`. -- BREAKING: `PyObject.GetAttr(name, default)` now only ignores `AttributeError` (previously ignored all exceptions). -- BREAKING: `PyObject` no longer implements `IEnumerable`. +- BREAKING: `PyObject.GetHashCode` can fail. +- BREAKING: Python.NET will no longer implicitly convert any Python object to `System.Boolean`. +- BREAKING: `PyObject.GetAttr(name, default)` now only ignores `AttributeError` (previously ignored all exceptions). +- BREAKING: `PyObject` no longer implements `IEnumerable`. Instead, `PyIterable` does that. -- BREAKING: `IPyObjectDecoder.CanDecode` `objectType` parameter type changed from `PyObject` to `PyType` +- BREAKING: `IPyObjectDecoder.CanDecode` `objectType` parameter type changed from `PyObject` to `PyType` ### Fixed -- Fix incorrect dereference of wrapper object in `tp_repr`, which may result in a program crash -- Fixed parameterless .NET constructor being silently called when a matching constructor overload is not found ([#238][i238]) -- Fix incorrect dereference in params array handling -- Fixes issue with function resolution when calling overloaded function with keyword arguments from python ([#1097][i1097]) -- Fix `object[]` parameters taking precedence when should not in overload resolution -- Fixed a bug where all .NET class instances were considered Iterable -- Fix incorrect choice of method to invoke when using keyword arguments. -- Fix non-delegate types incorrectly appearing as callable. -- Indexers can now be used with interface objects -- Fixed a bug where indexers could not be used if they were inherited -- Made it possible to use `__len__` also on `ICollection<>` interface objects -- Fixed issue when calling PythonException.Format where another exception would be raise for unnormalized exceptions -- Made it possible to call `ToString`, `GetHashCode`, and `GetType` on inteface objects -- Fixed objects returned by enumerating `PyObject` being disposed too soon -- Incorrectly using a non-generic type with type parameters now produces a helpful Python error instead of throwing NullReferenceException -- `import` may now raise errors with more detail than "No module named X" -- Exception stacktraces on `PythonException.StackTrace` are now properly formatted -- Providing an invalid type parameter to a generic type or method produces a helpful Python error -- Empty parameter names (as can be generated from F#) do not cause crashes -- Unicode strings with surrogates were truncated when converting from Python -- `Reload` mode now supports generic methods (previously Python would stop seeing them after reload) -- Temporarily fixed issue resolving method overload when method signature has `out` parameters ([#1672](i1672)) -- Decimal default parameters are now correctly taken into account +- Fix incorrect dereference of wrapper object in `tp_repr`, which may result in a program crash +- Fixed parameterless .NET constructor being silently called when a matching constructor overload is not found ([#238][i238]) +- Fix incorrect dereference in params array handling +- Fixes issue with function resolution when calling overloaded function with keyword arguments from python ([#1097][i1097]) +- Fix `object[]` parameters taking precedence when should not in overload resolution +- Fixed a bug where all .NET class instances were considered Iterable +- Fix incorrect choice of method to invoke when using keyword arguments. +- Fix non-delegate types incorrectly appearing as callable. +- Indexers can now be used with interface objects +- Fixed a bug where indexers could not be used if they were inherited +- Made it possible to use `__len__` also on `ICollection<>` interface objects +- Fixed issue when calling PythonException.Format where another exception would be raise for unnormalized exceptions +- Made it possible to call `ToString`, `GetHashCode`, and `GetType` on inteface objects +- Fixed objects returned by enumerating `PyObject` being disposed too soon +- Incorrectly using a non-generic type with type parameters now produces a helpful Python error instead of throwing NullReferenceException +- `import` may now raise errors with more detail than "No module named X" +- Exception stacktraces on `PythonException.StackTrace` are now properly formatted +- Providing an invalid type parameter to a generic type or method produces a helpful Python error +- Empty parameter names (as can be generated from F#) do not cause crashes +- Unicode strings with surrogates were truncated when converting from Python +- `Reload` mode now supports generic methods (previously Python would stop seeing them after reload) +- Temporarily fixed issue resolving method overload when method signature has `out` parameters ([#1672](i1672)) +- Decimal default parameters are now correctly taken into account ### Removed -- `ShutdownMode` has been removed. The only shutdown mode supported now is an equivalent of `ShutdownMode.Reload`. +- `ShutdownMode` has been removed. The only shutdown mode supported now is an equivalent of `ShutdownMode.Reload`. There is no need to specify it. -- implicit assembly loading (you have to explicitly `clr.AddReference` before doing import) -- messages in `PythonException` no longer start with exception type -- `PyScopeManager`, `PyScopeException`, `PyScope` (use `PyModule` instead) -- support for .NET Framework 4.0-4.6; Mono before 5.4. Python.NET now requires .NET Standard 2.0 +- implicit assembly loading (you have to explicitly `clr.AddReference` before doing import) +- messages in `PythonException` no longer start with exception type +- `PyScopeManager`, `PyScopeException`, `PyScope` (use `PyModule` instead) +- support for .NET Framework 4.0-4.6; Mono before 5.4. Python.NET now requires .NET Standard 2.0 (see [the matrix](https://docs.microsoft.com/en-us/dotnet/standard/net-standard#net-implementation-support)) ## [2.5.2](https://github.com/pythonnet/pythonnet/releases/tag/v2.5.2) - 2021-02-05 @@ -221,8 +245,9 @@ There is no need to specify it. Bugfix release. ### Fixed -- Fix `object[]` parameters taking precedence when should not in overload resolution -- Empty parameter names (as can be generated from F#) do not cause crashes + +- Fix `object[]` parameters taking precedence when should not in overload resolution +- Empty parameter names (as can be generated from F#) do not cause crashes ## [2.5.1](https://github.com/pythonnet/pythonnet/releases/tag/v2.5.1) - 2020-06-18 @@ -230,8 +255,8 @@ Bugfix release. ### Fixed -- Fix incorrect dereference of wrapper object in `tp_repr`, which may result in a program crash -- Fix incorrect dereference in params array handling +- Fix incorrect dereference of wrapper object in `tp_repr`, which may result in a program crash +- Fix incorrect dereference in params array handling ## [2.5.0](https://github.com/pythonnet/pythonnet/releases/tag/v2.5.0) - 2020-06-14 @@ -239,342 +264,341 @@ This version improves performance on benchmarks significantly compared to 2.3. ### Added -- Automatic NuGet package generation in appveyor and local builds -- Function that sets `Py_NoSiteFlag` to 1. -- Support for Jetson Nano. -- Support for `__len__` for .NET classes that implement ICollection -- `PyExport` attribute to hide .NET types from Python -- `PythonException.Format` method to format exceptions the same as +- Automatic NuGet package generation in appveyor and local builds +- Function that sets `Py_NoSiteFlag` to 1. +- Support for Jetson Nano. +- Support for `__len__` for .NET classes that implement ICollection +- `PyExport` attribute to hide .NET types from Python +- `PythonException.Format` method to format exceptions the same as `traceback.format_exception` -- `Runtime.None` to be able to pass `None` as parameter into Python from .NET -- `PyObject.IsNone()` to check if a Python object is None in .NET. -- Support for Python 3.8 -- Codecs as the designated way to handle automatic conversions between +- `Runtime.None` to be able to pass `None` as parameter into Python from .NET +- `PyObject.IsNone()` to check if a Python object is None in .NET. +- Support for Python 3.8 +- Codecs as the designated way to handle automatic conversions between .NET and Python types -- Added Python 3 buffer api support and PyBuffer interface for fast byte and numpy array read/write ([#980][p980]) +- Added Python 3 buffer api support and PyBuffer interface for fast byte and numpy array read/write ([#980][p980]) ### Changed -- Added argument types information to "No method matches given arguments" message -- Moved wheel import in setup.py inside of a try/except to prevent pip collection failures -- Removes `PyLong_GetMax` and `PyClass_New` when targetting Python3 -- Improved performance of calls from Python to C# -- Added support for converting python iterators to C# arrays -- Changed usage of the obsolete function +- Added argument types information to "No method matches given arguments" message +- Moved wheel import in setup.py inside of a try/except to prevent pip collection failures +- Removes `PyLong_GetMax` and `PyClass_New` when targetting Python3 +- Improved performance of calls from Python to C# +- Added support for converting python iterators to C# arrays +- Changed usage of the obsolete function `GetDelegateForFunctionPointer(IntPtr, Type)` to `GetDelegateForFunctionPointer(IntPtr)` -- When calling C# from Python, enable passing argument of any type to a +- When calling C# from Python, enable passing argument of any type to a parameter of C# type `object` by wrapping it into `PyObject` instance. ([#881][i881]) -- Added support for kwarg parameters when calling .NET methods from Python -- Changed method for finding MSBuild using vswhere -- Reworked `Finalizer`. Now objects drop into its queue upon finalization, +- Added support for kwarg parameters when calling .NET methods from Python +- Changed method for finding MSBuild using vswhere +- Reworked `Finalizer`. Now objects drop into its queue upon finalization, which is periodically drained when new objects are created. -- Marked `Runtime.OperatingSystemName` and `Runtime.MachineName` as +- Marked `Runtime.OperatingSystemName` and `Runtime.MachineName` as `Obsolete`, should never have been `public` in the first place. They also don't necessarily return a result that matches the `platform` module's. -- Unconditionally depend on `pycparser` for the interop module generation +- Unconditionally depend on `pycparser` for the interop module generation ### Fixed -- Fixed runtime that fails loading when using pythonnet in an environment +- Fixed runtime that fails loading when using pythonnet in an environment together with Nuitka -- Fixes bug where delegates get casts (dotnetcore) -- Determine size of interpreter longs at runtime -- Handling exceptions ocurred in ModuleObject's getattribute -- Fill `__classcell__` correctly for Python subclasses of .NET types -- Fixed issue with params methods that are not passed an array. -- Use UTF8 to encode strings passed to `PyRun_String` on Python 3 +- Fixes bug where delegates get casts (dotnetcore) +- Determine size of interpreter longs at runtime +- Handling exceptions ocurred in ModuleObject's getattribute +- Fill `__classcell__` correctly for Python subclasses of .NET types +- Fixed issue with params methods that are not passed an array. +- Use UTF8 to encode strings passed to `PyRun_String` on Python 3 ## [2.4.0][] - 2019-05-15 ### Added -- Added support for embedding python into dotnet core 2.0 (NetStandard 2.0) -- Added new build system (pythonnet.15.sln) based on dotnetcore-sdk/xplat(crossplatform msbuild). +- Added support for embedding python into dotnet core 2.0 (NetStandard 2.0) +- Added new build system (pythonnet.15.sln) based on dotnetcore-sdk/xplat(crossplatform msbuild). Currently there two side-by-side build systems that produces the same output (net40) from the same sources. After a some transition time, current (mono/ msbuild 14.0) build system will be removed. -- NUnit upgraded to 3.7 (eliminates travis-ci random bug) -- Added C# `PythonEngine.AddShutdownHandler` to help client code clean up on shutdown. -- Added `clr.GetClrType` ([#432][i432])([#433][p433]) -- Allowed passing `None` for nullable args ([#460][p460]) -- Added keyword arguments based on C# syntax for calling CPython methods ([#461][p461]) -- Catches exceptions thrown in C# iterators (yield returns) and rethrows them in python ([#475][i475])([#693][p693]) -- Implemented GetDynamicMemberNames() for PyObject to allow dynamic object members to be visible in the debugger ([#443][i443])([#690][p690]) -- Incorporated reference-style links to issues and pull requests in the CHANGELOG ([#608][i608]) -- Added PyObject finalizer support, Python objects referred by C# can be auto collect now ([#692][p692]). -- Added detailed comments about aproaches and dangers to handle multi-app-domains ([#625][p625]) -- Python 3.7 support, builds and testing added. Defaults changed from Python 3.6 to 3.7 ([#698][p698]) -- Added support for C# types to provide `__repr__` ([#680][p680]) +- NUnit upgraded to 3.7 (eliminates travis-ci random bug) +- Added C# `PythonEngine.AddShutdownHandler` to help client code clean up on shutdown. +- Added `clr.GetClrType` ([#432][i432])([#433][p433]) +- Allowed passing `None` for nullable args ([#460][p460]) +- Added keyword arguments based on C# syntax for calling CPython methods ([#461][p461]) +- Catches exceptions thrown in C# iterators (yield returns) and rethrows them in python ([#475][i475])([#693][p693]) +- Implemented GetDynamicMemberNames() for PyObject to allow dynamic object members to be visible in the debugger ([#443][i443])([#690][p690]) +- Incorporated reference-style links to issues and pull requests in the CHANGELOG ([#608][i608]) +- Added PyObject finalizer support, Python objects referred by C# can be auto collect now ([#692][p692]). +- Added detailed comments about aproaches and dangers to handle multi-app-domains ([#625][p625]) +- Python 3.7 support, builds and testing added. Defaults changed from Python 3.6 to 3.7 ([#698][p698]) +- Added support for C# types to provide `__repr__` ([#680][p680]) ### Changed -- PythonException included C# call stack -- Reattach python exception traceback information (#545) -- PythonEngine.Intialize will now call `Py_InitializeEx` with a default value of 0, so signals will not be configured by default on embedding. This is different from the previous behaviour, where `Py_Initialize` was called instead, which sets initSigs to 1. ([#449][i449]) -- Refactored MethodBinder.Bind in preparation to make it extensible (#829) -- Look for installed Windows 10 sdk's during installation instead of relying on specific versions. -- Remove `LoadLibrary` call. ([#880][p880]) +- PythonException included C# call stack +- Reattach python exception traceback information (#545) +- PythonEngine.Intialize will now call `Py_InitializeEx` with a default value of 0, so signals will not be configured by default on embedding. This is different from the previous behaviour, where `Py_Initialize` was called instead, which sets initSigs to 1. ([#449][i449]) +- Refactored MethodBinder.Bind in preparation to make it extensible (#829) +- Look for installed Windows 10 sdk's during installation instead of relying on specific versions. +- Remove `LoadLibrary` call. ([#880][p880]) ### Fixed -- Fixed secondary PythonEngine.Initialize call, all sensitive static variables now reseted. +- Fixed secondary PythonEngine.Initialize call, all sensitive static variables now reseted. This is a hidden bug. Once python cleaning up enough memory, objects from previous engine run becomes corrupted. ([#534][p534]) -- Fixed Visual Studio 2017 compat ([#434][i434]) for setup.py -- Fixed crashes when integrating pythonnet in Unity3d ([#714][i714]), +- Fixed Visual Studio 2017 compat ([#434][i434]) for setup.py +- Fixed crashes when integrating pythonnet in Unity3d ([#714][i714]), related to unloading the Application Domain -- Fixed interop methods with Py_ssize_t. NetCoreApp 2.0 is more sensitive than net40 and requires this fix. ([#531][p531]) -- Fixed crash on exit of the Python interpreter if a python class +- Fixed interop methods with Py_ssize_t. NetCoreApp 2.0 is more sensitive than net40 and requires this fix. ([#531][p531]) +- Fixed crash on exit of the Python interpreter if a python class derived from a .NET class has a `__namespace__` or `__assembly__` attribute ([#481][i481]) -- Fixed conversion of 'float' and 'double' values ([#486][i486]) -- Fixed 'clrmethod' for python 2 ([#492][i492]) -- Fixed double calling of constructor when deriving from .NET class ([#495][i495]) -- Fixed `clr.GetClrType` when iterating over `System` members ([#607][p607]) -- Fixed `LockRecursionException` when loading assemblies ([#627][i627]) -- Fixed errors breaking .NET Remoting on method invoke ([#276][i276]) -- Fixed PyObject.GetHashCode ([#676][i676]) -- Fix memory leaks due to spurious handle incrementation ([#691][i691]) -- Fix spurious assembly loading exceptions from private types ([#703][i703]) -- Fix inheritance of non-abstract base methods ([#755][i755]) - +- Fixed conversion of 'float' and 'double' values ([#486][i486]) +- Fixed 'clrmethod' for python 2 ([#492][i492]) +- Fixed double calling of constructor when deriving from .NET class ([#495][i495]) +- Fixed `clr.GetClrType` when iterating over `System` members ([#607][p607]) +- Fixed `LockRecursionException` when loading assemblies ([#627][i627]) +- Fixed errors breaking .NET Remoting on method invoke ([#276][i276]) +- Fixed PyObject.GetHashCode ([#676][i676]) +- Fix memory leaks due to spurious handle incrementation ([#691][i691]) +- Fix spurious assembly loading exceptions from private types ([#703][i703]) +- Fix inheritance of non-abstract base methods ([#755][i755]) ## [2.3.0][] - 2017-03-11 ### Added -- Added Code Coverage ([#345][p345]) -- Added `PySys_SetArgvEx` ([#347][p347]) -- Added XML Documentation ([#349][p349]) -- Added `Embedded_Tests` on AppVeyor ([#224][i224])([#353][p353]) -- Added `Embedded_Tests` on Travis ([#224][i224])([#391][p391]) -- Added PY3 settings to solution configuration-manager ([#346][p346]) -- Added `Slack` ([#384][p384])([#383][i383])([#386][p386]) -- Added function of passing an arbitrary .NET object as the value +- Added Code Coverage ([#345][p345]) +- Added `PySys_SetArgvEx` ([#347][p347]) +- Added XML Documentation ([#349][p349]) +- Added `Embedded_Tests` on AppVeyor ([#224][i224])([#353][p353]) +- Added `Embedded_Tests` on Travis ([#224][i224])([#391][p391]) +- Added PY3 settings to solution configuration-manager ([#346][p346]) +- Added `Slack` ([#384][p384])([#383][i383])([#386][p386]) +- Added function of passing an arbitrary .NET object as the value of an attribute of `PyObject` ([#370][i370])([#373][p373]) -- Added `Coverity scan` ([#390][i390]) -- Added `bumpversion` for version control ([#319][i319])([#398][p398]) -- Added `tox` for local testing ([#345][p345]) -- Added `requirements.txt` -- Added to `PythonEngine` methods `Eval` and `Exec` ([#389][p389]) -- Added implementations of `ICustomMarshal` ([#407][p407]) -- Added docker images ([#322][i322]) -- Added hooks in `pyinstaller` and `cx_freeze` for `pythonnet` ([#66][i66]) +- Added `Coverity scan` ([#390][i390]) +- Added `bumpversion` for version control ([#319][i319])([#398][p398]) +- Added `tox` for local testing ([#345][p345]) +- Added `requirements.txt` +- Added to `PythonEngine` methods `Eval` and `Exec` ([#389][p389]) +- Added implementations of `ICustomMarshal` ([#407][p407]) +- Added docker images ([#322][i322]) +- Added hooks in `pyinstaller` and `cx_freeze` for `pythonnet` ([#66][i66]) ### Changed -- Refactored python `unittests` ([#329][p329]) -- Refactored python `setup.py` ([#337][p337]) -- Refactored remaining of Build Directives on `runtime.cs` ([#339][p339]) -- Refactored `Embedded_Tests` to make easier to write tests ([#369][p369]) -- Changed `unittests` to `pytest` ([#368][p368]) -- Upgraded NUnit framework from `2.6.3` to `3.5.0` ([#341][p341]) -- Downgraded NUnit framework from `3.5.0` to `2.6.4` ([#353][p353]) -- Upgraded NUnit framework from `2.6.4` to `3.6.0` ([#371][p371]) -- Unfroze Mono version on Travis ([#345][p345]) -- Changed `conda.recipe` build to only pull-requests ([#345][p345]) -- Combine `Py_DEBUG` and `PYTHON_WITH_PYDEBUG` flags ([#362][i362]) +- Refactored python `unittests` ([#329][p329]) +- Refactored python `setup.py` ([#337][p337]) +- Refactored remaining of Build Directives on `runtime.cs` ([#339][p339]) +- Refactored `Embedded_Tests` to make easier to write tests ([#369][p369]) +- Changed `unittests` to `pytest` ([#368][p368]) +- Upgraded NUnit framework from `2.6.3` to `3.5.0` ([#341][p341]) +- Downgraded NUnit framework from `3.5.0` to `2.6.4` ([#353][p353]) +- Upgraded NUnit framework from `2.6.4` to `3.6.0` ([#371][p371]) +- Unfroze Mono version on Travis ([#345][p345]) +- Changed `conda.recipe` build to only pull-requests ([#345][p345]) +- Combine `Py_DEBUG` and `PYTHON_WITH_PYDEBUG` flags ([#362][i362]) ### Deprecated -- Deprecated `RunString` ([#401][i401]) +- Deprecated `RunString` ([#401][i401]) ### Fixed -- Fixed crash during Initialization ([#262][i262])([#343][p343]) -- Fixed crash during Shutdown ([#365][p365]) -- Fixed multiple build warnings -- Fixed method signature match for Object Type ([#203][i203])([#377][p377]) -- Fixed outdated version number in AssemblyInfo ([#398][p398]) -- Fixed wrong version number in `conda.recipe` ([#398][p398]) -- Fixed fixture location for Python tests and `Embedded_Tests` -- Fixed `PythonException` crash during Shutdown ([#400][p400]) -- Fixed `AppDomain` unload during GC ([#397][i397])([#400][p400]) -- Fixed `Py_Main` & `PySys_SetArgvEx` `no mem error` on `UCS4/PY3` ([#399][p399]) -- Fixed `Python.Runtime.dll.config` on macOS ([#120][i120]) -- Fixed crash on `PythonEngine.Version` ([#413][i413]) -- Fixed `PythonEngine.PythonPath` issues ([#179][i179])([#414][i414])([#415][p415]) -- Fixed missing information on 'No method matches given arguments' by adding the method name +- Fixed crash during Initialization ([#262][i262])([#343][p343]) +- Fixed crash during Shutdown ([#365][p365]) +- Fixed multiple build warnings +- Fixed method signature match for Object Type ([#203][i203])([#377][p377]) +- Fixed outdated version number in AssemblyInfo ([#398][p398]) +- Fixed wrong version number in `conda.recipe` ([#398][p398]) +- Fixed fixture location for Python tests and `Embedded_Tests` +- Fixed `PythonException` crash during Shutdown ([#400][p400]) +- Fixed `AppDomain` unload during GC ([#397][i397])([#400][p400]) +- Fixed `Py_Main` & `PySys_SetArgvEx` `no mem error` on `UCS4/PY3` ([#399][p399]) +- Fixed `Python.Runtime.dll.config` on macOS ([#120][i120]) +- Fixed crash on `PythonEngine.Version` ([#413][i413]) +- Fixed `PythonEngine.PythonPath` issues ([#179][i179])([#414][i414])([#415][p415]) +- Fixed missing information on 'No method matches given arguments' by adding the method name ### Removed -- Removed `six` dependency for `unittests` ([#329][p329]) -- Removed `Mono.Unix` dependency for `UCS4` ([#360][p360]) -- Removed need for `Python.Runtime.dll.config` -- Removed PY32 build option `PYTHON_WITH_WIDE_UNICODE` ([#417][i417]) +- Removed `six` dependency for `unittests` ([#329][p329]) +- Removed `Mono.Unix` dependency for `UCS4` ([#360][p360]) +- Removed need for `Python.Runtime.dll.config` +- Removed PY32 build option `PYTHON_WITH_WIDE_UNICODE` ([#417][i417]) ## [2.2.2][] - 2017-01-29 ### Fixed -- Missing files from packaging ([#336][i336]) +- Missing files from packaging ([#336][i336]) ## [2.2.1][] - 2017-01-26 -- `v2.2.0` had a release issue on PyPi. Bumped to `v2.2.1` +- `v2.2.0` had a release issue on PyPi. Bumped to `v2.2.1` ### Added -- Python 3.6 support ([#310][p310]) -- Added `__version__` to module ([#312][p312]) -- Added `conda` recipe ([#281][p281]) -- Nuget update on build ([#268][p268]) -- Added `__cause__` attribute on exception ([#287][p287]) +- Python 3.6 support ([#310][p310]) +- Added `__version__` to module ([#312][p312]) +- Added `conda` recipe ([#281][p281]) +- Nuget update on build ([#268][p268]) +- Added `__cause__` attribute on exception ([#287][p287]) ### Changed -- License to MIT ([#314][p314]) -- Project clean-up ([#320][p320]) -- Refactor `#if` directives -- Rename Decref/Incref to XDecref/XIncre ([#275][p275]) -- Remove printing if Decref is called with NULL ([#275][p275]) +- License to MIT ([#314][p314]) +- Project clean-up ([#320][p320]) +- Refactor `#if` directives +- Rename Decref/Incref to XDecref/XIncre ([#275][p275]) +- Remove printing if Decref is called with NULL ([#275][p275]) ### Removed -- Python 2.6 support ([#270][i270]) -- Python 3.2 support ([#270][i270]) +- Python 2.6 support ([#270][i270]) +- Python 3.2 support ([#270][i270]) ### Fixed -- Fixed `isinstance` refcount_leak ([#273][p273]) -- Comparison Operators ([#294][p294]) -- Improved Linux support ([#300][p300]) -- Exception pickling ([#286][p286]) +- Fixed `isinstance` refcount_leak ([#273][p273]) +- Comparison Operators ([#294][p294]) +- Improved Linux support ([#300][p300]) +- Exception pickling ([#286][p286]) ## [2.2.0-dev1][] - 2016-09-19 ### Changed -- Switch to C# 6.0 ([#219][p219]) -- `setup.py` improvements for locating build tools ([#208][p208]) -- unmanaged exports updated ([#206][p206]) -- Mono update pinned to 4.2.4.4 ([#233][p233]) +- Switch to C# 6.0 ([#219][p219]) +- `setup.py` improvements for locating build tools ([#208][p208]) +- unmanaged exports updated ([#206][p206]) +- Mono update pinned to 4.2.4.4 ([#233][p233]) ### Fixed -- Fixed relative imports ([#219][p219]) -- Fixed recursive types ([#250][p250]) -- Demo fix - stream reading ([#225][p225]) +- Fixed relative imports ([#219][p219]) +- Fixed recursive types ([#250][p250]) +- Demo fix - stream reading ([#225][p225]) ## [2.1.0][] - 2016-04-12 ### Added -- Added Python 3.2 support. ([#78][p78]) -- Added Python 3.3 support. ([#78][p78]) -- Added Python 3.4 support. ([#78][p78]) -- Added Python 3.5 support. ([#163][p163]) -- Managed types can be sub-classed in Python ([#78][p78]) -- Uses dynamic objects for cleaner code when embedding Python ([#78][p78]) +- Added Python 3.2 support. ([#78][p78]) +- Added Python 3.3 support. ([#78][p78]) +- Added Python 3.4 support. ([#78][p78]) +- Added Python 3.5 support. ([#163][p163]) +- Managed types can be sub-classed in Python ([#78][p78]) +- Uses dynamic objects for cleaner code when embedding Python ([#78][p78]) ### Changed -- Better Linux support (with or without --enable-shared option) ([#78][p78]) +- Better Linux support (with or without --enable-shared option) ([#78][p78]) ### Removed -- Implicit Type Casting ([#131][i131]) +- Implicit Type Casting ([#131][i131]) ## [2.0.0][] - 2015-06-26 -- Release +- Release ## 2.0.0-alpha.2 ### Changed -- First work on Python 2.5 compatibility. The destination version can be +- First work on Python 2.5 compatibility. The destination version can be set by defining PYTHON24 or PYTHON25. Python 2.6 compatibility is in work. -- Added VS 2005 solution and project files including a UnitTest +- Added VS 2005 solution and project files including a UnitTest configuration which runs the unit test suite. -- Enhanced unit test suite. All test cases are combined in a single +- Enhanced unit test suite. All test cases are combined in a single test suite now. -- Fixed bugs in generics support for all Python versions. +- Fixed bugs in generics support for all Python versions. -- Fixed exception bugs for Python 2.5+. When compiled for Python 2.5+ all +- Fixed exception bugs for Python 2.5+. When compiled for Python 2.5+ all managed exceptions are based on Python's `exceptions.Exception` class. -- Added deprecation warnings for importing from `CLR.*` and the CLR module. +- Added deprecation warnings for importing from `CLR.*` and the CLR module. -- Implemented support for methods with variable arguments +- Implemented support for methods with variable arguments `spam(params object[] egg)` -- Fixed Mono support by adding a custom marshaler for UCS-4 unicode, +- Fixed Mono support by adding a custom marshaler for UCS-4 unicode, fixing a some ref counter bugs and creating a new makefile.mono. -- Added a standard python extension to load the clr environment. +- Added a standard python extension to load the clr environment. The `src/monoclr/` directory contains additional sample code like a Python binary linked against `libpython2.x.so` and some example code how to embed Mono and PythonNet in a C application. -- Added yet another python prompt. This time it's a C application that +- Added yet another python prompt. This time it's a C application that embedds both Python and Mono. It may be useful as an example app for others and I need it to debug a nasty bug. -- Implemented `ModuleFunctionAttribute` and added +- Implemented `ModuleFunctionAttribute` and added `ForbidPythonThreadsAttribute`. The latter is required for module functions which invoke Python methods. -- Added `clr.setPreload()`, `clr.getPreload()`, +- Added `clr.setPreload()`, `clr.getPreload()`, `clr.AddReference("assembly name")`, `clr.FindAssembly("name")` and `clr.ListAssemblies(verbose)`. Automatic preloading can be enabled with clr.setPreload/True). Preloading is automatically enabled for interactive Python shells and disabled in all other cases. -- New Makefile that works for Windows and Mono and autodetects the Python +- New Makefile that works for Windows and Mono and autodetects the Python version and UCS 2/4 setting. -- Added code for Python 2.3. PythonNet can be build for Python 2.3 again +- Added code for Python 2.3. PythonNet can be build for Python 2.3 again but it is not fully supported. -- Changed the PythonException.Message value so it displays the name of +- Changed the PythonException.Message value so it displays the name of the exception class `Exception` instead of its representation ``. -- Added `Python.Runtime.dll.config`. +- Added `Python.Runtime.dll.config`. ## 2.0.0-alpha.1 ### Changed -- Moved the Python for .NET project to Sourceforge and moved version +- Moved the Python for .NET project to Sourceforge and moved version control to Subversion. -- Removed `CallConvCdecl` attributes and the IL hack that they supported. +- Removed `CallConvCdecl` attributes and the IL hack that they supported. .NET 2.x now supports `UnmanagedFunctionPointer`, which does the right thing without the hackery required in 1.x. This removes a dependency on ILASM to build the package and better supports Mono (in theory). -- Refactored import and assembly management machinery. The old `CLR.` +- Refactored import and assembly management machinery. The old `CLR.` syntax for import is deprecated, but still supported until 3.x. The recommended style now is to use `from System import xxx`, etc. We also now support `from X import *` correctly. -- Implemented a (lowercase) `clr` module to match IronPython for code +- Implemented a (lowercase) `clr` module to match IronPython for code compatibility. Methods of this module should be used to explicitly load assemblies. Implicit (name-based) assembly loading will still work until 3.x, but it is deprecated. -- Implemented support for generic types and generic methods using the +- Implemented support for generic types and generic methods using the same patterns and syntax as IronPython. See the documentation for usage details. -- Many small and large performance improvements, switched to generic +- Many small and large performance improvements, switched to generic collections for some internals, better algorithms for assembly scanning, etc. -- Fixed an unboxing issue in generated delegate implementation code +- Fixed an unboxing issue in generated delegate implementation code that affected delegates that return value types. ## [1.0.0][] - 2006-04-08 ### Changed -- Backported the refactored import and assembly management from the 2.x +- Backported the refactored import and assembly management from the 2.x line, mainly to improve the possibility of code-compatibility with IronPython. @@ -582,48 +606,48 @@ This version improves performance on benchmarks significantly compared to 2.3. ### Changed -- Changed some uses of Finalize as a static method name that confused the +- Changed some uses of Finalize as a static method name that confused the Mono compiler and people reading the code. Note that this may be a breaking change if anyone was calling `PythonEngine.Finalize()`. If so, you should now use `PythonEngine.Shutdown()`. -- Tweaked assembly lookup to ensure that assemblies can be found in the +- Tweaked assembly lookup to ensure that assemblies can be found in the current working directory, even after changing directories using things like `os.chdir()` from Python. -- Fixed some incorrect finalizers (thanks to Greg Chapman for the report) +- Fixed some incorrect finalizers (thanks to Greg Chapman for the report) that may have caused some threading oddities. -- Tweaked support for out and ref parameters. If a method has a return +- Tweaked support for out and ref parameters. If a method has a return type of void and a single ref or out parameter, that parameter will be returned as the result of the method. This matches the current behavior of IronPython and makes it more likely that code can be moved between Python for .NET and IP in the future. -- Refactored part of the assembly manager to remove a potential case of +- Refactored part of the assembly manager to remove a potential case of thread-deadlock in multi-threaded applications. -- Added a `__str__` method to managed exceptions that returns the Message +- Added a `__str__` method to managed exceptions that returns the Message attribute of the exception and the StackTrace (if available). ## 1.0.0-rc.1 ### Changed -- Implemented a workaround for the fact that exceptions cannot be new-style +- Implemented a workaround for the fact that exceptions cannot be new-style classes in the CPython interpreter. Managed exceptions can now be raised and caught naturally from Python (hooray!) -- Implemented support for invoking methods with out and ref parameters. +- Implemented support for invoking methods with out and ref parameters. Because there is no real equivalent to these in Python, methods that have out or ref parameters will return a tuple. The tuple will contain the result of the method as its first item, followed by out parameter values in the order of their declaration in the method signature. -- Fixed a refcount problem that caused a crash when CLR was imported in +- Fixed a refcount problem that caused a crash when CLR was imported in an existing installed Python interpreter. -- Added an automatic conversion from Python strings to `byte[]`. This makes +- Added an automatic conversion from Python strings to `byte[]`. This makes it easier to pass `byte[]` data to managed methods (or set properties, etc.) as a Python string without having to write explicit conversion code. Also works for sbyte arrays. Note that `byte` and `sbyte` arrays @@ -631,66 +655,66 @@ This version improves performance on benchmarks significantly compared to 2.3. do _not_ get converted to Python strings - they remain instances of `Byte[]` or `SByte[]`. -- Added conversion of generic Python sequences to object arrays when +- Added conversion of generic Python sequences to object arrays when appropriate (thanks to Mackenzie Straight for the patch). -- Added a bit of cautionary documentation for embedders, focused on +- Added a bit of cautionary documentation for embedders, focused on correct handling of the Python global interpreter lock from managed code for code that calls into Python. -- `PyObject.FromManagedObject` now correctly returns the Python None object +- `PyObject.FromManagedObject` now correctly returns the Python None object if the input is a null reference. Also added a new `AsManagedObject` method to `PyObject`, making it easier to convert a Python-wrapped managed object to the real managed object. -- Created a simple installer for windows platforms. +- Created a simple installer for windows platforms. ## 1.0.0-beta.5 ### Changed -- Refactored and fixed threading and global interpreter lock handling, +- Refactored and fixed threading and global interpreter lock handling, which was badly broken before. Also added a number of threading and GIL-handling tests. -- Related to the GIL fixes, added a note to embedders in the README +- Related to the GIL fixes, added a note to embedders in the README about using the AcquireLock and ReleaseLock methods of the PythonEngine class to manage the GIL. -- Fixed a problem in `Single <--> float` conversion for cultures that use +- Fixed a problem in `Single <--> float` conversion for cultures that use different decimal symbols than Python. -- Added a new `ReloadModule` method to the `PythonEngine` class that hooks +- Added a new `ReloadModule` method to the `PythonEngine` class that hooks Python module reloading (`PyImport_ReloadModule`). -- Added a new `StringAsModule` method to the PythonEngine class that can +- Added a new `StringAsModule` method to the PythonEngine class that can create a module from a managed string of code. -- Added a default `__str__` implementation for Python wrappers of managed +- Added a default `__str__` implementation for Python wrappers of managed objects that calls the `ToString` method of the managed object. ## 1.0.0-beta.4 ### Changed -- Fixed a problem that made it impossible to override "special" methods +- Fixed a problem that made it impossible to override "special" methods like `__getitem__` in subclasses of managed classes. Now the tests all pass, and there is much rejoicing. -- Managed classes reflected to Python now have an `__doc__` attribute that +- Managed classes reflected to Python now have an `__doc__` attribute that contains a listing of the class constructor signatures. -- Fixed a problem that prevented passing null (None) for array arguments. +- Fixed a problem that prevented passing null (None) for array arguments. -- Added a number of new argument conversion tests. Thanks to Laurent +- Added a number of new argument conversion tests. Thanks to Laurent Caumont for giving Python for .NET a good workout with managed DirectX. -- Updated the bundled C Python runtime and libraries to Python 2.4. The +- Updated the bundled C Python runtime and libraries to Python 2.4. The current release is known to also run with Python 2.3. It is known _not_ to work with older versions due to changes in CPython type object structure. -- Mostly fixed the differences in the way that import works depending +- Mostly fixed the differences in the way that import works depending on whether you are using the bundled interpreter or an existing Python interpreter. The hack I used makes import work uniformly for imports done in Python modules. Unfortunately, there is still a limitation @@ -703,21 +727,21 @@ This version improves performance on benchmarks significantly compared to 2.3. in control for the duration of that first import, so sub-names won't be found until the next import, which will use the now-installed hook. -- Added support to directly iterate over objects that support IEnumerator +- Added support to directly iterate over objects that support IEnumerator (as well as IEnumerable). Thanks to Greg Chapman for prodding me ;) -- Added a section to the README dealing with rebuilding Python for .NET +- Added a section to the README dealing with rebuilding Python for .NET against other CPython versions. -- Fixed a problem with accessing properties when only the interface for +- Fixed a problem with accessing properties when only the interface for an object is known. For example, `ICollection(ob).Count` failed because Python for .NET mistakenly decided that Count was abstract. -- Fixed some problems with how COM-based objects are exposed and how +- Fixed some problems with how COM-based objects are exposed and how members of inherited interfaces are exposed. Thanks to Bruce Dodson for patches on this. -- Changed the Runtime class to use a const string to target the +- Changed the Runtime class to use a const string to target the appropriate CPython dll in DllImport attributes. Now you only have to change one line to target a new Python version. @@ -725,7 +749,7 @@ This version improves performance on benchmarks significantly compared to 2.3. ### Changed -- A dumb bug that could cause a crash on startup on some platforms was +- A dumb bug that could cause a crash on startup on some platforms was fixed. Decided to update the beta for this, as a number of people were running into the problem. @@ -733,12 +757,12 @@ This version improves performance on benchmarks significantly compared to 2.3. ### Changed -- Exceptions raised as a result of getting or setting properties were +- Exceptions raised as a result of getting or setting properties were not very helpful (target invokation exception). This has been changed to pass through the inner exception the way that methods do, which is much more likely to be the real exception that caused the problem. -- Events were refactored as the implementation was based on some bad +- Events were refactored as the implementation was based on some bad assumptions. As a result, subscription and unsubscription now works correctly. A change from beta 1 is that event objects are no longer directly callable - this was not appropriate, since the internal @@ -746,21 +770,21 @@ This version improves performance on benchmarks significantly compared to 2.3. you should the appropriate `OnSomeEvent` method published by a class to fire an event. -- The distribution did not include the key file, making it a pain for +- The distribution did not include the key file, making it a pain for people to build from source. Added the key file to the distribution buildout for beta 2. -- Assemblies can now be found and loaded if they are on the PYTHONPATH. +- Assemblies can now be found and loaded if they are on the PYTHONPATH. Previously only the appbase and the GAC were checked. The system now checks PYTHONPATH first, then the appbase, then the GAC. -- Fixed a bug in constructor invokation during object instantiation. +- Fixed a bug in constructor invokation during object instantiation. ## 1.0.0-beta.1 ### Changed -- Added the baseline of the managed embedding API. Some of the details +- Added the baseline of the managed embedding API. Some of the details are still subject to change based on some real-world use and feedback. The embedding API is based on the `PyObject` class, along with a number @@ -769,117 +793,115 @@ This version improves performance on benchmarks significantly compared to 2.3. is intended be familar to anyone who has used Python / C++ wrapper libraries like CXX or Boost. -- Started integrating NUnit2 to support unit tests for the embedding +- Started integrating NUnit2 to support unit tests for the embedding layer - still need to add the embedding tests (many already exist, but were written for an older version of NUnit). -- Added Python iteration protocol support for arrays and managed objects +- Added Python iteration protocol support for arrays and managed objects that implement IEnumerable. This means that you can now use the Python idiom `for item in object:` on any array or IEnumerable object. -- Added automatic conversion from Python sequence types to managed array +- Added automatic conversion from Python sequence types to managed array types. This means, for example, that you can now call a managed method like AddRange that expects an array with any Python object that supports the Python sequence protocol, provided the items of the sequence are convertible to the item type of the managed array. -- Added new demo scripts, mostly more substantial winforms examples. +- Added new demo scripts, mostly more substantial winforms examples. -- Finished the unit tests for event support, and fixed lots of problems +- Finished the unit tests for event support, and fixed lots of problems with events and delegates as a result. This is one of the trickier parts of the integration layer, and there is good coverage of these in the unit tests now. -- Did a fair amount of profiling with an eval version of ANTS (which is +- Did a fair amount of profiling with an eval version of ANTS (which is quite nice, BTW) and made a few changes as a result. -- Type management was refactored, fixing the issue that caused segfaults +- Type management was refactored, fixing the issue that caused segfaults when GC was enabled. Unit tests, stress tests and demo apps now all run nicely with Python GC enabled. There are one or two things left to fix, but the fixes should not have any user impact. -- Changed to base PythonNet on Python 2.3.2. This is considered the most +- Changed to base PythonNet on Python 2.3.2. This is considered the most stable release, and a good 25 - 30% faster as well. -- Added a new `CLR.dll` that acts as an extension module that allows an +- Added a new `CLR.dll` that acts as an extension module that allows an existing unmodified Python 2.3 installation to simply `import CLR` to bootstrap the managed integration layer. -- A bug was causing managed methods to only expose overloads declared in +- A bug was causing managed methods to only expose overloads declared in a particular class, hiding inherited overloads of the same name. Fixed the bug and added some unit tests. -- Added a virtual `__doc__` attribute to managed methods that contains +- Added a virtual `__doc__` attribute to managed methods that contains the signature of the method. This also means that the Python `help` function now provides signature info when used on a managed class. -- Calling managed methods and events `unbound` (passing the instance as +- Calling managed methods and events `unbound` (passing the instance as the first argument) now works. There is a caveat for methods - if a class declares both static and instance methods with the same name, it is not possible to call that instance method unbound (the static method will always be called). -- Overload selection for overloaded methods is now much better and uses +- Overload selection for overloaded methods is now much better and uses a method resolution algorithm similar to that used by Jython. -- Changed the managed python.exe wrapper to run as an STA thread, which +- Changed the managed python.exe wrapper to run as an STA thread, which seems to be more compatible with winforms apps. This needs a better solution long-term. One possibility would be a command line switch so that -sta or -mta could control the python.exe apartment state. -- Added support for the Python boolean type (True, False). Bool values +- Added support for the Python boolean type (True, False). Bool values now appear as True or False to Python. ## 1.0.0-alpha.2 ### Changed -- Added a Mono makefile. Thanks to Camilo Uribe for help in testing and +- Added a Mono makefile. Thanks to Camilo Uribe for help in testing and working out problems on Mono. Note that it not currently possible to build PythonNet using mono, due to the use of some IL attributes that the mono assembler / disassembler doesn't support yet. -- Preliminary tests show that PythonNet _does_ actually run under mono, +- Preliminary tests show that PythonNet _does_ actually run under mono, though the test suite bombs out before the end with an "out of memory" error from the mono runtime. It's just a guess at this point, but I suspect there may be a limited pool for allocating certain reflection structures, and Python uses the reflection infrastructure quite heavily. -- Removed decoys like the non-working embedding APIs; lots of internal +- Removed decoys like the non-working embedding APIs; lots of internal refactoring. -- Implemented indexer support. Managed instances that implement indexers +- Implemented indexer support. Managed instances that implement indexers can now be used naturally from Python (e.g. `someobject[0]`). -- Implemented sequence protocol support for managed arrays. +- Implemented sequence protocol support for managed arrays. -- Implemented basic thread state management; calls to managed methods +- Implemented basic thread state management; calls to managed methods no longer block Python. I won't go so far as to say the thread choreography is "finished", as I don't have a comprehensive set of tests to back that up yet (and it will take some work to write a sufficiently large and evil set of tests). -- Fixed a bug that caused conversions of managed strings to PyUnicode to +- Fixed a bug that caused conversions of managed strings to PyUnicode to produce mangled values in certain situations. -- Fixed a number of problems related to subclassing a managed class, +- Fixed a number of problems related to subclassing a managed class, including the fact that it didn't work :) -- Fixed all of the bugs that were causing tests to fail. This release +- Fixed all of the bugs that were causing tests to fail. This release contains all new bugs and new failing tests. Progress! :) ## 1.0.0-alpha.1 ### Added -- Initial (mostly) working experimental release. +- Initial (mostly) working experimental release. [keep a changelog]: http://keepachangelog.com/ [semantic versioning]: http://semver.org/ -[unreleased]: ../../compare/v3.0.1...HEAD - [2.3.0]: ../../compare/v2.2.2...v2.3.0 [2.2.2]: ../../compare/v2.2.1...v2.2.2 @@ -904,7 +926,6 @@ This version improves performance on benchmarks significantly compared to 2.3. [p433]: https://github.com/pythonnet/pythonnet/pull/433 [p460]: https://github.com/pythonnet/pythonnet/pull/460 [p461]: https://github.com/pythonnet/pythonnet/pull/461 -[p433]: https://github.com/pythonnet/pythonnet/pull/433 [i434]: https://github.com/pythonnet/pythonnet/issues/434 [i481]: https://github.com/pythonnet/pythonnet/issues/481 [i486]: https://github.com/pythonnet/pythonnet/issues/486 @@ -929,7 +950,6 @@ This version improves performance on benchmarks significantly compared to 2.3. [i390]: https://github.com/pythonnet/pythonnet/issues/390 [i319]: https://github.com/pythonnet/pythonnet/issues/319 [p398]: https://github.com/pythonnet/pythonnet/pull/398 -[p345]: https://github.com/pythonnet/pythonnet/pull/345 [p389]: https://github.com/pythonnet/pythonnet/pull/389 [p407]: https://github.com/pythonnet/pythonnet/pull/407 [i322]: https://github.com/pythonnet/pythonnet/issues/322 @@ -940,9 +960,7 @@ This version improves performance on benchmarks significantly compared to 2.3. [p369]: https://github.com/pythonnet/pythonnet/pull/369 [p368]: https://github.com/pythonnet/pythonnet/pull/368 [p341]: https://github.com/pythonnet/pythonnet/pull/341 -[p353]: https://github.com/pythonnet/pythonnet/pull/353 [p371]: https://github.com/pythonnet/pythonnet/pull/371 -[p345]: https://github.com/pythonnet/pythonnet/pull/345 [i362]: https://github.com/pythonnet/pythonnet/issues/362 [i401]: https://github.com/pythonnet/pythonnet/issues/401 [i262]: https://github.com/pythonnet/pythonnet/issues/262 @@ -950,7 +968,6 @@ This version improves performance on benchmarks significantly compared to 2.3. [p365]: https://github.com/pythonnet/pythonnet/pull/365 [i203]: https://github.com/pythonnet/pythonnet/issues/203 [p377]: https://github.com/pythonnet/pythonnet/pull/377 -[p398]: https://github.com/pythonnet/pythonnet/pull/398 [p400]: https://github.com/pythonnet/pythonnet/pull/400 [i397]: https://github.com/pythonnet/pythonnet/issues/397 [p399]: https://github.com/pythonnet/pythonnet/pull/399 @@ -959,7 +976,6 @@ This version improves performance on benchmarks significantly compared to 2.3. [i179]: https://github.com/pythonnet/pythonnet/issues/179 [i414]: https://github.com/pythonnet/pythonnet/issues/414 [p415]: https://github.com/pythonnet/pythonnet/pull/415 -[p329]: https://github.com/pythonnet/pythonnet/pull/329 [p360]: https://github.com/pythonnet/pythonnet/pull/360 [i417]: https://github.com/pythonnet/pythonnet/issues/417 [i336]: https://github.com/pythonnet/pythonnet/issues/336 @@ -980,7 +996,6 @@ This version improves performance on benchmarks significantly compared to 2.3. [p208]: https://github.com/pythonnet/pythonnet/pull/208 [p206]: https://github.com/pythonnet/pythonnet/pull/206 [p233]: https://github.com/pythonnet/pythonnet/pull/233 -[p219]: https://github.com/pythonnet/pythonnet/pull/219 [p250]: https://github.com/pythonnet/pythonnet/pull/250 [p225]: https://github.com/pythonnet/pythonnet/pull/225 [p78]: https://github.com/pythonnet/pythonnet/pull/78 @@ -994,6 +1009,5 @@ This version improves performance on benchmarks significantly compared to 2.3. [i1342]: https://github.com/pythonnet/pythonnet/issues/1342 [i238]: https://github.com/pythonnet/pythonnet/issues/238 [i1481]: https://github.com/pythonnet/pythonnet/issues/1481 -[i1672]: https://github.com/pythonnet/pythonnet/pull/1672 [i2311]: https://github.com/pythonnet/pythonnet/issues/2311 [i1977]: https://github.com/pythonnet/pythonnet/issues/1977 diff --git a/Directory.Build.props b/Directory.Build.props index 85e4039b9..377db2ff5 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,16 +4,21 @@ Copyright (c) 2006-2025 The Contributors of the Python.NET Project pythonnet Python.NET - 12.0 + 14 false + true $([System.IO.File]::ReadAllText("$(MSBuildThisFileDirectory)version.txt").Trim()) $(FullVersion.Split('-', 2)[0]) $(FullVersion.Split('-', 2)[1]) $(MSBuildThisFileDirectory) + + False + None + - - + + all runtime; build; native; contentfiles; analyzers diff --git a/Justfile b/Justfile new file mode 100644 index 000000000..b5228e2e0 --- /dev/null +++ b/Justfile @@ -0,0 +1,14 @@ +default: + @just --choose + +setup: + dotnet restore + uv sync + +docs: + doxygen doc/Doxyfile + uv run --group doc sphinx-build doc/source/ ./doc/build/html/ + +build-wheels: + uv build + uv build --wheel -C="--global-option=--net46-support" \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in index 71473c2c3..1d4889e25 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,7 @@ graft src/runtime prune src/runtime/obj prune src/runtime/bin +graft src/compat include src/pythonnet.snk include Directory.Build.* include pythonnet.sln diff --git a/doc/Makefile b/doc/Makefile deleted file mode 100644 index d0c3cbf10..000000000 --- a/doc/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = source -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/doc/make.bat b/doc/make.bat deleted file mode 100644 index dc1312ab0..000000000 --- a/doc/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=source -set BUILDDIR=build - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.https://www.sphinx-doc.org/ - exit /b 1 -) - -if "%1" == "" goto help - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/doc/requirements.txt b/doc/requirements.txt deleted file mode 100644 index 8ef3b7159..000000000 --- a/doc/requirements.txt +++ /dev/null @@ -1,12 +0,0 @@ -sphinx - -# Theme, force pygments update -furo>=2022.9.15 -pygments>=2.7 - -# C# via doxygen -breathe -git+https://github.com/rogerbarton/sphinx-csharp.git - -# Dependency of pythonnet, needed for autodocs -clr_loader diff --git a/doc/source/python.rst b/doc/source/python.rst index a9228537c..d39081eba 100644 --- a/doc/source/python.rst +++ b/doc/source/python.rst @@ -45,7 +45,8 @@ Mono (``mono``) .NET Framework (``netfx``) Default on Windows and also only supported there. Must be at least version - 4.7.2. + 4.6.1, with 4.7.2 or later recommended. For .NET 4.6 support, the wheel has + to be built with the environment variable `PYTHONNET_BUILD_NET46_SUPPORT=1`. .NET Core (``coreclr``) Self-contained is not supported, must be at least version 3.1. @@ -479,6 +480,34 @@ Python idioms: for item in domain.GetAssemblies(): name = item.GetName() +Using Context Managers (IDisposable) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.NET types that implement ``IDisposable`` can be used with Python's context manager +protocol using the standard ``with`` statement. This automatically calls the object's +``Dispose()`` method when exiting the ``with`` block: + +.. code:: python + + from System.IO import MemoryStream, StreamWriter + + # Use a MemoryStream as a context manager + with MemoryStream() as stream: + # The stream is automatically disposed when exiting the with block + writer = StreamWriter(stream) + writer.Write("Hello, context manager!") + writer.Flush() + + # Do something with the stream + stream.Position = 0 + # ... + + # After exiting the with block, the stream is disposed + # Attempting to use it here would raise an exception + +This works for any .NET type that implements ``IDisposable``, making resource +management much cleaner and safer in Python code. + Type Conversion --------------- diff --git a/pyproject.toml b/pyproject.toml index e3fba0dda..0966038cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=80"] +requires = ["setuptools>=80", "packaging>=24", "pyproject-parser>=0.14"] build-backend = "setuptools.build_meta" [project] @@ -10,23 +10,21 @@ license = "MIT" readme = "README.rst" dependencies = [ - "clr_loader>=0.2.7,<0.3.0" + "clr_loader>=0.3.1,<0.4.0" ] -requires-python = ">=3.10, <3.14" +requires-python = ">=3.10, <3.15" classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Programming Language :: C#", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Operating System :: MacOS :: MacOS X", @@ -40,7 +38,13 @@ dev = [ "find_libpython >= 0.3", "numpy >=2 ; python_version >= '3.10'", "numpy <2 ; python_version < '3.10'", - "psutil" +] +doc = [ + "sphinx", + "furo>=2025.12.19", + "pygments>=2.20", + "breathe", + "sphinx-csharp @ git+https://github.com/rogerbarton/sphinx-csharp.git", ] [[project.authors]] @@ -51,6 +55,9 @@ email = "pythonnet@python.org" Homepage = "https://pythonnet.github.io/" Sources = "https://github.com/pythonnet/pythonnet" +[project.entry-points.pyinstaller40] +hook-dirs = "pythonnet._pyinstaller:get_hook_dirs" + [tool.setuptools] zip-safe = false py-modules = ["clr"] diff --git a/pythonnet/_pyinstaller/__init__.py b/pythonnet/_pyinstaller/__init__.py new file mode 100644 index 000000000..2ed816a4e --- /dev/null +++ b/pythonnet/_pyinstaller/__init__.py @@ -0,0 +1,4 @@ +import os + +def get_hook_dirs(): + return [os.path.dirname(__file__)] diff --git a/pythonnet/_pyinstaller/hook-clr.py b/pythonnet/_pyinstaller/hook-clr.py new file mode 100644 index 000000000..f0b058681 --- /dev/null +++ b/pythonnet/_pyinstaller/hook-clr.py @@ -0,0 +1,9 @@ +from PyInstaller.utils.hooks import collect_data_files, collect_dynamic_libs + +try: + binaries = collect_dynamic_libs("pythonnet") + datas = collect_data_files("pythonnet") +except Exception: + # name conflict with https://pypi.org/project/clr/, do not crash if just clr is present + binaries = [] + datas = [] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 33f38cca7..000000000 --- a/requirements.txt +++ /dev/null @@ -1,14 +0,0 @@ -# Requirements for both Travis and AppVeyor -pytest -psutil - -# Coverage upload -coverage -codecov - -wheel -pycparser -clr-loader==0.2.* - -# Discover libpython -find_libpython==0.3.* diff --git a/setup.py b/setup.py index 7c02b7710..1c2ffe2fd 100644 --- a/setup.py +++ b/setup.py @@ -3,15 +3,25 @@ import distutils from distutils.command.build import build as _build from setuptools.command.develop import develop as _develop -from wheel.bdist_wheel import bdist_wheel as _bdist_wheel +from setuptools.command.bdist_wheel import bdist_wheel as _bdist_wheel +from packaging.specifiers import SpecifierSet +from packaging.version import Version +from pyproject_parser import PyProject from setuptools import Distribution from setuptools import setup, Command import os +import sys # Disable SourceLink during the build until it can read repo-format v1, #1613 os.environ["EnableSourceControlManagerQueries"] = "false" +NET46_SUPPORT_OPTION = "--net46-support" +NET46_SUPPORT = NET46_SUPPORT_OPTION in sys.argv +if NET46_SUPPORT: + sys.argv.remove(NET46_SUPPORT_OPTION) +WINDOWS_PLATFORM_TAG = "win32.win_amd64" + class DotnetLib: def __init__(self, name, path, **kwargs): @@ -107,11 +117,29 @@ def install_for_development(self): class bdist_wheel(_bdist_wheel): - def finalize_options(self): - # Monkey patch bdist_wheel to think the package is pure even though we - # include DLLs - super().finalize_options() - self.root_is_pure = True + def get_tag(self): + if NET46_SUPPORT: + platform_tag = WINDOWS_PLATFORM_TAG + else: + platform_tag = "any" + abi_tag = "none" + python_tag = self._get_python_tag() + return python_tag, abi_tag, platform_tag + + def _get_python_tag(self) -> str: + pyproject = PyProject.load("pyproject.toml") + project = pyproject.project or {} + + requires_python = project.get("requires-python") + if not requires_python: + raise RuntimeError("project.requires-python is required") + + specifiers = SpecifierSet(str(requires_python)) + return ".".join( + f"cp3{minor}" + for minor in range(0, 100) + if specifiers.contains(Version(f"3.{minor}"), prereleases=True) + ) # Monkey-patch Distribution s.t. it supports the dotnet_libs attribute @@ -124,13 +152,13 @@ def finalize_options(self): "bdist_wheel": bdist_wheel, } -dotnet_libs = [ - DotnetLib( - "python-runtime", - "src/runtime/Python.Runtime.csproj", - output="pythonnet/runtime", - ) -] + +if NET46_SUPPORT: + csproj = "src/compat/Python.Runtime.Compat.csproj" +else: + csproj = "src/runtime/Python.Runtime.csproj" + +dotnet_libs = [DotnetLib("python-runtime", csproj, output="pythonnet/runtime")] setup( cmdclass=cmdclass, diff --git a/src/compat/Python.Runtime.Compat.csproj b/src/compat/Python.Runtime.Compat.csproj new file mode 100644 index 000000000..5c607e157 --- /dev/null +++ b/src/compat/Python.Runtime.Compat.csproj @@ -0,0 +1,13 @@ + + + + net461 + Library + + false + false + + + + + diff --git a/src/embed_tests/Codecs.cs b/src/embed_tests/Codecs.cs index d4d22dcac..6159060b3 100644 --- a/src/embed_tests/Codecs.cs +++ b/src/embed_tests/Codecs.cs @@ -32,7 +32,7 @@ static void TupleConversionsGeneric() scope.Set(nameof(tuple), tuple); scope.Set(nameof(accept), accept); scope.Exec($"{nameof(accept)}({nameof(tuple)})"); - Assert.AreEqual(expected: tuple, actual: restored); + Assert.That(actual: restored, Is.EqualTo(expected: tuple)); } } @@ -53,7 +53,7 @@ static void TupleConversionsObject() scope.Set(nameof(tuple), tuple); scope.Set(nameof(accept), accept); scope.Exec($"{nameof(accept)}({nameof(tuple)})"); - Assert.AreEqual(expected: tuple, actual: restored); + Assert.That(actual: restored, Is.EqualTo(expected: tuple)); } } @@ -67,7 +67,7 @@ static void TupleRoundtripObject() var tuple = Activator.CreateInstance(typeof(T), 42.0, "42", new object()); using var pyTuple = TupleCodec.Instance.TryEncode(tuple); Assert.IsTrue(TupleCodec.Instance.TryDecode(pyTuple, out object restored)); - Assert.AreEqual(expected: tuple, actual: restored); + Assert.That(actual: restored, Is.EqualTo(expected: tuple)); } [Test] @@ -81,7 +81,7 @@ static void TupleRoundtripGeneric() var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); using var pyTuple = TupleCodec.Instance.TryEncode(tuple); Assert.IsTrue(TupleCodec.Instance.TryDecode(pyTuple, out T restored)); - Assert.AreEqual(expected: tuple, actual: restored); + Assert.That(actual: restored, Is.EqualTo(expected: tuple)); } static PyObject GetPythonIterable() => PythonEngine.Eval("map(lambda x: x, [1,2,3])"); @@ -118,7 +118,7 @@ public void ListDecoderTest() //the IList will report a Count of 3. IList stringList = null; Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out stringList); }); - Assert.AreEqual(stringList.Count, 3); + Assert.That(3, Is.EqualTo(stringList.Count)); Assert.Throws(typeof(InvalidCastException), () => { var x = stringList[0]; }); //can't convert python iterable to list (this will require a copy which isn't lossless) @@ -162,8 +162,8 @@ public void SequenceDecoderTest() //the IList will report a Count of 3. ICollection stringCollection = null; Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out stringCollection); }); - Assert.AreEqual(3, stringCollection.Count()); - Assert.Throws(typeof(InvalidCastException), () => { + Assert.That(stringCollection.Count(), Is.EqualTo(3)); + Assert.Throws(() => { string[] array = new string[3]; stringCollection.CopyTo(array, 0); }); @@ -199,8 +199,8 @@ public void SequenceDecoderTest() //the IList will report a Count of 3. ICollection stringCollection2 = null; Assert.DoesNotThrow(() => { codec.TryDecode(pyTuple, out stringCollection2); }); - Assert.AreEqual(3, stringCollection2.Count()); - Assert.Throws(typeof(InvalidCastException), () => { + Assert.That(stringCollection2.Count, Is.EqualTo(3)); + Assert.Throws(() => { string[] array = new string[3]; stringCollection2.CopyTo(array, 0); }); @@ -280,6 +280,7 @@ public void IterableDecoderTest() // regression for https://github.com/pythonnet/pythonnet/issues/1427 [Test] + [Ignore("Broken, the list_encoder object ends up in builtins and fails during GC")] public void PythonRegisteredDecoder_NoStackOverflowOnSystemType() { const string PyCode = @" @@ -320,7 +321,7 @@ def call(func): "); var callFunc = scope.Get("call"); string message = callFunc.Invoke(callMeAction.ToPython()).As(); - Assert.AreEqual(TestExceptionMessage, message); + Assert.That(message, Is.EqualTo(TestExceptionMessage)); } [Test] @@ -330,7 +331,7 @@ public void ExceptionDecoded() using var scope = Py.CreateScope(); var error = Assert.Throws(() => PythonEngine.Exec($"raise ValueError('{TestExceptionMessage}')")); - Assert.AreEqual(TestExceptionMessage, error.Message); + Assert.That(error.Message, Is.EqualTo(TestExceptionMessage)); } [Test] @@ -359,7 +360,7 @@ public void FloatDerivedDecoded() PyObjectConversions.RegisterDecoder(decoder); using var result = scope.Eval("FloatDerived()"); object decoded = result.As(); - Assert.AreEqual(42, decoded); + Assert.That(decoded, Is.EqualTo(42)); } [Test] @@ -373,7 +374,7 @@ public void ExceptionDecodedNoInstance() var error = Assert.Throws(() => PythonEngine.Exec($"[].__iter__().__next__()") ); - Assert.AreEqual(TestExceptionMessage, error.Message); + Assert.That(error.Message, Is.EqualTo(TestExceptionMessage)); } else { @@ -394,7 +395,7 @@ public void As_Object_AffectedByDecoders() var pyObj = PythonEngine.Eval("iter"); var decoded = pyObj.As(); - Assert.AreSame(everythingElseToSelf, decoded); + Assert.That(decoded, Is.SameAs(everythingElseToSelf)); } public class EverythingElseToSelfDecoder : IPyObjectDecoder diff --git a/src/embed_tests/Dynamic.cs b/src/embed_tests/Dynamic.cs index 174167118..9d35bfacc 100644 --- a/src/embed_tests/Dynamic.cs +++ b/src/embed_tests/Dynamic.cs @@ -19,12 +19,12 @@ public void AssignObject() sys.testattr = stream; // Check whether there are the same object. dynamic _stream = sys.testattr.AsManagedObject(typeof(StringBuilder)); - Assert.AreEqual(_stream, stream); + Assert.That(_stream, Is.EqualTo(stream)); PythonEngine.RunSimpleString( "import sys\n" + "sys.testattr.Append('Hello!')\n"); - Assert.AreEqual(stream.ToString(), "Hello!"); + Assert.That(stream.ToString(), Is.EqualTo("Hello!")); } /// @@ -45,25 +45,15 @@ public void AssignNone() /// Check whether we can get the attr of a python object when the /// value of attr is a PyObject. /// - /// - /// FIXME: Issue on Travis PY27: Error : Python.EmbeddingTest.dynamicTest.AssignPyObject - /// Python.Runtime.PythonException : ImportError : /home/travis/virtualenv/python2.7.9/lib/python2.7/lib-dynload/_io.so: undefined symbol: _PyLong_AsInt - /// [Test] public void AssignPyObject() { - if (Environment.GetEnvironmentVariable("TRAVIS") == "true" && - Environment.GetEnvironmentVariable("TRAVIS_PYTHON_VERSION") == "2.7") - { - Assert.Ignore("Fails on Travis/PY27: ImportError: ... undefined symbol: _PyLong_AsInt"); - } - dynamic sys = Py.Import("sys"); dynamic io = Py.Import("io"); sys.testattr = io.StringIO(); dynamic bb = sys.testattr; // Get the PyObject bb.write("Hello!"); - Assert.AreEqual(bb.getvalue().ToString(), "Hello!"); + Assert.That(bb.getvalue().ToString(), Is.EqualTo("Hello!")); } /// @@ -87,7 +77,7 @@ public void PassObjectInPython() "import sys\n" + "sys.testattr3 = sys.testattr1 is sys.testattr2\n" ); - Assert.AreEqual(sys.testattr3.ToString(), "True"); + Assert.That(sys.testattr3.ToString(), Is.EqualTo("True")); // Compare in .NET Assert.IsTrue(sys.testattr1.Equals(sys.testattr2)); @@ -110,7 +100,7 @@ public void PassPyObjectInNet() "sys.testattr3 = sys.testattr1 is sys.testattr2\n" ); - Assert.AreEqual(sys.testattr3.ToString(), "True"); + Assert.That(sys.testattr3.ToString(), Is.EqualTo("True")); // Compare in .NET Assert.IsTrue(sys.testattr1.Equals(sys.testattr2)); diff --git a/src/embed_tests/Events.cs b/src/embed_tests/Events.cs index 94a30726b..d2f43ea50 100644 --- a/src/embed_tests/Events.cs +++ b/src/embed_tests/Events.cs @@ -31,7 +31,7 @@ del example gc.collect() "); Runtime.Runtime.TryCollectingGarbage(10); - Assert.AreEqual(0, ClassWithEventHandler.alive); + Assert.That(ClassWithEventHandler.alive, Is.EqualTo(0)); } } @@ -48,6 +48,12 @@ public ClassWithEventHandler() this.arr = new int[800]; } + // Reference LeakEvent to silence warning + protected virtual void OnLeakEvent(EventArgs e) + { + LeakEvent?.Invoke(this, e); + } + ~ClassWithEventHandler() { Interlocked.Decrement(ref alive); diff --git a/src/embed_tests/Modules.cs b/src/embed_tests/Modules.cs index 67fa3d0fc..f06c5173e 100644 --- a/src/embed_tests/Modules.cs +++ b/src/embed_tests/Modules.cs @@ -149,11 +149,11 @@ public void TestScopeClass() ); dynamic obj1 = _ps.Class1(20); var result = obj1.call(10).As(); - Assert.AreEqual(130, result); + Assert.That(result, Is.EqualTo(130)); obj1.update(10); result = ps.Get("bb"); - Assert.AreEqual(30, result); + Assert.That(result, Is.EqualTo(30)); } } @@ -185,11 +185,11 @@ public void TestCreateVirtualPackageStructure() dynamic obj1 = ps2.Class1(20); var result = obj1.call(10).As(); - Assert.AreEqual(130, result); + Assert.That(result, Is.EqualTo(130)); obj1.update(10); result = ps2.Get("bb"); - Assert.AreEqual(30, result); + Assert.That(result, Is.EqualTo(30)); } } @@ -229,7 +229,7 @@ public void TestImportModule() var value1 = ps.Eval("sys.attr1"); var value2 = sys.attr1.As(); Assert.That(value1, Is.EqualTo(2)); - Assert.AreEqual(2, value2); + Assert.That(value2, Is.EqualTo(2)); //import as ps.Import("sys", "sys1"); @@ -308,16 +308,16 @@ public void TestImportScopeFunction() dynamic func2 = scope.Get("func2"); var result1 = func2().As(); - Assert.AreEqual(0, result1); + Assert.That(result1, Is.EqualTo(0)); scope.Set("cc", 20);//it has no effect on the globals of 'func1' var result2 = func2().As(); - Assert.AreEqual(-10, result2); + Assert.That(result2, Is.EqualTo(-10)); scope.Set("cc", 10); //rollback ps.Set("cc", 20); var result3 = func2().As(); - Assert.AreEqual(10, result3); + Assert.That(result3, Is.EqualTo(10)); ps.Set("cc", 10); //rollback } } @@ -437,7 +437,7 @@ public void TestCreate() scope.Execute(code); Assert.That(scope.TryGet("x", out dynamic x), Is.True); - Assert.AreEqual("True", x.ToString()); + Assert.That(x.ToString(), Is.EqualTo("True")); } [Test] diff --git a/src/embed_tests/NeedsReinit/TestDomainReload.cs b/src/embed_tests/NeedsReinit/TestDomainReload.cs index a8d2cd3d8..417a915a8 100644 --- a/src/embed_tests/NeedsReinit/TestDomainReload.cs +++ b/src/embed_tests/NeedsReinit/TestDomainReload.cs @@ -146,8 +146,8 @@ public override ValueType Execute(ValueType arg) "); } var clrObj = obj.As(); - Assert.AreEqual(clrObj.Property, 2); - Assert.AreEqual(clrObj.Field, 20); + Assert.That(2, Is.EqualTo(clrObj.Property)); + Assert.That(20, Is.EqualTo(clrObj.Field)); } } } diff --git a/src/embed_tests/NeedsReinit/TestPyInitialize.cs b/src/embed_tests/NeedsReinit/TestPyInitialize.cs index 1ef4127b8..b724e158e 100644 --- a/src/embed_tests/NeedsReinit/TestPyInitialize.cs +++ b/src/embed_tests/NeedsReinit/TestPyInitialize.cs @@ -28,7 +28,7 @@ public static void LoadDefaultArgs() { using(var argv = new PyList(Runtime.Runtime.PySys_GetObject("argv"))) { - Assert.AreNotEqual(0, argv.Length()); + Assert.That(argv.Length(), Is.Not.EqualTo(0)); } } } diff --git a/src/embed_tests/NeedsReinit/TestPythonEngineProperties.cs b/src/embed_tests/NeedsReinit/TestPythonEngineProperties.cs index 8eb9e975d..0e99005ae 100644 --- a/src/embed_tests/NeedsReinit/TestPythonEngineProperties.cs +++ b/src/embed_tests/NeedsReinit/TestPythonEngineProperties.cs @@ -22,7 +22,7 @@ public void SetPythonHome() PythonEngine.PythonHome = pythonHome; PythonEngine.Initialize(); - Assert.AreEqual(pythonHome, PythonEngine.PythonHome); + Assert.That(PythonEngine.PythonHome, Is.EqualTo(pythonHome)); PythonEngine.Shutdown(); // Restoring valid pythonhome. @@ -45,7 +45,7 @@ public void SetPythonHomeTwice() PythonEngine.PythonHome = pythonHome; PythonEngine.Initialize(); - Assert.AreEqual(pythonHome, PythonEngine.PythonHome); + Assert.That(PythonEngine.PythonHome, Is.EqualTo(pythonHome)); PythonEngine.Shutdown(); PythonEngine.PythonHome = pythonHomeBackup; @@ -65,7 +65,7 @@ public void SetPythonHomeEmptyString() } PythonEngine.PythonHome = ""; - Assert.AreEqual("", PythonEngine.PythonHome); + Assert.That(PythonEngine.PythonHome, Is.EqualTo("")); PythonEngine.PythonHome = backup; PythonEngine.Shutdown(); @@ -86,7 +86,7 @@ public void SetProgramName() PythonEngine.ProgramName = programName; PythonEngine.Initialize(); - Assert.AreEqual(programName, PythonEngine.ProgramName); + Assert.That(PythonEngine.ProgramName, Is.EqualTo(programName)); PythonEngine.Shutdown(); PythonEngine.ProgramName = programNameBackup; @@ -124,7 +124,7 @@ public void SetPythonPath() PythonEngine.PythonPath = path; PythonEngine.Initialize(); - Assert.AreEqual(path, PythonEngine.PythonPath); + Assert.That(PythonEngine.PythonPath, Is.EqualTo(path)); if (importShouldSucceed) Py.Import(moduleName); PythonEngine.Shutdown(); diff --git a/src/embed_tests/NeedsReinit/TestRuntime.cs b/src/embed_tests/NeedsReinit/TestRuntime.cs index 193bf57d3..ded0d9d2a 100644 --- a/src/embed_tests/NeedsReinit/TestRuntime.cs +++ b/src/embed_tests/NeedsReinit/TestRuntime.cs @@ -16,11 +16,11 @@ public static void Py_IsInitializedValue() Runtime.Runtime.PyGILState_Ensure(); } Runtime.Runtime.Py_Finalize(); - Assert.AreEqual(0, Runtime.Runtime.Py_IsInitialized()); + Assert.That(Runtime.Runtime.Py_IsInitialized(), Is.EqualTo(0)); Runtime.Runtime.Py_Initialize(); - Assert.AreEqual(1, Runtime.Runtime.Py_IsInitialized()); + Assert.That(Runtime.Runtime.Py_IsInitialized(), Is.EqualTo(1)); Runtime.Runtime.Py_Finalize(); - Assert.AreEqual(0, Runtime.Runtime.Py_IsInitialized()); + Assert.That(Runtime.Runtime.Py_IsInitialized(), Is.EqualTo(0)); } [Test] @@ -30,27 +30,27 @@ public static void RefCountTest() using var op = Runtime.Runtime.PyString_FromString("FooBar"); // New object RefCount should be one - Assert.AreEqual(1, Runtime.Runtime.Refcount32(op.BorrowOrThrow())); + Assert.That(Runtime.Runtime.Refcount32(op.BorrowOrThrow()), Is.EqualTo(1)); // Checking refcount didn't change refcount - Assert.AreEqual(1, Runtime.Runtime.Refcount32(op.Borrow())); + Assert.That(Runtime.Runtime.Refcount32(op.Borrow()), Is.EqualTo(1)); // Borrowing a reference doesn't increase refcount BorrowedReference p = op.Borrow(); - Assert.AreEqual(1, Runtime.Runtime.Refcount32(p)); + Assert.That(Runtime.Runtime.Refcount32(p), Is.EqualTo(1)); // Py_IncRef/Py_DecRef increase and decrease RefCount Runtime.Runtime.Py_IncRef(op.Borrow()); - Assert.AreEqual(2, Runtime.Runtime.Refcount32(p)); + Assert.That(Runtime.Runtime.Refcount32(p), Is.EqualTo(2)); Runtime.Runtime.Py_DecRef(StolenReference.DangerousFromPointer(op.DangerousGetAddress())); - Assert.AreEqual(1, Runtime.Runtime.Refcount32(p)); + Assert.That(Runtime.Runtime.Refcount32(p), Is.EqualTo(1)); // XIncref/XDecref increase and decrease RefCount #pragma warning disable CS0618 // Type or member is obsolete. We are testing corresponding members Runtime.Runtime.XIncref(p); - Assert.AreEqual(2, Runtime.Runtime.Refcount32(p)); + Assert.That(Runtime.Runtime.Refcount32(p), Is.EqualTo(2)); Runtime.Runtime.XDecref(op.Steal()); - Assert.AreEqual(1, Runtime.Runtime.Refcount32(p)); + Assert.That(Runtime.Runtime.Refcount32(p), Is.EqualTo(1)); #pragma warning restore CS0618 // Type or member is obsolete op.Dispose(); diff --git a/src/embed_tests/NumPyTests.cs b/src/embed_tests/NumPyTests.cs index 6f4a85716..57ff11b2b 100644 --- a/src/embed_tests/NumPyTests.cs +++ b/src/embed_tests/NumPyTests.cs @@ -25,7 +25,7 @@ public void Dispose() [Test] public void TestReadme() { - Assert.AreEqual("1.0", np.cos(np.pi * 2).ToString()); + Assert.That(np.cos(np.pi * 2).ToString(), Is.EqualTo("1.0")); dynamic sin = np.sin; StringAssert.StartsWith("-0.95892", sin(5).ToString()); @@ -34,12 +34,12 @@ public void TestReadme() Assert.That(c, Is.EqualTo(-0.675262).Within(0.01)); dynamic a = np.array(new List { 1, 2, 3 }); - Assert.AreEqual("float64", a.dtype.ToString()); + Assert.That(a.dtype.ToString(), Is.EqualTo("float64")); dynamic b = np.array(new List { 6, 5, 4 }, Py.kw("dtype", np.int32)); - Assert.AreEqual("int32", b.dtype.ToString()); + Assert.That(b.dtype.ToString(), Is.EqualTo("int32")); - Assert.AreEqual("[ 6. 10. 12.]", (a * b).ToString().Replace(" ", " ")); + Assert.That((a * b).ToString().Replace(" ", " "), Is.EqualTo("[ 6. 10. 12.]")); } [Test] @@ -47,9 +47,9 @@ public void MultidimensionalNumPyArray() { var array = new[,] { { 1, 2 }, { 3, 4 } }; var ndarray = np.InvokeMethod("asarray", array.ToPython()); - Assert.AreEqual((2, 2), ndarray.GetAttr("shape").As<(int, int)>()); - Assert.AreEqual(1, ndarray[(0, 0).ToPython()].InvokeMethod("__int__").As()); - Assert.AreEqual(array[1, 0], ndarray[(1, 0).ToPython()].InvokeMethod("__int__").As()); + Assert.That(ndarray.GetAttr("shape").As<(int, int)>(), Is.EqualTo((2, 2))); + Assert.That(ndarray[(0, 0).ToPython()].InvokeMethod("__int__").As(), Is.EqualTo(1)); + Assert.That(ndarray[(1, 0).ToPython()].InvokeMethod("__int__").As(), Is.EqualTo(array[1, 0])); } [Test] @@ -57,9 +57,9 @@ public void Int64Array() { var array = new long[,] { { 1, 2 }, { 3, 4 } }; var ndarray = np.InvokeMethod("asarray", array.ToPython()); - Assert.AreEqual((2, 2), ndarray.GetAttr("shape").As<(int, int)>()); - Assert.AreEqual(1, ndarray[(0, 0).ToPython()].InvokeMethod("__int__").As()); - Assert.AreEqual(array[1, 0], ndarray[(1, 0).ToPython()].InvokeMethod("__int__").As()); + Assert.That(ndarray.GetAttr("shape").As<(int, int)>(), Is.EqualTo((2, 2))); + Assert.That(ndarray[(0, 0).ToPython()].InvokeMethod("__int__").As(), Is.EqualTo(1)); + Assert.That(ndarray[(1, 0).ToPython()].InvokeMethod("__int__").As(), Is.EqualTo(array[1, 0])); } [Test] diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index 28076120a..15258fc83 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -1,7 +1,7 @@ - net472;net8.0 + net472;net10.0 ..\pythonnet.snk true @@ -29,13 +29,13 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + all runtime; build; native; contentfiles; analyzers; buildtransitive - 1.0.0 + 1.* all runtime; build; native; contentfiles; analyzers diff --git a/src/embed_tests/StateSerialization/MethodSerialization.cs b/src/embed_tests/StateSerialization/MethodSerialization.cs index d565c1e7a..1a032098b 100644 --- a/src/embed_tests/StateSerialization/MethodSerialization.cs +++ b/src/embed_tests/StateSerialization/MethodSerialization.cs @@ -16,7 +16,7 @@ public void GenericRoundtrip() var maybeMethod = new MaybeMethodBase(method); var restored = SerializationRoundtrip(maybeMethod); Assert.IsTrue(restored.Valid); - Assert.AreEqual(method, restored.Value); + Assert.That(restored.Value, Is.EqualTo(method)); } [Test] @@ -26,7 +26,7 @@ public void ConstructorRoundtrip() var maybeConstructor = new MaybeMethodBase(ctor); var restored = SerializationRoundtrip(maybeConstructor); Assert.IsTrue(restored.Valid); - Assert.AreEqual(ctor, restored.Value); + Assert.That(restored.Value, Is.EqualTo(ctor)); } static T SerializationRoundtrip(T item) diff --git a/src/embed_tests/TestConverter.cs b/src/embed_tests/TestConverter.cs index 3feced8d0..743731285 100644 --- a/src/embed_tests/TestConverter.cs +++ b/src/embed_tests/TestConverter.cs @@ -155,7 +155,7 @@ public void RawListProxy() var list = new List {"hello", "world"}; var listProxy = PyObject.FromManagedObject(list); var clrObject = (CLRObject)ManagedType.GetManagedObject(listProxy); - Assert.AreSame(list, clrObject.inst); + Assert.That(clrObject.inst, Is.SameAs(list)); } [Test] @@ -164,7 +164,7 @@ public void RawPyObjectProxy() var pyObject = "hello world!".ToPython(); var pyObjectProxy = PyObject.FromManagedObject(pyObject); var clrObject = (CLRObject)ManagedType.GetManagedObject(pyObjectProxy); - Assert.AreSame(pyObject, clrObject.inst); + Assert.That(clrObject.inst, Is.SameAs(pyObject)); #pragma warning disable CS0612 // Type or member is obsolete const string handlePropertyName = nameof(PyObject.Handle); diff --git a/src/embed_tests/TestPyFloat.cs b/src/embed_tests/TestPyFloat.cs index c6111f180..5c7f3cce7 100644 --- a/src/embed_tests/TestPyFloat.cs +++ b/src/embed_tests/TestPyFloat.cs @@ -15,7 +15,7 @@ public void FloatCtor() const float a = 4.5F; var i = new PyFloat(a); Assert.True(PyFloat.IsFloatType(i)); - // Assert.Assert.AreEqual(i, a.ToInt32()); + // Assert.Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -24,7 +24,7 @@ public void PyObjectCtorGood() var i = new PyFloat(5); var a = new PyFloat(i); Assert.True(PyFloat.IsFloatType(a)); - // Assert.Assert.AreEqual(i, a.ToInt32()); + // Assert.Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -45,7 +45,7 @@ public void DoubleCtor() const double a = 4.5; var i = new PyFloat(a); Assert.True(PyFloat.IsFloatType(i)); - // Assert.Assert.AreEqual(i, a.ToInt32()); + // Assert.Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -54,7 +54,7 @@ public void StringIntCtor() const string a = "5"; var i = new PyFloat(a); Assert.True(PyFloat.IsFloatType(i)); - // Assert.Assert.AreEqual(i, a.ToInt32()); + // Assert.Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -63,7 +63,7 @@ public void StringDoubleCtor() const string a = "4.5"; var i = new PyFloat(a); Assert.True(PyFloat.IsFloatType(i)); - // Assert.Assert.AreEqual(i, a.ToInt32()); + // Assert.Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -101,7 +101,7 @@ public void AsFloatGood() PyFloat s = PyFloat.AsFloat(i); Assert.True(PyFloat.IsFloatType(s)); - // Assert.Assert.AreEqual(i, a.ToInt32()); + // Assert.Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] diff --git a/src/embed_tests/TestPyInt.cs b/src/embed_tests/TestPyInt.cs index 36319cf1a..e79792d51 100644 --- a/src/embed_tests/TestPyInt.cs +++ b/src/embed_tests/TestPyInt.cs @@ -15,7 +15,7 @@ public void TestCtorInt() { const int i = 5; var a = new PyInt(i); - Assert.AreEqual(i, a.ToInt32()); + Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -23,7 +23,7 @@ public void TestCtorUInt() { const uint i = 5; var a = new PyInt(i); - Assert.AreEqual(i, a.ToInt32()); + Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -31,7 +31,7 @@ public void TestCtorLong() { const long i = 5; var a = new PyInt(i); - Assert.AreEqual(i, a.ToInt32()); + Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -39,7 +39,7 @@ public void TestCtorULong() { const ulong i = 5; var a = new PyInt(i); - Assert.AreEqual(i, a.ToInt32()); + Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -47,7 +47,7 @@ public void TestCtorShort() { const short i = 5; var a = new PyInt(i); - Assert.AreEqual(i, a.ToInt32()); + Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -55,7 +55,7 @@ public void TestCtorUShort() { const ushort i = 5; var a = new PyInt(i); - Assert.AreEqual(i, a.ToInt32()); + Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -63,7 +63,7 @@ public void TestCtorByte() { const byte i = 5; var a = new PyInt(i); - Assert.AreEqual(i, a.ToInt32()); + Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -71,7 +71,7 @@ public void TestCtorSByte() { const sbyte i = 5; var a = new PyInt(i); - Assert.AreEqual(i, a.ToInt32()); + Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -79,7 +79,7 @@ public void TestCtorPyObject() { var i = new PyInt(5); var a = new PyInt(i); - Assert.AreEqual(5, a.ToInt32()); + Assert.That(a.ToInt32(), Is.EqualTo(5)); } [Test] @@ -99,7 +99,7 @@ public void TestCtorString() { const string i = "5"; var a = new PyInt(i); - Assert.AreEqual(5, a.ToInt32()); + Assert.That(a.ToInt32(), Is.EqualTo(5)); } [Test] @@ -133,7 +133,7 @@ public void TestAsIntGood() { var i = new PyInt(5); var a = PyInt.AsInt(i); - Assert.AreEqual(5, a.ToInt32()); + Assert.That(a.ToInt32(), Is.EqualTo(5)); } [Test] @@ -152,7 +152,7 @@ public void TestConvertToInt32() { var a = new PyInt(5); Assert.IsInstanceOf(typeof(int), a.ToInt32()); - Assert.AreEqual(5, a.ToInt32()); + Assert.That(a.ToInt32(), Is.EqualTo(5)); } [Test] @@ -160,7 +160,7 @@ public void TestConvertToInt16() { var a = new PyInt(5); Assert.IsInstanceOf(typeof(short), a.ToInt16()); - Assert.AreEqual(5, a.ToInt16()); + Assert.That(a.ToInt16(), Is.EqualTo(5)); } [Test] @@ -169,7 +169,7 @@ public void TestConvertToInt64() long val = 5 + (long)int.MaxValue; var a = new PyInt(val); Assert.IsInstanceOf(typeof(long), a.ToInt64()); - Assert.AreEqual(val, a.ToInt64()); + Assert.That(a.ToInt64(), Is.EqualTo(val)); } [Test] @@ -195,7 +195,7 @@ public void ToBigInteger() var expected = simpleValues.Select(v => new BigInteger(v)).ToArray(); var actual = simpleValues.Select(v => new PyInt(v).ToBigInteger()).ToArray(); - CollectionAssert.AreEqual(expected, actual); + Assert.That(actual, Is.EqualTo(expected)); } [Test] @@ -204,37 +204,37 @@ public void CompareTo() var v = new PyInt(42); #region Signed - Assert.AreEqual(0, v.CompareTo(42L)); - Assert.AreEqual(0, v.CompareTo(42)); - Assert.AreEqual(0, v.CompareTo((short)42)); - Assert.AreEqual(0, v.CompareTo((sbyte)42)); - - Assert.AreEqual(1, v.CompareTo(41L)); - Assert.AreEqual(1, v.CompareTo(41)); - Assert.AreEqual(1, v.CompareTo((short)41)); - Assert.AreEqual(1, v.CompareTo((sbyte)41)); - - Assert.AreEqual(-1, v.CompareTo(43L)); - Assert.AreEqual(-1, v.CompareTo(43)); - Assert.AreEqual(-1, v.CompareTo((short)43)); - Assert.AreEqual(-1, v.CompareTo((sbyte)43)); + Assert.That(v.CompareTo(42L), Is.EqualTo(0)); + Assert.That(v.CompareTo(42), Is.EqualTo(0)); + Assert.That(v.CompareTo((short)42), Is.EqualTo(0)); + Assert.That(v.CompareTo((sbyte)42), Is.EqualTo(0)); + + Assert.That(v.CompareTo(41L), Is.EqualTo(1)); + Assert.That(v.CompareTo(41), Is.EqualTo(1)); + Assert.That(v.CompareTo((short)41), Is.EqualTo(1)); + Assert.That(v.CompareTo((sbyte)41), Is.EqualTo(1)); + + Assert.That(v.CompareTo(43L), Is.EqualTo(-1)); + Assert.That(v.CompareTo(43), Is.EqualTo(-1)); + Assert.That(v.CompareTo((short)43), Is.EqualTo(-1)); + Assert.That(v.CompareTo((sbyte)43), Is.EqualTo(-1)); #endregion Signed #region Unsigned - Assert.AreEqual(0, v.CompareTo(42UL)); - Assert.AreEqual(0, v.CompareTo(42U)); - Assert.AreEqual(0, v.CompareTo((ushort)42)); - Assert.AreEqual(0, v.CompareTo((byte)42)); - - Assert.AreEqual(1, v.CompareTo(41UL)); - Assert.AreEqual(1, v.CompareTo(41U)); - Assert.AreEqual(1, v.CompareTo((ushort)41)); - Assert.AreEqual(1, v.CompareTo((byte)41)); - - Assert.AreEqual(-1, v.CompareTo(43UL)); - Assert.AreEqual(-1, v.CompareTo(43U)); - Assert.AreEqual(-1, v.CompareTo((ushort)43)); - Assert.AreEqual(-1, v.CompareTo((byte)43)); + Assert.That(v.CompareTo(42UL), Is.EqualTo(0)); + Assert.That(v.CompareTo(42U), Is.EqualTo(0)); + Assert.That(v.CompareTo((ushort)42), Is.EqualTo(0)); + Assert.That(v.CompareTo((byte)42), Is.EqualTo(0)); + + Assert.That(v.CompareTo(41UL), Is.EqualTo(1)); + Assert.That(v.CompareTo(41U), Is.EqualTo(1)); + Assert.That(v.CompareTo((ushort)41), Is.EqualTo(1)); + Assert.That(v.CompareTo((byte)41), Is.EqualTo(1)); + + Assert.That(v.CompareTo(43UL), Is.EqualTo(-1)); + Assert.That(v.CompareTo(43U), Is.EqualTo(-1)); + Assert.That(v.CompareTo((ushort)43), Is.EqualTo(-1)); + Assert.That(v.CompareTo((byte)43), Is.EqualTo(-1)); #endregion Unsigned } @@ -273,10 +273,10 @@ public void ToBigIntegerLarge() { BigInteger val = BigInteger.Pow(2, 1024) + 3; var pyInt = new PyInt(val); - Assert.AreEqual(val, pyInt.ToBigInteger()); + Assert.That(pyInt.ToBigInteger(), Is.EqualTo(val)); val = -val; pyInt = new PyInt(val); - Assert.AreEqual(val, pyInt.ToBigInteger()); + Assert.That(pyInt.ToBigInteger(), Is.EqualTo(val)); } } } diff --git a/src/embed_tests/TestPyIter.cs b/src/embed_tests/TestPyIter.cs index 5da660242..4492080e8 100644 --- a/src/embed_tests/TestPyIter.cs +++ b/src/embed_tests/TestPyIter.cs @@ -18,7 +18,7 @@ public void KeepOldObjects() PyObject[] chars = testString.ToArray(); Assert.IsTrue(chars.Length > 1); string reconstructed = string.Concat(chars.Select(c => c.As())); - Assert.AreEqual(testString.As(), reconstructed); + Assert.That(reconstructed, Is.EqualTo(testString.As())); } } } diff --git a/src/embed_tests/TestPyList.cs b/src/embed_tests/TestPyList.cs index a380f0b2d..927eaa1da 100644 --- a/src/embed_tests/TestPyList.cs +++ b/src/embed_tests/TestPyList.cs @@ -29,7 +29,7 @@ public void TestStringAsListType() var ex = Assert.Throws(() => t = PyList.AsList(i)); - Assert.AreEqual("'int' object is not iterable", ex.Message); + Assert.That(ex.Message, Is.EqualTo("'int' object is not iterable")); Assert.IsNull(t); } @@ -49,7 +49,7 @@ public void TestEmptyCtor() var s = new PyList(); Assert.IsInstanceOf(typeof(PyList), s); - Assert.AreEqual(0, s.Length()); + Assert.That(s.Length(), Is.EqualTo(0)); } [Test] @@ -59,10 +59,10 @@ public void TestPyObjectArrayCtor() var s = new PyList(ai); Assert.IsInstanceOf(typeof(PyList), s); - Assert.AreEqual(3, s.Length()); - Assert.AreEqual("3", s[0].ToString()); - Assert.AreEqual("2", s[1].ToString()); - Assert.AreEqual("1", s[2].ToString()); + Assert.That(s.Length(), Is.EqualTo(3)); + Assert.That(s[0].ToString(), Is.EqualTo("3")); + Assert.That(s[1].ToString(), Is.EqualTo("2")); + Assert.That(s[2].ToString(), Is.EqualTo("1")); } [Test] @@ -72,7 +72,7 @@ public void TestPyObjectCtor() var s = new PyList(a); Assert.IsInstanceOf(typeof(PyList), s); - Assert.AreEqual(0, s.Length()); + Assert.That(s.Length(), Is.EqualTo(0)); } [Test] @@ -83,7 +83,7 @@ public void TestBadPyObjectCtor() var ex = Assert.Throws(() => t = new PyList(i)); - Assert.AreEqual("object is not a list", ex.Message); + Assert.That(ex.Message, Is.EqualTo("object is not a list")); Assert.IsNull(t); } @@ -94,8 +94,8 @@ public void TestAppend() var s = new PyList(ai); s.Append(new PyInt(4)); - Assert.AreEqual(4, s.Length()); - Assert.AreEqual("4", s[3].ToString()); + Assert.That(s.Length(), Is.EqualTo(4)); + Assert.That(s[3].ToString(), Is.EqualTo("4")); } [Test] @@ -105,8 +105,8 @@ public void TestInsert() var s = new PyList(ai); s.Insert(0, new PyInt(4)); - Assert.AreEqual(4, s.Length()); - Assert.AreEqual("4", s[0].ToString()); + Assert.That(s.Length(), Is.EqualTo(4)); + Assert.That(s[0].ToString(), Is.EqualTo("4")); } [Test] @@ -117,10 +117,10 @@ public void TestReverse() s.Reverse(); - Assert.AreEqual(3, s.Length()); - Assert.AreEqual("2", s[0].ToString()); - Assert.AreEqual("1", s[1].ToString()); - Assert.AreEqual("3", s[2].ToString()); + Assert.That(s.Length(), Is.EqualTo(3)); + Assert.That(s[0].ToString(), Is.EqualTo("2")); + Assert.That(s[1].ToString(), Is.EqualTo("1")); + Assert.That(s[2].ToString(), Is.EqualTo("3")); } [Test] @@ -131,10 +131,10 @@ public void TestSort() s.Sort(); - Assert.AreEqual(3, s.Length()); - Assert.AreEqual("1", s[0].ToString()); - Assert.AreEqual("2", s[1].ToString()); - Assert.AreEqual("3", s[2].ToString()); + Assert.That(s.Length(), Is.EqualTo(3)); + Assert.That(s[0].ToString(), Is.EqualTo("1")); + Assert.That(s[1].ToString(), Is.EqualTo("2")); + Assert.That(s[2].ToString(), Is.EqualTo("3")); } [Test] @@ -151,10 +151,10 @@ public void TestOnPyList() result.Add(item.ToString()); } - Assert.AreEqual(3, result.Count); - Assert.AreEqual("foo", result[0]); - Assert.AreEqual("bar", result[1]); - Assert.AreEqual("baz", result[2]); + Assert.That(result.Count, Is.EqualTo(3)); + Assert.That(result[0], Is.EqualTo("foo")); + Assert.That(result[1], Is.EqualTo("bar")); + Assert.That(result[2], Is.EqualTo("baz")); } } } diff --git a/src/embed_tests/TestPyObject.cs b/src/embed_tests/TestPyObject.cs index f762b94e9..27b0a7b26 100644 --- a/src/embed_tests/TestPyObject.cs +++ b/src/embed_tests/TestPyObject.cs @@ -65,7 +65,7 @@ public void UnaryMinus_ThrowsOnBadType() { dynamic list = new PyList(); var error = Assert.Throws(() => list = -list); - Assert.AreEqual("TypeError", error.Type.Name); + Assert.That(error.Type.Name, Is.EqualTo("TypeError")); } [Test] @@ -80,7 +80,7 @@ public void GetAttrDefault_IgnoresAttributeErrorOnly() var typeErrResult = Assert.Throws( () => ob.GetAttr(nameof(PyObjectTestMethods.RaisesTypeError), fallback) ); - Assert.AreEqual(Exceptions.TypeError, typeErrResult.Type); + Assert.That(typeErrResult.Type, Is.EqualTo(Exceptions.TypeError)); } // regression test from https://github.com/pythonnet/pythonnet/issues/1642 diff --git a/src/embed_tests/TestPySequence.cs b/src/embed_tests/TestPySequence.cs index 339ea1e83..df9e73863 100644 --- a/src/embed_tests/TestPySequence.cs +++ b/src/embed_tests/TestPySequence.cs @@ -26,16 +26,16 @@ public void TestGetSlice() var t = new PyString("FooBar"); PyObject s = t.GetSlice(0, 3); - Assert.AreEqual("Foo", s.ToString()); + Assert.That(s.ToString(), Is.EqualTo("Foo")); PyObject s2 = t.GetSlice(3, 6); - Assert.AreEqual("Bar", s2.ToString()); + Assert.That(s2.ToString(), Is.EqualTo("Bar")); PyObject s3 = t.GetSlice(0, 6); - Assert.AreEqual("FooBar", s3.ToString()); + Assert.That(s3.ToString(), Is.EqualTo("FooBar")); PyObject s4 = t.GetSlice(0, 12); - Assert.AreEqual("FooBar", s4.ToString()); + Assert.That(s4.ToString(), Is.EqualTo("FooBar")); } [Test] @@ -46,7 +46,7 @@ public void TestConcat() PyObject actual = t1.Concat(t2); - Assert.AreEqual("FooBar", actual.ToString()); + Assert.That(actual.ToString(), Is.EqualTo("FooBar")); } [Test] @@ -55,10 +55,10 @@ public void TestRepeat() var t1 = new PyString("Foo"); PyObject actual = t1.Repeat(3); - Assert.AreEqual("FooFooFoo", actual.ToString()); + Assert.That(actual.ToString(), Is.EqualTo("FooFooFoo")); actual = t1.Repeat(-3); - Assert.AreEqual("", actual.ToString()); + Assert.That(actual.ToString(), Is.EqualTo("")); } [Test] @@ -75,9 +75,9 @@ public void TestIndex() { var t1 = new PyString("FooBar"); - Assert.AreEqual(4, t1.Index32(new PyString("a"))); - Assert.AreEqual(5L, t1.Index64(new PyString("r"))); - Assert.AreEqual(-(nint)1, t1.Index(new PyString("z"))); + Assert.That(t1.Index32(new PyString("a")), Is.EqualTo(4)); + Assert.That(t1.Index64(new PyString("r")), Is.EqualTo(5L)); + Assert.That(t1.Index(new PyString("z")), Is.EqualTo(-(nint)1)); } } } diff --git a/src/embed_tests/TestPyString.cs b/src/embed_tests/TestPyString.cs index a1fdd6079..8949ca28c 100644 --- a/src/embed_tests/TestPyString.cs +++ b/src/embed_tests/TestPyString.cs @@ -11,7 +11,7 @@ public void TestStringCtor() { const string expected = "foo"; var actual = new PyString(expected); - Assert.AreEqual(expected, actual.ToString()); + Assert.That(actual.ToString(), Is.EqualTo(expected)); } [Test] @@ -19,11 +19,10 @@ public void TestEmptyStringCtor() { const string expected = ""; var actual = new PyString(expected); - Assert.AreEqual(expected, actual.ToString()); + Assert.That(actual.ToString(), Is.EqualTo(expected)); } [Test] - [Ignore("Ambiguous behavior between PY2/PY3. Needs remapping")] public void TestPyObjectCtor() { const string expected = "Foo"; @@ -31,7 +30,7 @@ public void TestPyObjectCtor() var t = new PyString(expected); var actual = new PyString(t); - Assert.AreEqual(expected, actual.ToString()); + Assert.That(actual.ToString(), Is.EqualTo(expected)); } [Test] @@ -54,11 +53,10 @@ public void TestCtorBorrowed() var t = new PyString(expected); var actual = new PyString(t.Reference); - Assert.AreEqual(expected, actual.ToString()); + Assert.That(actual.ToString(), Is.EqualTo(expected)); } [Test] - [Ignore("Ambiguous behavior between PY2/PY3. Needs remapping")] public void IsStringTrue() { var t = new PyString("foo"); @@ -79,7 +77,7 @@ public void TestUnicode() { const string expected = "foo\u00e9"; PyObject actual = new PyString(expected); - Assert.AreEqual(expected, actual.ToString()); + Assert.That(actual.ToString(), Is.EqualTo(expected)); } [Test] @@ -87,8 +85,8 @@ public void TestUnicodeSurrogateToString() { var expected = "foo\ud83d\udc3c"; var actual = PythonEngine.Eval("'foo\ud83d\udc3c'"); - Assert.AreEqual(4, actual.Length()); - Assert.AreEqual(expected, actual.ToString()); + Assert.That(actual.Length(), Is.EqualTo(4)); + Assert.That(actual.ToString(), Is.EqualTo(expected)); } [Test] @@ -97,8 +95,8 @@ public void TestUnicodeSurrogate() const string expected = "foo\ud83d\udc3c"; // "foo🐼" PyObject actual = new PyString(expected); // python treats "foo🐼" as 4 characters, dotnet as 5 - Assert.AreEqual(4, actual.Length()); - Assert.AreEqual(expected, actual.ToString()); + Assert.That(actual.Length(), Is.EqualTo(4)); + Assert.That(actual.ToString(), Is.EqualTo(expected)); } [Test] @@ -106,9 +104,9 @@ public void CompareTo() { var a = new PyString("foo"); - Assert.AreEqual(0, a.CompareTo("foo")); - Assert.AreEqual("foo".CompareTo("bar"), a.CompareTo("bar")); - Assert.AreEqual("foo".CompareTo("foz"), a.CompareTo("foz")); + Assert.That(a.CompareTo("foo"), Is.EqualTo(0)); + Assert.That(a.CompareTo("bar"), Is.EqualTo("foo".CompareTo("bar"))); + Assert.That(a.CompareTo("foz"), Is.EqualTo("foo".CompareTo("foz"))); } [Test] diff --git a/src/embed_tests/TestPyTuple.cs b/src/embed_tests/TestPyTuple.cs index 3a3fbf2a0..2a9238533 100644 --- a/src/embed_tests/TestPyTuple.cs +++ b/src/embed_tests/TestPyTuple.cs @@ -31,7 +31,7 @@ public void TestPyTupleIsTupleType() public void TestPyTupleEmpty() { var t = new PyTuple(); - Assert.AreEqual(0, t.Length()); + Assert.That(t.Length(), Is.EqualTo(0)); } [Test] @@ -42,7 +42,7 @@ public void TestPyTupleBadCtor() var ex = Assert.Throws(() => t = new PyTuple(i)); - Assert.AreEqual("object is not a tuple", ex.Message); + Assert.That(ex.Message, Is.EqualTo("object is not a tuple")); Assert.IsNull(t); } @@ -52,7 +52,7 @@ public void TestPyTupleCtorEmptyArray() var a = new PyObject[] { }; var t = new PyTuple(a); - Assert.AreEqual(0, t.Length()); + Assert.That(t.Length(), Is.EqualTo(0)); } [Test] @@ -61,7 +61,7 @@ public void TestPyTupleCtorArrayPyIntEmpty() var a = new PyInt[] { }; var t = new PyTuple(a); - Assert.AreEqual(0, t.Length()); + Assert.That(t.Length(), Is.EqualTo(0)); } [Test] @@ -70,7 +70,7 @@ public void TestPyTupleCtorArray() var a = new PyObject[] { new PyInt(1), new PyString("Foo") }; var t = new PyTuple(a); - Assert.AreEqual(2, t.Length()); + Assert.That(t.Length(), Is.EqualTo(2)); } /// @@ -81,8 +81,6 @@ public void TestPyTupleCtorArray() /// Test has second purpose. Currently it generated an Exception /// that the GC failed to remove often and caused AppDomain unload /// errors at the end of tests. See GH#397 for more info. - /// - /// Curious, on PY27 it gets a Unicode on the ex.Message. On PY3+ its string. /// [Test] public void TestPyTupleInvalidAppend() @@ -93,7 +91,7 @@ public void TestPyTupleInvalidAppend() var ex = Assert.Throws(() => t.Concat(s)); StringAssert.StartsWith("can only concatenate tuple", ex.Message); - Assert.AreEqual(0, t.Length()); + Assert.That(t.Length(), Is.EqualTo(0)); Assert.IsEmpty(t); } @@ -116,9 +114,9 @@ public void TestPyTupleStringConvert() Assert.IsNotNull(t); Assert.IsInstanceOf(typeof(PyTuple), t); - Assert.AreEqual("f", t[0].ToString()); - Assert.AreEqual("o", t[1].ToString()); - Assert.AreEqual("o", t[2].ToString()); + Assert.That(t[0].ToString(), Is.EqualTo("f")); + Assert.That(t[1].ToString(), Is.EqualTo("o")); + Assert.That(t[2].ToString(), Is.EqualTo("o")); } [Test] @@ -152,7 +150,7 @@ public void TestInvalidAsTuple() var ex = Assert.Throws(() => t = PyTuple.AsTuple(i)); - Assert.AreEqual("'int' object is not iterable", ex.Message); + Assert.That(ex.Message, Is.EqualTo("'int' object is not iterable")); Assert.IsNull(t); } } diff --git a/src/embed_tests/TestPyType.cs b/src/embed_tests/TestPyType.cs index c29032a8a..cd7d37816 100644 --- a/src/embed_tests/TestPyType.cs +++ b/src/embed_tests/TestPyType.cs @@ -28,9 +28,9 @@ public void CanCreateHeapType() ); using var type = new PyType(spec); - Assert.AreEqual(name, type.GetAttr("__name__").As()); - Assert.AreEqual(name, type.Name); - Assert.AreEqual(docStr, type.GetAttr("__doc__").As()); + Assert.That(type.GetAttr("__name__").As(), Is.EqualTo(name)); + Assert.That(type.Name, Is.EqualTo(name)); + Assert.That(type.GetAttr("__doc__").As(), Is.EqualTo(docStr)); } } } diff --git a/src/embed_tests/TestPythonEngineProperties.cs b/src/embed_tests/TestPythonEngineProperties.cs index 485931cfb..8d5a17491 100644 --- a/src/embed_tests/TestPythonEngineProperties.cs +++ b/src/embed_tests/TestPythonEngineProperties.cs @@ -72,7 +72,7 @@ public static void GetPythonPathDefault() { string s = PythonEngine.PythonPath; - StringAssert.Contains("python", s.ToLower()); + Assert.That(s.ToLower(), Does.Contain("python")); } [Test] @@ -94,7 +94,7 @@ public static void GetPythonHomeDefault() string enginePythonHome = PythonEngine.PythonHome; - Assert.AreEqual(envPythonHome, enginePythonHome); + Assert.That(enginePythonHome, Is.EqualTo(envPythonHome)); } } } diff --git a/src/embed_tests/TestPythonException.cs b/src/embed_tests/TestPythonException.cs index 91a412749..f69ba7a8f 100644 --- a/src/embed_tests/TestPythonException.cs +++ b/src/embed_tests/TestPythonException.cs @@ -14,7 +14,7 @@ public void TestMessage() var ex = Assert.Throws(() => foo = list[0]); - Assert.AreEqual("list index out of range", ex.Message); + Assert.That(ex.Message, Is.EqualTo("list index out of range")); Assert.IsNull(foo); } @@ -26,7 +26,7 @@ public void TestType() var ex = Assert.Throws(() => foo = list[0]); - Assert.AreEqual("IndexError", ex.Type.Name); + Assert.That(ex.Type.Name, Is.EqualTo("IndexError")); Assert.IsNull(foo); } @@ -108,7 +108,7 @@ public void TestPythonExceptionFormatNoTraceback() catch (PythonException ex) { // ImportError/ModuleNotFoundError do not have a traceback when not running in a script - Assert.AreEqual(ex.StackTrace, ex.Format()); + Assert.That(ex.Format(), Is.EqualTo(ex.StackTrace)); } } @@ -151,7 +151,7 @@ def __init__(self, val): // exception was raised by initializing the exception Assert.IsFalse(PythonReferenceComparer.Instance.Equals(type, typeObj)); // the message should now be the string from the throw exception during normalization - Assert.AreEqual("invalid literal for int() with base 10: 'dummy string'", strObj.ToString()); + Assert.That(strObj.ToString(), Is.EqualTo("invalid literal for int() with base 10: 'dummy string'")); } } diff --git a/src/embed_tests/pyimport.cs b/src/embed_tests/pyimport.cs index c774af345..0236bf0f6 100644 --- a/src/embed_tests/pyimport.cs +++ b/src/embed_tests/pyimport.cs @@ -73,12 +73,12 @@ public void TestSysArgsImportException() public void TestCastGlobalVar() { dynamic foo = Py.Import("PyImportTest.cast_global_var"); - Assert.AreEqual("1", foo.FOO.ToString()); - Assert.AreEqual("1", foo.test_foo().ToString()); + Assert.That(foo.FOO.ToString(), Is.EqualTo("1")); + Assert.That(foo.test_foo().ToString(), Is.EqualTo("1")); foo.FOO = 2; - Assert.AreEqual("2", foo.FOO.ToString()); - Assert.AreEqual("2", foo.test_foo().ToString()); + Assert.That(foo.FOO.ToString(), Is.EqualTo("2")); + Assert.That(foo.test_foo().ToString(), Is.EqualTo("2")); } [Test] diff --git a/src/python_tests_runner/Python.PythonTestsRunner.csproj b/src/python_tests_runner/Python.PythonTestsRunner.csproj index 5fc55d158..9f782bd2b 100644 --- a/src/python_tests_runner/Python.PythonTestsRunner.csproj +++ b/src/python_tests_runner/Python.PythonTestsRunner.csproj @@ -1,7 +1,7 @@ - net472;net8.0 + net472;net10.0 @@ -11,7 +11,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/runtime/AssemblyManager.cs b/src/runtime/AssemblyManager.cs index 82658bf50..dcc5aa2f0 100644 --- a/src/runtime/AssemblyManager.cs +++ b/src/runtime/AssemblyManager.cs @@ -123,6 +123,11 @@ private static void AssemblyLoadHandler(object ob, AssemblyLoadEventArgs args) internal static AssemblyName? TryParseAssemblyName(string name) { + // workaround for https://github.com/dotnet/runtime/issues/123951 + if (name.IndexOfAny(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }) >= 0) + { + return null; + } try { return new AssemblyName(name); diff --git a/src/runtime/CollectionWrappers/ListWrapper.cs b/src/runtime/CollectionWrappers/ListWrapper.cs index 41ccb8fae..91c156c32 100644 --- a/src/runtime/CollectionWrappers/ListWrapper.cs +++ b/src/runtime/CollectionWrappers/ListWrapper.cs @@ -14,12 +14,14 @@ public T this[int index] { get { + using var _ = Py.GIL(); var item = Runtime.PyList_GetItem(pyObject, index); var pyItem = new PyObject(item); return pyItem.As()!; } set { + using var _ = Py.GIL(); var pyItem = value.ToPython(); var result = Runtime.PyList_SetItem(pyObject, index, new NewReference(pyItem).Steal()); if (result == -1) @@ -37,6 +39,7 @@ public void Insert(int index, T item) if (IsReadOnly) throw new InvalidOperationException("Collection is read-only"); + using var _ = Py.GIL(); var pyItem = item.ToPython(); int result = Runtime.PyList_Insert(pyObject, index, pyItem); diff --git a/src/runtime/CollectionWrappers/SequenceWrapper.cs b/src/runtime/CollectionWrappers/SequenceWrapper.cs index fcc5c23f4..feb0e515d 100644 --- a/src/runtime/CollectionWrappers/SequenceWrapper.cs +++ b/src/runtime/CollectionWrappers/SequenceWrapper.cs @@ -14,10 +14,14 @@ public int Count { get { - var size = Runtime.PySequence_Size(pyObject.Reference); - if (size == -1) + nint size = -1; { - Runtime.CheckExceptionOccurred(); + using var _ = Py.GIL(); + size = Runtime.PySequence_Size(pyObject.Reference); + if (size == -1) + { + Runtime.CheckExceptionOccurred(); + } } return checked((int)size); @@ -38,6 +42,7 @@ public void Clear() { if (IsReadOnly) throw new NotImplementedException(); + using var _ = Py.GIL(); int result = Runtime.PySequence_DelSlice(pyObject, 0, Count); if (result == -1) { @@ -77,12 +82,16 @@ protected bool removeAt(int index) if (index >= Count || index < 0) return false; - int result = Runtime.PySequence_DelItem(pyObject, index); - if (result == 0) - return true; + { + using var _ = Py.GIL(); + int result = Runtime.PySequence_DelItem(pyObject, index); + + if (result == 0) + return true; - Runtime.CheckExceptionOccurred(); + Runtime.CheckExceptionOccurred(); + } return false; } diff --git a/src/runtime/InteropConfiguration.cs b/src/runtime/InteropConfiguration.cs index 781d0d01f..0cd441ebc 100644 --- a/src/runtime/InteropConfiguration.cs +++ b/src/runtime/InteropConfiguration.cs @@ -22,6 +22,7 @@ public static InteropConfiguration MakeDefault() { DefaultBaseTypeProvider.Instance, new CollectionMixinsProvider(new Lazy(() => Py.Import("clr._extras.collections"))), + new DynamicObjectMixinsProvider(new Lazy(() => Py.Import("clr._extras.dlr"))), }, }; } diff --git a/src/runtime/Loader.cs b/src/runtime/Loader.cs index c0e964abc..08c0be7cb 100644 --- a/src/runtime/Loader.cs +++ b/src/runtime/Loader.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Text; namespace Python.Runtime @@ -12,6 +13,28 @@ public unsafe static int Initialize(IntPtr data, int size) { try { + // On .NET Framework, the host is python.exe which has no binding + // redirects for netstandard2.0 shims (e.g. RuntimeInformation + // Version=0.0.0.0 vs the 4.0.2.0 shim on disk). Binding redirects + // via config files can't be injected after AppDomain creation, so + // resolve assemblies from our runtime directory directly. + // Only needed on .NET Framework; on .NET (Core) this causes + // duplicate assembly loads, as .deps.json is respected and + // the correct assembly is already found. + if (typeof(object).Assembly.GetName().Name == "mscorlib") + { + AppDomain.CurrentDomain.AssemblyResolve += (_, args) => + { + var name = new System.Reflection.AssemblyName(args.Name); + var dir = Path.GetDirectoryName(typeof(Loader).Assembly.Location); + var path = Path.Combine(dir, name.Name + ".dll"); + + return File.Exists(path) + ? System.Reflection.Assembly.LoadFrom(path) + : null; + }; + } + var dllPath = Encodings.UTF8.GetString((byte*)data.ToPointer(), size); if (!string.IsNullOrEmpty(dllPath)) diff --git a/src/runtime/Mixins/CollectionMixinsProvider.cs b/src/runtime/Mixins/CollectionMixinsProvider.cs index d1b19e4d8..2bd352d16 100644 --- a/src/runtime/Mixins/CollectionMixinsProvider.cs +++ b/src/runtime/Mixins/CollectionMixinsProvider.cs @@ -63,6 +63,12 @@ public IEnumerable GetBaseTypes(Type type, IList existingBases) newBases.Add(new PyType(this.Mixins.GetAttr("IteratorMixin"))); } + // context managers (for IDisposable) + if (interfaces.Contains(typeof(IDisposable))) + { + newBases.Add(new PyType(this.Mixins.GetAttr("ContextManagerMixin"))); + } + if (newBases.Count == existingBases.Count) { return existingBases; diff --git a/src/runtime/Mixins/DynamicObjectMixinsProvider.cs b/src/runtime/Mixins/DynamicObjectMixinsProvider.cs new file mode 100644 index 000000000..6afa31f7e --- /dev/null +++ b/src/runtime/Mixins/DynamicObjectMixinsProvider.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; + +namespace Python.Runtime.Mixins; + +class DynamicObjectMixinsProvider : IPythonBaseTypeProvider, IDisposable +{ + readonly Lazy mixinsModule; + + public DynamicObjectMixinsProvider(Lazy mixinsModule) => + this.mixinsModule = mixinsModule ?? throw new ArgumentNullException(nameof(mixinsModule)); + + public PyObject Mixins => mixinsModule.Value; + + public IEnumerable GetBaseTypes(Type type, IList existingBases) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); + + if (existingBases is null) + throw new ArgumentNullException(nameof(existingBases)); + + if (!typeof(IDynamicMetaObjectProvider).IsAssignableFrom(type)) + return existingBases; + + var newBases = new List(existingBases) + { + new(Mixins.GetAttr("DynamicMetaObjectProviderMixin")) + }; + + if (type.IsInterface && type.BaseType is null) + { + newBases.RemoveAll(@base => PythonReferenceComparer.Instance.Equals(@base, Runtime.PyBaseObjectType)); + } + + return newBases; + } + + public void Dispose() + { + if (this.mixinsModule.IsValueCreated) + { + this.mixinsModule.Value.Dispose(); + } + } +} diff --git a/src/runtime/Mixins/collections.py b/src/runtime/Mixins/collections.py index 95a6d8162..e6eaef2e5 100644 --- a/src/runtime/Mixins/collections.py +++ b/src/runtime/Mixins/collections.py @@ -5,6 +5,22 @@ import collections.abc as col +class ContextManagerMixin: + """Implements Python's context manager protocol for .NET IDisposable types""" + def __enter__(self): + """Return self for use in the with block""" + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """Call Dispose() when exiting the with block""" + if hasattr(self, 'Dispose'): + self.Dispose() + else: + from System import IDisposable + IDisposable(self).Dispose() + # Return False to indicate that exceptions should propagate + return False + class IteratorMixin(col.Iterator): def close(self): if hasattr(self, 'Dispose'): diff --git a/src/runtime/Mixins/dlr.py b/src/runtime/Mixins/dlr.py new file mode 100644 index 000000000..cd44035e4 --- /dev/null +++ b/src/runtime/Mixins/dlr.py @@ -0,0 +1,18 @@ +""" +Implements helpers for Dynamic Language Runtime (DLR) types. +""" + +class DynamicMetaObjectProviderMixin: + def __dir__(self): + names = set(super().__dir__()) + + get_dynamic_member_names = getattr(self, "GetDynamicMemberNames", None) + if callable(get_dynamic_member_names): + try: + for name in get_dynamic_member_names(): + if isinstance(name, str): + names.add(name) + except Exception: + pass + + return list(sorted(names)) diff --git a/src/runtime/Native/CustomMarshaler.cs b/src/runtime/Native/CustomMarshaler.cs index 299af3a33..58f7c6b20 100644 --- a/src/runtime/Native/CustomMarshaler.cs +++ b/src/runtime/Native/CustomMarshaler.cs @@ -72,7 +72,7 @@ public static ICustomMarshaler GetInstance(string cookie) return Instance; } - public static string? PtrToStringUni(IntPtr p) + public static string? PtrToString(IntPtr p) { if (p == IntPtr.Zero) { @@ -106,36 +106,19 @@ public static int GetUnicodeByteLength(IntPtr p) } /// - /// Utility function for Marshaling Unicode on PY3 and AnsiStr on PY2. - /// Use on functions whose Input signatures changed between PY2/PY3. - /// Ex. Py_SetPythonHome + /// Utility function for Marshaling Unicode /// /// Managed String /// - /// Ptr to Native String ANSI(PY2)/Unicode(PY3/UCS2)/UTF32(PY3/UCS4. + /// Ptr to Native String /// /// /// You MUST deallocate the IntPtr of the Return when done with it. /// - public static IntPtr Py3UnicodePy2StringtoPtr(string s) + public static IntPtr StringToPtr(string s) { return Instance.MarshalManagedToNative(s); } - - /// - /// Utility function for Marshaling Unicode IntPtr on PY3 and - /// AnsiStr IntPtr on PY2 to Managed Strings. Use on Python functions - /// whose return type changed between PY2/PY3. - /// Ex. Py_GetPythonHome - /// - /// Native Ansi/Unicode/UTF32 String - /// - /// Managed String - /// - public static string? PtrToPy3UnicodePy2String(IntPtr p) - { - return PtrToStringUni(p); - } } diff --git a/src/runtime/Native/PyIdentifier_.cs b/src/runtime/Native/PyIdentifier_.cs index 1ea2b704c..8d693c09f 100644 --- a/src/runtime/Native/PyIdentifier_.cs +++ b/src/runtime/Native/PyIdentifier_.cs @@ -13,8 +13,6 @@ static class PyIdentifier public static BorrowedReference __doc__ => new(f__doc__); static IntPtr f__class__; public static BorrowedReference __class__ => new(f__class__); - static IntPtr f__clear_reentry_guard__; - public static BorrowedReference __clear_reentry_guard__ => new(f__clear_reentry_guard__); static IntPtr f__module__; public static BorrowedReference __module__ => new(f__module__); static IntPtr f__file__; @@ -24,6 +22,8 @@ static class PyIdentifier static IntPtr f__self__; public static BorrowedReference __self__ => new(f__self__); static IntPtr f__annotations__; + public static BorrowedReference __dictoffset__ => new(f__dictoffset__); + static IntPtr f__dictoffset__; public static BorrowedReference __annotations__ => new(f__annotations__); static IntPtr f__init__; public static BorrowedReference __init__ => new(f__init__); @@ -51,12 +51,12 @@ static partial class InternString "__dict__", "__doc__", "__class__", - "__clear_reentry_guard__", "__module__", "__file__", "__slots__", "__self__", "__annotations__", + "__dictoffset__", "__init__", "__repr__", "__import__", diff --git a/src/runtime/Native/PyIdentifier_.tt b/src/runtime/Native/PyIdentifier_.tt index 9350cde43..72dc1d70e 100644 --- a/src/runtime/Native/PyIdentifier_.tt +++ b/src/runtime/Native/PyIdentifier_.tt @@ -7,12 +7,12 @@ "__dict__", "__doc__", "__class__", - "__clear_reentry_guard__", "__module__", "__file__", "__slots__", "__self__", "__annotations__", + "__dictoffset__", "__init__", "__repr__", diff --git a/src/runtime/Native/TypeOffset.cs b/src/runtime/Native/TypeOffset.cs index 803b86bfa..282107432 100644 --- a/src/runtime/Native/TypeOffset.cs +++ b/src/runtime/Native/TypeOffset.cs @@ -77,6 +77,8 @@ static partial class TypeOffset internal static int tp_setattro { get; private set; } internal static int tp_str { get; private set; } internal static int tp_traverse { get; private set; } + // Special case: Only available in Python 3.14 onwards, set to -1 by default + internal static int ht_token { get; private set; } = -1; internal static void Use(ITypeOffsets offsets, int extraHeadOffset) { @@ -89,6 +91,19 @@ internal static void Use(ITypeOffsets offsets, int extraHeadOffset) slotNames.Add(offsetProperty.Name); var sourceProperty = typeof(ITypeOffsets).GetProperty(offsetProperty.Name); + if (sourceProperty == null) + { + if ((int)offsetProperty.GetValue(null) == -1) + { + // Skip, this is an optional offset value + continue; + } + else + { + throw new Exception($"No offset defined for necessary slot {offsetProperty.Name}"); + } + } + int value = (int)sourceProperty.GetValue(offsets, null); value += extraHeadOffset; offsetProperty.SetValue(obj: null, value: value, index: null); diff --git a/src/runtime/Native/TypeOffset314.cs b/src/runtime/Native/TypeOffset314.cs new file mode 100644 index 000000000..28101ba12 --- /dev/null +++ b/src/runtime/Native/TypeOffset314.cs @@ -0,0 +1,153 @@ + +// Auto-generated by geninterop.py. +// DO NOT MODIFY BY HAND. + +// Python 3.14: ABI flags: '' + +// ReSharper disable InconsistentNaming +// ReSharper disable IdentifierTypo + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +using Python.Runtime.Native; + +namespace Python.Runtime +{ + [SuppressMessage("Style", "IDE1006:Naming Styles", + Justification = "Following CPython", + Scope = "type")] + + [StructLayout(LayoutKind.Sequential)] + internal class TypeOffset314 : GeneratedTypeOffsets, ITypeOffsets + { + public TypeOffset314() { } + // Auto-generated from PyHeapTypeObject in Python.h + public int ob_refcnt_full { get; private set; } + public int ob_type { get; private set; } + public int ob_size { get; private set; } + public int tp_name { get; private set; } + public int tp_basicsize { get; private set; } + public int tp_itemsize { get; private set; } + public int tp_dealloc { get; private set; } + public int tp_vectorcall_offset { get; private set; } + public int tp_getattr { get; private set; } + public int tp_setattr { get; private set; } + public int tp_as_async { get; private set; } + public int tp_repr { get; private set; } + public int tp_as_number { get; private set; } + public int tp_as_sequence { get; private set; } + public int tp_as_mapping { get; private set; } + public int tp_hash { get; private set; } + public int tp_call { get; private set; } + public int tp_str { get; private set; } + public int tp_getattro { get; private set; } + public int tp_setattro { get; private set; } + public int tp_as_buffer { get; private set; } + public int tp_flags { get; private set; } + public int tp_doc { get; private set; } + public int tp_traverse { get; private set; } + public int tp_clear { get; private set; } + public int tp_richcompare { get; private set; } + public int tp_weaklistoffset { get; private set; } + public int tp_iter { get; private set; } + public int tp_iternext { get; private set; } + public int tp_methods { get; private set; } + public int tp_members { get; private set; } + public int tp_getset { get; private set; } + public int tp_base { get; private set; } + public int tp_dict { get; private set; } + public int tp_descr_get { get; private set; } + public int tp_descr_set { get; private set; } + public int tp_dictoffset { get; private set; } + public int tp_init { get; private set; } + public int tp_alloc { get; private set; } + public int tp_new { get; private set; } + public int tp_free { get; private set; } + public int tp_is_gc { get; private set; } + public int tp_bases { get; private set; } + public int tp_mro { get; private set; } + public int tp_cache { get; private set; } + public int tp_subclasses { get; private set; } + public int tp_weaklist { get; private set; } + public int tp_del { get; private set; } + public int tp_version_tag { get; private set; } + public int tp_finalize { get; private set; } + public int tp_vectorcall { get; private set; } + // This is an error in our generator: + // + // The fields below are actually not pointers (like we incorrectly + // assume for all other fields) but instead a char (1 byte) and a short + // (2 bytes). By dropping one of the fields, we still get the correct + // overall size of the struct. + public int tp_watched { get; private set; } + // public int tp_versions_used { get; private set; } + public int am_await { get; private set; } + public int am_aiter { get; private set; } + public int am_anext { get; private set; } + public int am_send { get; private set; } + public int nb_add { get; private set; } + public int nb_subtract { get; private set; } + public int nb_multiply { get; private set; } + public int nb_remainder { get; private set; } + public int nb_divmod { get; private set; } + public int nb_power { get; private set; } + public int nb_negative { get; private set; } + public int nb_positive { get; private set; } + public int nb_absolute { get; private set; } + public int nb_bool { get; private set; } + public int nb_invert { get; private set; } + public int nb_lshift { get; private set; } + public int nb_rshift { get; private set; } + public int nb_and { get; private set; } + public int nb_xor { get; private set; } + public int nb_or { get; private set; } + public int nb_int { get; private set; } + public int nb_reserved { get; private set; } + public int nb_float { get; private set; } + public int nb_inplace_add { get; private set; } + public int nb_inplace_subtract { get; private set; } + public int nb_inplace_multiply { get; private set; } + public int nb_inplace_remainder { get; private set; } + public int nb_inplace_power { get; private set; } + public int nb_inplace_lshift { get; private set; } + public int nb_inplace_rshift { get; private set; } + public int nb_inplace_and { get; private set; } + public int nb_inplace_xor { get; private set; } + public int nb_inplace_or { get; private set; } + public int nb_floor_divide { get; private set; } + public int nb_true_divide { get; private set; } + public int nb_inplace_floor_divide { get; private set; } + public int nb_inplace_true_divide { get; private set; } + public int nb_index { get; private set; } + public int nb_matrix_multiply { get; private set; } + public int nb_inplace_matrix_multiply { get; private set; } + public int mp_length { get; private set; } + public int mp_subscript { get; private set; } + public int mp_ass_subscript { get; private set; } + public int sq_length { get; private set; } + public int sq_concat { get; private set; } + public int sq_repeat { get; private set; } + public int sq_item { get; private set; } + public int was_sq_slice { get; private set; } + public int sq_ass_item { get; private set; } + public int was_sq_ass_slice { get; private set; } + public int sq_contains { get; private set; } + public int sq_inplace_concat { get; private set; } + public int sq_inplace_repeat { get; private set; } + public int bf_getbuffer { get; private set; } + public int bf_releasebuffer { get; private set; } + public int name { get; private set; } + public int ht_slots { get; private set; } + public int qualname { get; private set; } + public int ht_cached_keys { get; private set; } + public int ht_module { get; private set; } + public int _ht_tpname { get; private set; } + public int ht_token { get; private set; } + public int spec_cache_getitem { get; private set; } + public int getitem_version { get; private set; } + public int init { get; private set; } + } +} + diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index 307b2c3ad..291da366b 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -60,7 +60,7 @@ - - + + diff --git a/src/runtime/PythonEngine.cs b/src/runtime/PythonEngine.cs index 0b28c3a35..264835fff 100644 --- a/src/runtime/PythonEngine.cs +++ b/src/runtime/PythonEngine.cs @@ -88,13 +88,13 @@ public static string ProgramName get { IntPtr p = Runtime.TryUsingDll(() => Runtime.Py_GetProgramName()); - return UcsMarshaler.PtrToPy3UnicodePy2String(p) ?? ""; + return UcsMarshaler.PtrToString(p) ?? ""; } set { Marshal.FreeHGlobal(_programName); _programName = Runtime.TryUsingDll( - () => UcsMarshaler.Py3UnicodePy2StringtoPtr(value) + () => UcsMarshaler.StringToPtr(value) ); Runtime.Py_SetProgramName(_programName); } @@ -106,13 +106,13 @@ public static string PythonHome { EnsureInitialized(); IntPtr p = Runtime.TryUsingDll(() => Runtime.Py_GetPythonHome()); - return UcsMarshaler.PtrToPy3UnicodePy2String(p) ?? ""; + return UcsMarshaler.PtrToString(p) ?? ""; } set { // this value is null in the beginning Marshal.FreeHGlobal(_pythonHome); - _pythonHome = UcsMarshaler.Py3UnicodePy2StringtoPtr(value); + _pythonHome = UcsMarshaler.StringToPtr(value); Runtime.TryUsingDll(() => Runtime.Py_SetPythonHome(_pythonHome)); } } @@ -122,20 +122,20 @@ public static string PythonPath get { IntPtr p = Runtime.TryUsingDll(() => Runtime.Py_GetPath()); - return UcsMarshaler.PtrToPy3UnicodePy2String(p) ?? ""; + return UcsMarshaler.PtrToString(p) ?? ""; } set { Marshal.FreeHGlobal(_pythonPath); _pythonPath = Runtime.TryUsingDll( - () => UcsMarshaler.Py3UnicodePy2StringtoPtr(value) + () => UcsMarshaler.StringToPtr(value) ); Runtime.Py_SetPath(_pythonPath); } } public static Version MinSupportedVersion => new(3, 7); - public static Version MaxSupportedVersion => new(3, 13, int.MaxValue, int.MaxValue); + public static Version MaxSupportedVersion => new(3, 14, int.MaxValue, int.MaxValue); public static bool IsSupportedVersion(Version version) => version >= MinSupportedVersion && version <= MaxSupportedVersion; public static string Version @@ -299,7 +299,7 @@ static void LoadSubmodule(BorrowedReference targetModuleDict, string fullName, s static void LoadMixins(BorrowedReference targetModuleDict) { - foreach (string nested in new[] { "collections" }) + foreach (string nested in new[] { "collections", "dlr" }) { LoadSubmodule(targetModuleDict, fullName: "clr._extras." + nested, diff --git a/src/runtime/PythonTypes/PyFloat.cs b/src/runtime/PythonTypes/PyFloat.cs index 50621d5c2..379228f29 100644 --- a/src/runtime/PythonTypes/PyFloat.cs +++ b/src/runtime/PythonTypes/PyFloat.cs @@ -103,5 +103,7 @@ public static PyFloat AsFloat(PyObject value) public double ToDouble() => Runtime.PyFloat_AsDouble(obj); public override TypeCode GetTypeCode() => TypeCode.Double; + + public override int GetHashCode() => base.GetHashCode(); } } diff --git a/src/runtime/PythonTypes/PyInt.cs b/src/runtime/PythonTypes/PyInt.cs index 0d00f5a13..7ab7f9ec1 100644 --- a/src/runtime/PythonTypes/PyInt.cs +++ b/src/runtime/PythonTypes/PyInt.cs @@ -232,5 +232,7 @@ public string ToString(string format, IFormatProvider formatProvider) } public override TypeCode GetTypeCode() => TypeCode.Int64; + + public override int GetHashCode() => base.GetHashCode(); } } diff --git a/src/runtime/PythonTypes/PyObject.cs b/src/runtime/PythonTypes/PyObject.cs index cf0c2a03f..1949710fb 100644 --- a/src/runtime/PythonTypes/PyObject.cs +++ b/src/runtime/PythonTypes/PyObject.cs @@ -93,6 +93,12 @@ internal PyObject(in StolenReference reference) Finalizer.Instance.ThrottledCollect(); } + /// + /// Create a new PyObject instance of this object, bumping the reference + /// count. + /// + public PyObject NewReference() => new(this); + // Ensure that encapsulated Python object is decref'ed appropriately // when the managed wrapper is garbage-collected. ~PyObject() diff --git a/src/runtime/PythonTypes/PyType.cs b/src/runtime/PythonTypes/PyType.cs index 28bda5d3e..dd82450db 100644 --- a/src/runtime/PythonTypes/PyType.cs +++ b/src/runtime/PythonTypes/PyType.cs @@ -35,6 +35,12 @@ internal PyType(in StolenReference reference, bool prevalidated = false) : base( throw new ArgumentException("object is not a type"); } + /// + /// Create a new PyType instance of this object, bumping the reference + /// count. + /// + public new PyType NewReference() => new(this); + protected PyType(SerializationInfo info, StreamingContext context) : base(info, context) { } internal new static PyType? FromNullableReference(BorrowedReference reference) diff --git a/src/runtime/TypeManager.cs b/src/runtime/TypeManager.cs index 559d5148e..c02d94a1f 100644 --- a/src/runtime/TypeManager.cs +++ b/src/runtime/TypeManager.cs @@ -1,9 +1,12 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Dynamic; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Diagnostics; + using Python.Runtime.Native; using Python.Runtime.StateSerialization; @@ -37,10 +40,190 @@ internal class TypeManager "tp_clear", }; + static readonly DynamicObjectMemberAccessor dynamicMemberAccessor = new(); + + // tp_getattro_dlr_proxy / tp_setattro_dlr_proxy hit HasClrMember on every + // attribute access; cache the reflection result per (Type, name). + static readonly ConcurrentDictionary<(Type, string), bool> _hasClrMemberCache = new(); + + static bool HasClrMember(object instance, string memberName) => + _hasClrMemberCache.GetOrAdd( + (instance.GetType(), memberName), + k => k.Item1.GetMember(k.Item2, BindingFlags.Public | BindingFlags.Instance).Length > 0); + + static bool IsPythonSpecialAttributeName(string memberName) => + memberName.Length > 4 && memberName.StartsWith("__") && memberName.EndsWith("__"); + + static bool TryGetDynamicInstance(BorrowedReference ob, out object instance, out IDynamicMetaObjectProvider dynamicObject) + { + if (ManagedType.GetManagedObject(ob) is CLRObject co && co.inst is IDynamicMetaObjectProvider coDynamic) + { + instance = co.inst; + dynamicObject = coDynamic; + return true; + } + + if (Converter.ToManaged(ob, typeof(IDynamicMetaObjectProvider), out object? managedDynamic, false) + && managedDynamic is IDynamicMetaObjectProvider convertedDynamic) + { + instance = managedDynamic; + dynamicObject = convertedDynamic; + return true; + } + + if (Converter.ToManaged(ob, typeof(object), out object? managedInstance, false) + && managedInstance is IDynamicMetaObjectProvider boxedDynamic) + { + instance = managedInstance; + dynamicObject = boxedDynamic; + return true; + } + + instance = null!; + dynamicObject = null!; + return false; + } + + public static NewReference tp_getattro_dlr_proxy(BorrowedReference ob, BorrowedReference key) + { + var isDynamic = TryGetDynamicInstance(ob, out object instance, out IDynamicMetaObjectProvider dynamicObject); + + // The whole DLR machinery only makes sense with string keys and dynamic objects + if (!isDynamic || !Runtime.PyString_Check(key)) + { + return Runtime.PyObject_GenericGetAttr(ob, key); + } + + string memberName = Runtime.GetManagedString(key)!; + + // Forward requests to GetDynamicMemberNames to the mixin implementation + if (memberName == nameof(DynamicObjectMemberAccessor.GetDynamicMemberNames) + && !HasClrMember(instance, memberName)) + { + using var pyMemberNames = new Func>( + () => dynamicMemberAccessor.GetDynamicMemberNames(dynamicObject) + ).ToPython(); + return pyMemberNames.NewReferenceOrNull(); + } + + // Now, first try to access the Python attribute + var attr = Runtime.PyObject_GenericGetAttr(ob, key); + if (!attr.IsNull()) + return attr; + + // attr is null, so an exception must be set. If that exception is not an AttributeError, + // we return from this function immediately without clearing. All later returns until the + // very end will lead to the AttributeError getting raised. + if (Runtime.PyErr_ExceptionMatches(Exceptions.AttributeError) == 0) + { + return default; + } + + if (HasClrMember(instance, memberName) || IsPythonSpecialAttributeName(memberName)) + { + return default; + } + + bool resolved = false; + object? value = null; + try + { + resolved = dynamicMemberAccessor.TryGetMember(dynamicObject, memberName, out value); + } + catch (Exception e) + { + // Avoid wrapping the CLR exception via Converter.ToPython here: that would trigger + // CLR type initialisation which can re-enter this slot on the same live object, + // causing infinite recursion. A plain RuntimeError with the message is safe. + Runtime.PyErr_Clear(); + Exceptions.SetError(Exceptions.RuntimeError, e.Message); + return default; + } + + if (!resolved) + { + return default; + } + + Runtime.PyErr_Clear(); + + using var pyValue = value.ToPython(); + return pyValue.NewReferenceOrNull(); + } + + public static int tp_setattro_dlr_proxy(BorrowedReference ob, BorrowedReference key, BorrowedReference val) + { + var isDynamic = TryGetDynamicInstance(ob, out object instance, out IDynamicMetaObjectProvider dynamicObject); + + // The whole DLR machinery only makes sense with string keys and dynamic objects + if (!isDynamic || !Runtime.PyString_Check(key)) + { + return Runtime.PyObject_GenericSetAttr(ob, key, val); + } + + string memberName = Runtime.GetManagedString(key)!; + + // For Python-derived types (IPythonDerivedType), the Python descriptor protocol + // (e.g. @property setters) takes priority over DLR member storage. + if (instance is IPythonDerivedType) + { + int pyResult = Runtime.PyObject_GenericSetAttr(ob, key, val); + if (pyResult == 0) + return 0; + + if (Runtime.PyErr_ExceptionMatches(Exceptions.AttributeError) == 0) + return pyResult; + + Runtime.PyErr_Clear(); + // Fall through to DLR fallback below + } + + if (!HasClrMember(instance, memberName) && !IsPythonSpecialAttributeName(memberName)) + { + // Try DLR member storage first + bool handled; + + try + { + if (val.IsNull) + { + handled = dynamicMemberAccessor.TryDeleteMember(dynamicObject, memberName); + } + else + { + object? managedValue = null; + if (val != Runtime.PyNone && !Converter.ToManaged(val, typeof(object), out managedValue, true)) + return -1; + + handled = dynamicMemberAccessor.TrySetMember(dynamicObject, memberName, managedValue); + if (!handled) + { + Exceptions.SetError(Exceptions.AttributeError, $"'{instance.GetType().Name}' object has no attribute '{memberName}'"); + return -1; + } + } + } + catch (Exception e) + { + // Same reasoning as the getter: avoid Converter.ToPython(e) to keep this + // slot re-entry-safe on live dynamic objects. + Exceptions.SetError(Exceptions.RuntimeError, e.Message); + return -1; + } + + if (handled) + return 0; + } + + // Fall back to Python attribute setting + return Runtime.PyObject_GenericSetAttr(ob, key, val); + } + internal static void Initialize() { Debug.Assert(cache.Count == 0, "Cache should be empty", "Some errors may occurred on last shutdown"); + dynamicMemberAccessor.Clear(); using (var plainType = SlotHelper.CreateObjectType()) { subtype_traverse = Util.ReadIntPtr(plainType.Borrow(), TypeOffset.tp_traverse); @@ -64,6 +247,8 @@ internal static void RemoveTypes() } } + dynamicMemberAccessor.Clear(); + foreach (var type in cache.Values) { type.Dispose(); @@ -313,6 +498,13 @@ internal static void InitializeClass(PyType type, ClassBase impl, Type clrType) throw PythonException.ThrowLastAsClrException(); } + if (typeof(IDynamicMetaObjectProvider).IsAssignableFrom(clrType)) + { + InitializeSlot(type, TypeOffset.tp_getattro, new Interop.BB_N(tp_getattro_dlr_proxy), slotsHolder); + InitializeSlot(type, TypeOffset.tp_setattro, new Interop.BBB_I32(tp_setattro_dlr_proxy), slotsHolder); + Runtime.PyType_Modified(type.Reference); + } + var dict = Util.ReadRef(type, TypeOffset.tp_dict); string mn = clrType.Namespace ?? ""; using (var mod = Runtime.PyString_FromString(mn)) @@ -618,6 +810,11 @@ internal static PyType AllocateTypeObject(string name, PyType metatype) Util.WriteIntPtr(type, TypeOffset.tp_traverse, subtype_traverse); Util.WriteIntPtr(type, TypeOffset.tp_clear, subtype_clear); + // This is a new mechanism in Python 3.14. We should eventually use it to implement + // a nicer type check, but for now we just need to ensure that it is set to NULL. + if (TypeOffset.ht_token != -1) + Util.WriteIntPtr(type, TypeOffset.ht_token, IntPtr.Zero); + InheritSubstructs(type.Reference.DangerousGetAddress()); return type; diff --git a/src/runtime/Types/ClassBase.cs b/src/runtime/Types/ClassBase.cs index 2d6ce8a47..3fcb7ca4f 100644 --- a/src/runtime/Types/ClassBase.cs +++ b/src/runtime/Types/ClassBase.cs @@ -374,6 +374,8 @@ public static int tp_clear(BorrowedReference ob) return 0; } + static readonly HashSet ClearVisited = new(); + internal static unsafe int BaseUnmanagedClear(BorrowedReference ob) { var type = Runtime.PyObject_TYPE(ob); @@ -385,21 +387,20 @@ internal static unsafe int BaseUnmanagedClear(BorrowedReference ob) } var clear = (delegate* unmanaged[Cdecl])clearPtr; - bool usesSubtypeClear = clearPtr == TypeManager.subtype_clear; - if (usesSubtypeClear) + if (clearPtr == TypeManager.subtype_clear) { - // workaround for https://bugs.python.org/issue45266 (subtype_clear) - using var dict = Runtime.PyObject_GenericGetDict(ob); - if (Runtime.PyMapping_HasKey(dict.Borrow(), PyIdentifier.__clear_reentry_guard__) != 0) + var addr = ob.DangerousGetAddress(); + if (!ClearVisited.Add(addr)) return 0; - int res = Runtime.PyDict_SetItem(dict.Borrow(), PyIdentifier.__clear_reentry_guard__, Runtime.None); - if (res != 0) return res; - res = clear(ob); - Runtime.PyDict_DelItem(dict.Borrow(), PyIdentifier.__clear_reentry_guard__); + int res = clear(ob); + ClearVisited.Remove(addr); return res; } - return clear(ob); + else + { + return clear(ob); + } } protected override Dictionary OnSave(BorrowedReference ob) diff --git a/src/runtime/Types/ClassDerived.cs b/src/runtime/Types/ClassDerived.cs index 592eefd55..69eba2cc2 100644 --- a/src/runtime/Types/ClassDerived.cs +++ b/src/runtime/Types/ClassDerived.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Dynamic; using System.Diagnostics; using System.Linq; using System.Reflection; @@ -232,6 +233,13 @@ internal static Type CreateDerivedType(string name, continue; } + // Avoid re-entrant DLR binder recursion when Python derives from + // DynamicObject-based types (including overrides in intermediate bases). + if (IsDynamicObjectHookMethod(method)) + { + continue; + } + // skip if this property has already been overridden if ((method.Name.StartsWith("get_") || method.Name.StartsWith("set_")) && pyProperties.Contains(method.Name.Substring(4))) @@ -300,6 +308,35 @@ internal static Type CreateDerivedType(string name, return type; } + static bool IsDynamicObjectHookMethod(MethodInfo method) + { + MethodInfo origin = method.GetBaseDefinition(); + Type? originType = origin.DeclaringType; + if (originType == typeof(DynamicObject)) + { + return origin.Name switch + { + nameof(DynamicObject.TryGetMember) + or nameof(DynamicObject.TrySetMember) + or nameof(DynamicObject.TryDeleteMember) + or nameof(DynamicObject.TryInvokeMember) + or nameof(DynamicObject.TryConvert) + or nameof(DynamicObject.TryGetIndex) + or nameof(DynamicObject.TrySetIndex) + or nameof(DynamicObject.GetDynamicMemberNames) + or nameof(IDynamicMetaObjectProvider.GetMetaObject) => true, + _ => false, + }; + } + + if (originType == typeof(IDynamicMetaObjectProvider)) + { + return origin.Name == nameof(IDynamicMetaObjectProvider.GetMetaObject); + } + + return false; + } + /// /// Add a constructor override that calls the python ctor after calling the base type constructor. /// diff --git a/src/runtime/Types/DynamicObjectMemberAccessor.cs b/src/runtime/Types/DynamicObjectMemberAccessor.cs new file mode 100644 index 000000000..9e1018a15 --- /dev/null +++ b/src/runtime/Types/DynamicObjectMemberAccessor.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Linq.Expressions; +using System.Runtime.CompilerServices; +using Microsoft.CSharp.RuntimeBinder; + +namespace Python.Runtime; + +class DynamicObjectMemberAccessor +{ + const int MaxCacheEntries = 1000; + + readonly ConcurrentLruCache> getters = new(MaxCacheEntries); + readonly ConcurrentLruCache> setters = new(MaxCacheEntries); + readonly ConcurrentLruCache> deleters = new(MaxCacheEntries); + + static readonly CSharpArgumentInfo[] getArgumentInfo = + { + CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), + }; + + static readonly CSharpArgumentInfo[] setArgumentInfo = + { + CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), + CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), + }; + + public bool TryGetMember(IDynamicMetaObjectProvider obj, string memberName, out object? value) + { + if (obj is null) + throw new ArgumentNullException(nameof(obj)); + if (memberName is null) + throw new ArgumentNullException(nameof(memberName)); + + var getter = getters.GetOrAdd(new MemberKey(obj.GetType(), memberName), static key => + { + if (typeof(DynamicObject).IsAssignableFrom(key.Type)) + { + var getBinder = new GetMemberNameBinder(key.MemberName); + return obj => + { + if (((DynamicObject)obj).TryGetMember(getBinder, out object? result)) + { + return result; + } + + throw new RuntimeBinderException($"Could not get member '{key.MemberName}'"); + }; + } + + var binder = Binder.GetMember(CSharpBinderFlags.None, key.MemberName, key.Type, getArgumentInfo); + var callSite = CallSite>.Create(binder); + return obj => callSite.Target(callSite, (IDynamicMetaObjectProvider)obj); + }); + + try + { + value = getter(obj); + return true; + } + catch (RuntimeBinderException) + { + value = null; + return false; + } + } + + public bool TrySetMember(IDynamicMetaObjectProvider obj, string memberName, object? value) + { + if (obj is null) + throw new ArgumentNullException(nameof(obj)); + if (memberName is null) + throw new ArgumentNullException(nameof(memberName)); + + var setter = setters.GetOrAdd(new MemberKey(obj.GetType(), memberName), static key => + { + if (typeof(DynamicObject).IsAssignableFrom(key.Type)) + { + var setBinder = new SetMemberNameBinder(key.MemberName); + return (obj, value) => + { + if (!((DynamicObject)obj).TrySetMember(setBinder, value)) + { + throw new RuntimeBinderException($"Could not set member '{key.MemberName}'"); + } + }; + } + + var binder = Binder.SetMember(CSharpBinderFlags.None, key.MemberName, key.Type, setArgumentInfo); + var callSite = CallSite>.Create(binder); + return (obj, value) => callSite.Target(callSite, (IDynamicMetaObjectProvider)obj, value); + }); + + try + { + setter(obj, value); + return true; + } + catch (RuntimeBinderException) + { + return false; + } + } + + public bool TryDeleteMember(IDynamicMetaObjectProvider obj, string memberName) + { + if (obj is null) + throw new ArgumentNullException(nameof(obj)); + if (memberName is null) + throw new ArgumentNullException(nameof(memberName)); + + var deleter = deleters.GetOrAdd(new MemberKey(obj.GetType(), memberName), static key => + { + if (typeof(DynamicObject).IsAssignableFrom(key.Type)) + { + var binder = new DeleteMemberNameBinder(key.MemberName); + return obj => ((DynamicObject)obj).TryDeleteMember(binder); + } + + if (typeof(ExpandoObject).IsAssignableFrom(key.Type)) + { + return obj => ((IDictionary)(ExpandoObject)obj).Remove(key.MemberName); + } + + return _ => false; + }); + + try + { + return deleter(obj); + } + catch (RuntimeBinderException) + { + return false; + } + } + + public IReadOnlyCollection GetDynamicMemberNames(IDynamicMetaObjectProvider obj) + { + if (obj is null) + throw new ArgumentNullException(nameof(obj)); + + if (obj is ExpandoObject expandoObject) + { + return ((IDictionary)expandoObject).Keys.ToArray(); + } + + if (obj is DynamicObject dynamicObject) + { + return dynamicObject.GetDynamicMemberNames().ToArray(); + } + + var metaObject = obj.GetMetaObject(Expression.Constant(obj)); + return metaObject.GetDynamicMemberNames().ToArray(); + } + + readonly record struct MemberKey(Type Type, string MemberName); + + sealed class DeleteMemberNameBinder : DeleteMemberBinder + { + public DeleteMemberNameBinder(string name) + : base(name, ignoreCase: false) + { + } + + public override DynamicMetaObject FallbackDeleteMember(DynamicMetaObject target, DynamicMetaObject? errorSuggestion) + => errorSuggestion ?? throw new RuntimeBinderException($"Could not delete member '{Name}'"); + } + + sealed class GetMemberNameBinder : GetMemberBinder + { + public GetMemberNameBinder(string name) + : base(name, ignoreCase: false) + { + } + + public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject? errorSuggestion) + => errorSuggestion ?? throw new RuntimeBinderException($"Could not get member '{Name}'"); + } + + sealed class SetMemberNameBinder : SetMemberBinder + { + public SetMemberNameBinder(string name) + : base(name, ignoreCase: false) + { + } + + public override DynamicMetaObject FallbackSetMember( + DynamicMetaObject target, + DynamicMetaObject value, + DynamicMetaObject? errorSuggestion) + => errorSuggestion ?? throw new RuntimeBinderException($"Could not set member '{Name}'"); + } + + public void Clear() + { + getters.Clear(); + setters.Clear(); + deleters.Clear(); + } +} diff --git a/src/runtime/Types/ExtensionType.cs b/src/runtime/Types/ExtensionType.cs index 305fdc15d..114f2d706 100644 --- a/src/runtime/Types/ExtensionType.cs +++ b/src/runtime/Types/ExtensionType.cs @@ -84,8 +84,18 @@ public unsafe static void tp_dealloc(NewReference lastRef) DecrefTypeAndFree(lastRef.Steal()); } + /// + /// Called during tp_clear before the GCHandle is released. + /// Override to eagerly dispose Python object references (PyObject fields) + /// held by the subclass, preventing the multi-hop .NET finalizer chain + /// from delaying Python-side refcount decrements. + /// + protected virtual void OnClear() { } + public static int tp_clear(BorrowedReference ob) { + (GetManagedObject(ob) as ExtensionType)?.OnClear(); + var weakrefs = Runtime.PyObject_GetWeakRefList(ob); if (weakrefs != null) { diff --git a/src/runtime/Types/MetaType.cs b/src/runtime/Types/MetaType.cs index 57fcaa232..ecbc9cac5 100644 --- a/src/runtime/Types/MetaType.cs +++ b/src/runtime/Types/MetaType.cs @@ -21,6 +21,7 @@ internal sealed class MetaType : ManagedType // set in Initialize private static PyType PyCLRMetaType; private static SlotsHolder _metaSlotsHodler; + private static int TypeDictOffset = -1; #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. internal static readonly string[] CustomMethods = new string[] @@ -35,6 +36,25 @@ internal sealed class MetaType : ManagedType public static PyType Initialize() { PyCLRMetaType = TypeManager.CreateMetaType(typeof(MetaType), out _metaSlotsHodler); + + // Retrieve the offset of the type's dictionary from PyType_Type for + // use in the tp_setattro implementation. + using (NewReference dictOffset = Runtime.PyObject_GetAttr(Runtime.PyTypeType, PyIdentifier.__dictoffset__)) + { + if (dictOffset.IsNull()) + { + throw new InvalidOperationException("Could not get __dictoffset__ from PyType_Type"); + } + + nint dictOffsetVal = Runtime.PyLong_AsSignedSize_t(dictOffset.Borrow()); + if (dictOffsetVal <= 0) + { + throw new InvalidOperationException("Could not get __dictoffset__ from PyType_Type"); + } + + TypeDictOffset = checked((int)dictOffsetVal); + } + return PyCLRMetaType; } @@ -44,6 +64,7 @@ public static void Release() { _metaSlotsHodler.ResetSlots(); } + TypeDictOffset = -1; PyCLRMetaType.Dispose(); } @@ -287,7 +308,28 @@ public static int tp_setattro(BorrowedReference tp, BorrowedReference name, Borr } } - int res = Runtime.PyObject_GenericSetAttr(tp, name, value); + // Access the type's dictionary directly + // + // We can not use the PyObject_GenericSetAttr because since Python + // 3.14 as https://github.com/python/cpython/pull/118454 intrdoduced + // an assertion to prevent it from being called from metatypes. + // + // The direct dictionary access is equivalent to what Cython does + // to work around the same issue: https://github.com/cython/cython/pull/6325 + BorrowedReference typeDict = new(Util.ReadIntPtr(tp, TypeDictOffset)); + int res; + if (value.IsNull) + { + res = Runtime.PyDict_DelItem(typeDict, name); + if (res != 0) + { + Exceptions.SetError(Exceptions.AttributeError, "attribute not found"); + } + } + else + { + res = Runtime.PyDict_SetItem(typeDict, name, value); + } Runtime.PyType_Modified(tp); return res; diff --git a/src/runtime/Types/MethodBinding.cs b/src/runtime/Types/MethodBinding.cs index bfe22b0f3..b68f338ff 100644 --- a/src/runtime/Types/MethodBinding.cs +++ b/src/runtime/Types/MethodBinding.cs @@ -18,14 +18,12 @@ internal class MethodBinding : ExtensionType internal MaybeMethodInfo info; internal MethodObject m; internal PyObject? target; - internal PyType? targetType; + internal PyType targetType; - public MethodBinding(MethodObject m, PyObject? target, PyType? targetType = null) + public MethodBinding(MethodObject m, PyObject? target, PyType targetType) { this.target = target; - - this.targetType = targetType ?? target?.GetPythonType(); - + this.targetType = targetType; this.info = null; this.m = m; } @@ -54,7 +52,7 @@ public static NewReference mp_subscript(BorrowedReference tp, BorrowedReference } MethodObject overloaded = self.m.WithOverloads(overloads); - var mb = new MethodBinding(overloaded, self.target, self.targetType); + var mb = new MethodBinding(overloaded, self.target?.NewReference(), self.targetType.NewReference()); return mb.Alloc(); } @@ -141,7 +139,7 @@ public static NewReference tp_getattro(BorrowedReference ob, BorrowedReference k // FIXME: deprecate __overloads__ soon... case "__overloads__": case "Overloads": - var om = new OverloadMapper(self.m, self.target); + var om = new OverloadMapper(self.m, self.target?.NewReference(), self.targetType.NewReference()); return om.Alloc(); case "__signature__" when Runtime.InspectModule is not null: var sig = self.Signature; @@ -249,7 +247,6 @@ public static NewReference tp_call(BorrowedReference ob, BorrowedReference args, } } - /// /// MethodBinding __hash__ implementation. /// @@ -281,5 +278,12 @@ public static NewReference tp_repr(BorrowedReference ob) string name = self.m.name; return Runtime.PyString_FromString($"<{type} method '{name}'>"); } + + protected override void OnClear() + { + target?.Dispose(); + targetType.Dispose(); + target = null; + } } } diff --git a/src/runtime/Types/MethodObject.cs b/src/runtime/Types/MethodObject.cs index 12484d301..1bb3083c0 100644 --- a/src/runtime/Types/MethodObject.cs +++ b/src/runtime/Types/MethodObject.cs @@ -197,7 +197,7 @@ public static NewReference tp_descr_get(BorrowedReference ds, BorrowedReference && self.type.Value.IsInstanceOfType(obj.inst)) { var basecls = ReflectedClrType.GetOrCreate(self.type.Value); - return new MethodBinding(self, new PyObject(ob), basecls).Alloc(); + return new MethodBinding(self, new PyObject(ob), basecls.NewReference()).Alloc(); } return new MethodBinding(self, target: new PyObject(ob), targetType: new PyType(tp)).Alloc(); diff --git a/src/runtime/Types/ModuleObject.cs b/src/runtime/Types/ModuleObject.cs index f641b393e..e525564b2 100644 --- a/src/runtime/Types/ModuleObject.cs +++ b/src/runtime/Types/ModuleObject.cs @@ -19,6 +19,7 @@ internal class ModuleObject : ExtensionType internal PyDict dict; protected string _namespace; private readonly PyList __all__ = new (); + private readonly HashSet allNames = new(); // Attributes to be set on the module according to PEP302 and 451 // by the import machinery. @@ -178,22 +179,23 @@ public void LoadNames() { foreach (string name in AssemblyManager.GetNames(_namespace)) { - cache.TryGetValue(name, out var m); - if (m != null) + bool hasValidAttribute = cache.TryGetValue(name, out var m); + if (!hasValidAttribute) { - continue; - } - BorrowedReference attr = Runtime.PyDict_GetItemString(dict, name); - // If __dict__ has already set a custom property, skip it. - if (!attr.IsNull) - { - continue; + BorrowedReference attr = Runtime.PyDict_GetItemString(dict, name); + // If __dict__ has already set a custom property, skip it. + if (!attr.IsNull) + { + continue; + } + + using var attrVal = GetAttribute(name, true); + hasValidAttribute = !attrVal.IsNull(); } - using var attrVal = GetAttribute(name, true); - if (!attrVal.IsNull()) + if (hasValidAttribute && allNames.Add(name)) { - // if it's a valid attribute, add it to __all__ + // if it's a valid attribute, add it to __all__ once. using var pyname = Runtime.PyString_FromString(name); if (Runtime.PyList_Append(__all__, pyname.Borrow()) != 0) { diff --git a/src/runtime/Types/OverloadMapper.cs b/src/runtime/Types/OverloadMapper.cs index 8f6e30478..79130a669 100644 --- a/src/runtime/Types/OverloadMapper.cs +++ b/src/runtime/Types/OverloadMapper.cs @@ -10,11 +10,13 @@ namespace Python.Runtime internal class OverloadMapper : ExtensionType { private readonly MethodObject m; - private readonly PyObject? target; + private PyObject? target; + readonly PyType targetType; - public OverloadMapper(MethodObject m, PyObject? target) + public OverloadMapper(MethodObject m, PyObject? target, PyType targetType) { this.target = target; + this.targetType = targetType; this.m = m; } @@ -42,7 +44,7 @@ public static NewReference mp_subscript(BorrowedReference tp, BorrowedReference return Exceptions.RaiseTypeError(e); } - var mb = new MethodBinding(self.m, self.target) { info = mi }; + var mb = new MethodBinding(self.m, self.target?.NewReference(), self.targetType.NewReference()) { info = mi }; return mb.Alloc(); } @@ -54,5 +56,12 @@ public static NewReference tp_repr(BorrowedReference op) var self = (OverloadMapper)GetManagedObject(op)!; return self.m.GetDocString(); } + + protected override void OnClear() + { + target?.Dispose(); + targetType.Dispose(); + target = null; + } } } diff --git a/src/runtime/Util/ConcurrentLruCache.cs b/src/runtime/Util/ConcurrentLruCache.cs new file mode 100644 index 000000000..42acc15d1 --- /dev/null +++ b/src/runtime/Util/ConcurrentLruCache.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace Python.Runtime; + +internal sealed class ConcurrentLruCache where TKey : notnull +{ + readonly ConcurrentDictionary> map = new(); + readonly LinkedList lru = new(); + readonly object gate = new(); + + sealed record CacheItem(TKey Key, TValue Value); + + public ConcurrentLruCache(int capacity) + { + if (capacity <= 0) + throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than zero."); + + Capacity = capacity; + } + + public int Capacity { get; private set; } + + public int Count => map.Count; + + public TValue GetOrAdd(TKey key, Func valueFactory) + { + if (valueFactory is null) + throw new ArgumentNullException(nameof(valueFactory)); + + if (TryGetValue(key, out var existing)) + return existing; + + var created = valueFactory(key); + + lock (gate) + { + if (map.TryGetValue(key, out var alreadyAdded)) + { + MoveToFront(alreadyAdded); + return alreadyAdded.Value.Value; + } + + var item = new CacheItem(key, created); + var node = new LinkedListNode(item); + lru.AddFirst(node); + map[key] = node; + EvictOverflow(); + return created; + } + } + + public bool TryGetValue(TKey key, out TValue value) + { + if (map.TryGetValue(key, out var node)) + { + lock (gate) + { + if (map.TryGetValue(key, out node)) + { + MoveToFront(node); + value = node.Value.Value; + return true; + } + } + } + + value = default!; + return false; + } + + public void Clear() + { + lock (gate) + { + lru.Clear(); + map.Clear(); + } + } + + void MoveToFront(LinkedListNode node) + { + if (ReferenceEquals(lru.First, node)) + return; + + lru.Remove(node); + lru.AddFirst(node); + } + + void EvictOverflow() + { + while (map.Count > Capacity) + { + var last = lru.Last; + if (last is null) + return; + + lru.RemoveLast(); + map.TryRemove(last.Value.Key, out _); + } + } +} diff --git a/src/runtime/Util/ReflectionPolyfills.cs b/src/runtime/Util/ReflectionPolyfills.cs index b33698509..4bdf687c8 100644 --- a/src/runtime/Util/ReflectionPolyfills.cs +++ b/src/runtime/Util/ReflectionPolyfills.cs @@ -14,7 +14,7 @@ public static AssemblyBuilder DefineDynamicAssembly(this AppDomain _, AssemblyNa public static Type CreateType(this TypeBuilder typeBuilder) { - return typeBuilder.CreateTypeInfo(); + return typeBuilder.CreateTypeInfo()!; } public static T GetCustomAttribute(this Type type) where T: Attribute diff --git a/src/testing/Python.Test.csproj b/src/testing/Python.Test.csproj index b7ba6cd4e..c2455b1a5 100644 --- a/src/testing/Python.Test.csproj +++ b/src/testing/Python.Test.csproj @@ -1,6 +1,6 @@ - netstandard2.0;net8.0 + netstandard2.0;net10.0 true true ..\pythonnet.snk diff --git a/src/testing/dlrtest.cs b/src/testing/dlrtest.cs new file mode 100644 index 000000000..783a3a133 --- /dev/null +++ b/src/testing/dlrtest.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; + +namespace Python.Test; + +/// +/// Base class for dynamic test helpers. Uses lazy storage initialization so that +/// Python-derived subclasses can safely call DynamicObject member hooks before +/// managed field initializers have run. +/// +public class DynamicStorageObject : DynamicObject +{ + Dictionary storage; + + // Python-defined subclasses may reach this type without running managed field + // initializers (see ClassDerivedObject.NewObjectToPython). Via the lazy init + // we can ensure that the access is still safe, even when the constructor has + // not run. + protected Dictionary Storage => storage ??= []; + + public void AddDynamicMember(string name, object value) => Storage[name] = value; + + public override bool TryGetMember(GetMemberBinder binder, out object result) + => Storage.TryGetValue(binder.Name, out result); + + public override bool TrySetMember(SetMemberBinder binder, object value) + { + Storage[binder.Name] = value; + return true; + } + + public override bool TryDeleteMember(DeleteMemberBinder binder) + => Storage.Remove(binder.Name); + + public override IEnumerable GetDynamicMemberNames() => Storage.Keys; +} + +public class DynamicMappingObject : DynamicStorageObject +{ + // Native members for testing that regular CLR access is unaffected. + public string Label = "default"; + public int Multiplier { get; set; } = 1; + public int Multiply(int value) => value * Multiplier; + + // Test helper: bypass normal member binding and write directly to dynamic storage. + public void SetDynamicValue(string name, object value) => Storage[name] = value; + + // Test helper: retrieve the actual value stored in C# (for verification that None was stored as null) + public object GetDynamicValue(string name) => Storage.TryGetValue(name, out var value) ? value : null; +} + +public class RejectingSetDynamicObject : DynamicStorageObject +{ + public override bool TrySetMember(SetMemberBinder binder, object value) + { + if (!Storage.ContainsKey(binder.Name)) + return false; + + Storage[binder.Name] = value; + return true; + } +} + +public class ThrowingGetDynamicObject : DynamicStorageObject +{ + public override bool TryGetMember(GetMemberBinder binder, out object result) + => throw new InvalidOperationException($"TryGetMember failed for '{binder.Name}'"); +} + +public class ThrowingSetDynamicObject : DynamicStorageObject +{ + public override bool TrySetMember(SetMemberBinder binder, object value) + => throw new InvalidOperationException($"TrySetMember failed for '{binder.Name}'"); +} + +public class RejectingDeleteDynamicObject : DynamicStorageObject +{ + public override bool TryDeleteMember(DeleteMemberBinder binder) + { + if (!Storage.ContainsKey(binder.Name)) + return false; + + return Storage.Remove(binder.Name); + } +} + +public class ThrowingDeleteDynamicObject : DynamicStorageObject +{ + public override bool TryDeleteMember(DeleteMemberBinder binder) + => throw new InvalidOperationException($"TryDeleteMember failed for '{binder.Name}'"); +} diff --git a/tests/conftest.py b/tests/conftest.py index 1ac20e1dd..0576e161e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -# TODO: move tests one out of src to project root. -# TODO: travis has numpy on their workers. Maybe add tests? """Helpers for testing.""" diff --git a/tests/domain_tests/Python.DomainReloadTests.csproj b/tests/domain_tests/Python.DomainReloadTests.csproj index 9cb61c6f4..a7d6d4f6b 100644 --- a/tests/domain_tests/Python.DomainReloadTests.csproj +++ b/tests/domain_tests/Python.DomainReloadTests.csproj @@ -14,13 +14,17 @@ - + - + + 1.* + all + runtime; build; native; contentfiles; analyzers + diff --git a/tests/test_array.py b/tests/test_array.py index d207a36fb..0d4028be8 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -120,10 +120,9 @@ def test_array_contains(): assert 3 in items assert 4 in items - assert not (5 in items) # "H:\Python27\Lib\unittest\case.py", line 592, in deprecated_func, - assert not (-1 in items) # TypeError: int() argument must be a string or a number, not 'NoneType' - assert not (None in items) # which threw ^ here which is a little odd. - # But when run from runtests.py. Not when this module ran by itself. + assert not (5 in items) + assert not (-1 in items) + assert not (None in items) def test_boolean_array(): diff --git a/tests/test_disposable.py b/tests/test_disposable.py new file mode 100644 index 000000000..3c8fb1159 --- /dev/null +++ b/tests/test_disposable.py @@ -0,0 +1,111 @@ +import pytest + +from System.IO import MemoryStream, FileStream, FileMode, File, Path, StreamWriter + + +def test_memory_stream_context_manager(): + """Test that MemoryStream can be used as a context manager""" + data = bytes([1, 2, 3, 4, 5]) + + with MemoryStream() as stream: + # Convert Python bytes to .NET byte array for proper writing + from System import Array, Byte + + dotnet_bytes = Array[Byte](data) + stream.Write(dotnet_bytes, 0, len(dotnet_bytes)) + + assert stream.Length == 5 + stream.Position = 0 + + # Create a .NET byte array to read into + buffer = Array[Byte](5) + stream.Read(buffer, 0, 5) + + # Convert back to Python bytes for comparison + result = bytes(buffer) + assert result == data + + # The stream should be disposed (closed) after the with block + with pytest.raises(Exception): + stream.Position = 0 # This should fail because the stream is closed + + +def test_file_stream_context_manager(tmpdir: str): + """Test that FileStream can be used as a context manager""" + # Create a temporary file path + temp_path = Path.Combine(str(tmpdir), Path.GetRandomFileName()) + + try: + # Write data to the file using with statement + data = "Hello, context manager!" + with FileStream(temp_path, FileMode.Create) as fs: + writer = StreamWriter(fs) + writer.Write(data) + writer.Flush() + + # Verify the file was written and stream was closed + assert File.Exists(temp_path) + content = File.ReadAllText(temp_path) + assert content == data + + # The stream should be disposed after the with block + with pytest.raises(Exception): + fs.Position = 0 # This should fail because the stream is closed + finally: + # Clean up + if File.Exists(temp_path): + File.Delete(temp_path) + + +def test_disposable_in_multiple_contexts(): + """Test that using .NET IDisposable objects in multiple contexts works correctly""" + # Create multiple streams and check that they're all properly disposed + + # Create a list to track if streams were properly disposed + # (we'll check this by trying to access the stream after disposal) + streams_disposed = [False, False] + + # Use nested context managers with .NET IDisposable objects + with MemoryStream() as outer_stream: + # Write some data to the outer stream + from System import Array, Byte + + outer_data = Array[Byte]([10, 20, 30]) + outer_stream.Write(outer_data, 0, len(outer_data)) + + # Check that the outer stream is usable + assert outer_stream.Length == 3 + + with MemoryStream() as inner_stream: + # Write different data to the inner stream + inner_data = Array[Byte]([40, 50, 60, 70]) + inner_stream.Write(inner_data, 0, len(inner_data)) + + # Check that the inner stream is usable + assert inner_stream.Length == 4 + + # Try to use the inner stream - should fail because it's disposed + try: + inner_stream.Position = 0 + except Exception: + streams_disposed[1] = True + + # Try to use the outer stream - should fail because it's disposed + try: + outer_stream.Position = 0 + except Exception: + streams_disposed[0] = True + + # Verify both streams were properly disposed + assert all(streams_disposed) + + +def test_exception_handling(): + """Test that exceptions propagate correctly through the context manager""" + with pytest.raises(ValueError): + with MemoryStream() as stream: + raise ValueError("Test exception") + + # Stream should be disposed despite the exception + with pytest.raises(Exception): + stream.Position = 0 diff --git a/tests/test_dynamic.py b/tests/test_dynamic.py new file mode 100644 index 000000000..f093ee19a --- /dev/null +++ b/tests/test_dynamic.py @@ -0,0 +1,218 @@ +# -*- coding: utf-8 -*- + +import pytest +from System.Collections.Generic import Dictionary +from System.Dynamic import ExpandoObject + +from Python.Test import DynamicMappingObject +from Python.Test import RejectingDeleteDynamicObject +from Python.Test import RejectingSetDynamicObject +from Python.Test import ThrowingDeleteDynamicObject +from Python.Test import ThrowingGetDynamicObject +from Python.Test import ThrowingSetDynamicObject + + +def _mro_names(obj): + return [f"{t.__module__}.{t.__name__}" for t in type(obj).__mro__] + + +@pytest.mark.parametrize( + "obj, expected", + [ + (DynamicMappingObject(), True), + (ExpandoObject(), True), + (Dictionary[str, int](), False), + ], +) +def test_dlr_mixin_presence(obj, expected): + has_mixin = "clr._extras.dlr.DynamicMetaObjectProviderMixin" in _mro_names(obj) + assert has_mixin is expected + + +@pytest.mark.parametrize("obj", [DynamicMappingObject(), ExpandoObject()]) +def test_dynamic_binder(obj): + assert "answer" not in dir(obj) + assert "wrong_answer" not in dir(obj) + + setattr(obj, "answer", 42) + obj.wrong_answer = 54 + + assert obj.answer == 42 + assert obj.wrong_answer == 54 + + assert "answer" in dir(obj) + assert "wrong_answer" in dir(obj) + + +def test_native_members_are_accessible_and_keep_priority(): + obj = DynamicMappingObject() + setattr(obj, "answer", 42) + obj.SetDynamicValue("Multiplier", 999) + + # Native field + assert obj.Label == "default" + obj.Label = "changed" + assert obj.Label == "changed" + + # Native property takes precedence over dynamic fallback + assert obj.Multiplier == 1 + obj.Multiplier = 7 + assert obj.Multiplier == 7 + + # Native method + obj.Multiplier = 3 + assert obj.Multiply(5) == 15 + +def test_dynamic_and_native_members_coexist(): + obj = DynamicMappingObject() + setattr(obj, "answer", 42) + obj.Multiplier = 2 + assert obj.answer == 42 + assert obj.Multiplier == 2 + assert obj.Multiply(10) == 20 + + +@pytest.mark.parametrize("obj", [DynamicMappingObject(), ExpandoObject()]) +def test_set_and_get_dynamic_property(obj): + """Test that setting and getting dynamic properties goes through DLR binder.""" + # Get initial value (should be None for non-existent property) + assert not hasattr(obj, "MyProp") + + # Set a dynamic property to a value + obj.MyProp = 42 + assert obj.MyProp == 42 + + # Set to None and verify it stays None through DLR + obj.MyProp = None + assert obj.MyProp is None + + # Set to another value and verify + obj.MyProp = "hello" + assert obj.MyProp == "hello" + + +def test_update_dynamic_value(): + """Dynamic-only members should use DLR get/set/modify/delete end-to-end.""" + obj = DynamicMappingObject() + assert not hasattr(obj, "DynamicOnly") + + # Initial set should create a dynamic member + obj.DynamicOnly = "initial" + assert obj.DynamicOnly == "initial" + + # Modify flows through TrySetMember + obj.DynamicOnly = "updated" + assert obj.DynamicOnly == "updated" + + # Setting None keeps a present member with None value + obj.DynamicOnly = None + assert obj.DynamicOnly is None + + # Delete flows through TryDeleteMember + del obj.DynamicOnly + assert "DynamicOnly" not in dir(obj) + assert not hasattr(obj, "DynamicOnly") + + +def test_dynamic_set_none_updates_managed_store_after_get(): + """Regression: get->set(None)->get must route through DLR and update managed storage.""" + obj = DynamicMappingObject() + obj.SetDynamicValue("MyProp", "initial") + + x = obj.MyProp + assert x == "initial" + + obj.MyProp = None + + y = obj.MyProp + assert y is None + assert obj.GetDynamicValue("MyProp") is None + + +@pytest.mark.parametrize("obj", [DynamicMappingObject(), ExpandoObject()]) +def test_dynamic_member_lifecycle(obj): + """Dynamic members should support set/modify/get/delete via the DLR binder.""" + name = "LifecycleMember" + + assert not hasattr(obj, name) + + setattr(obj, name, 1) + assert getattr(obj, name) == 1 + + setattr(obj, name, 2) + assert getattr(obj, name) == 2 + + delattr(obj, name) + assert not hasattr(obj, name) + + +def test_derive_from_dynamic_class(): + class MyMappingObject(DynamicMappingObject): + __namespace__ = "PythonNetTest" + + def __init__(self): + self._custom = 0 + + @property + def custom_property(self): + return self._custom + + @custom_property.setter + def custom_property(self, i): + self._custom += i + + + obj = MyMappingObject() + with pytest.raises(AttributeError): + x = obj.unknown_property + + assert obj.custom_property == 0 + + obj.custom_property = 5 + assert obj.custom_property == 5 + + obj.custom_property = 5 + assert obj.custom_property == 10 + + obj.other_property = None + assert obj.other_property is None + + +def test_trysetmember_false_raises_attributeerror_instead_of_silent_python_setattr(): + obj = RejectingSetDynamicObject() + + with pytest.raises(AttributeError): + obj.typoed_name = 42 + + assert not hasattr(obj, "typoed_name") + + +def test_trygetmember_exception_is_raised_in_python(): + obj = ThrowingGetDynamicObject() + obj.AddDynamicMember("any_key", 1) + + with pytest.raises(Exception, match="TryGetMember failed for 'any_key'"): + _ = obj.any_key + + +def test_trysetmember_exception_is_raised_in_python(): + obj = ThrowingSetDynamicObject() + + with pytest.raises(Exception, match="TrySetMember failed for 'bad_name'"): + obj.bad_name = 42 + + +def test_trydeletemember_false_raises_attributeerror(): + obj = RejectingDeleteDynamicObject() + obj.AddDynamicMember("existing_name", 42) + + with pytest.raises(AttributeError): + del obj.missing_name + + +def test_trydeletemember_exception_is_raised_in_python(): + obj = ThrowingDeleteDynamicObject() + obj.bad_name = 42 + + with pytest.raises(Exception, match="TryDeleteMember failed for 'bad_name'"): + del obj.bad_name \ No newline at end of file diff --git a/tests/test_field.py b/tests/test_field.py index 52fed54cb..c45a85468 100644 --- a/tests/test_field.py +++ b/tests/test_field.py @@ -190,7 +190,6 @@ def test_field_descriptor_abuse(): def test_boolean_field(): """Test boolean fields.""" - # change this to true / false later for Python 2.3? ob = FieldTest() assert ob.BooleanField is False diff --git a/tests/test_import.py b/tests/test_import.py index 877eacd84..f90192190 100644 --- a/tests/test_import.py +++ b/tests/test_import.py @@ -5,6 +5,11 @@ import pytest import sys +# Unused import to preload the class +# +# This resulted in the FileStream name missing from the wildcard import later +from System.IO import FileStream # noqa: F401 + def test_relative_missing_import(): """Test that a relative missing import doesn't crash. Some modules use this to check if a package is installed. diff --git a/tests/test_method.py b/tests/test_method.py index c70200c7e..53c614498 100644 --- a/tests/test_method.py +++ b/tests/test_method.py @@ -4,8 +4,29 @@ import System import pytest +import sys +import gc +import tracemalloc from Python.Test import MethodTest +@pytest.fixture(scope="function") +def memory_usage_tracking(): + was_tracing = tracemalloc.is_tracing() + if not was_tracing: + tracemalloc.start() + yield + if not was_tracing: + tracemalloc.stop() + +def _get_total_memory_bytes() -> int: + """Get total memory consumption combining .NET GC memory and Python tracemalloc.""" + dotnet_memory = System.GC.GetTotalMemory(forceFullCollection=False) + # Get Python-side memory + current, _peak = tracemalloc.get_traced_memory() + # Return combined measurement + return dotnet_memory + current + + def test_instance_method_overwritable(): """Test instance method overwriting.""" @@ -110,7 +131,7 @@ def test_overloaded_method_inheritance(): def test_method_descriptor_abuse(): """Test method descriptor abuse.""" - desc = MethodTest.__dict__['PublicMethod'] + desc = MethodTest.__dict__["PublicMethod"] with pytest.raises(TypeError): desc.__get__(0, 0) @@ -122,7 +143,7 @@ def test_method_descriptor_abuse(): def test_method_docstrings(): """Test standard method docstring generation""" method = MethodTest.GetType - value = 'System.Type GetType()' + value = "System.Type GetType()" assert method.__doc__ == value @@ -180,36 +201,36 @@ def test_null_array_conversion(): def test_string_params_args(): """Test use of string params.""" - result = MethodTest.TestStringParamsArg('one', 'two', 'three') + result = MethodTest.TestStringParamsArg("one", "two", "three") assert result.Length == 4 assert len(result) == 4, result - assert result[0] == 'one' - assert result[1] == 'two' - assert result[2] == 'three' + assert result[0] == "one" + assert result[1] == "two" + assert result[2] == "three" # ensures params string[] overload takes precedence over params object[] - assert result[3] == 'tail' + assert result[3] == "tail" - result = MethodTest.TestStringParamsArg(['one', 'two', 'three']) + result = MethodTest.TestStringParamsArg(["one", "two", "three"]) assert len(result) == 4 - assert result[0] == 'one' - assert result[1] == 'two' - assert result[2] == 'three' - assert result[3] == 'tail' + assert result[0] == "one" + assert result[1] == "two" + assert result[2] == "three" + assert result[3] == "tail" def test_object_params_args(): """Test use of object params.""" - result = MethodTest.TestObjectParamsArg('one', 'two', 'three') + result = MethodTest.TestObjectParamsArg("one", "two", "three") assert len(result) == 3, result - assert result[0] == 'one' - assert result[1] == 'two' - assert result[2] == 'three' + assert result[0] == "one" + assert result[1] == "two" + assert result[2] == "three" - result = MethodTest.TestObjectParamsArg(['one', 'two', 'three']) + result = MethodTest.TestObjectParamsArg(["one", "two", "three"]) assert len(result) == 3, result - assert result[0] == 'one' - assert result[1] == 'two' - assert result[2] == 'three' + assert result[0] == "one" + assert result[1] == "two" + assert result[2] == "three" def test_value_params_args(): @@ -233,38 +254,42 @@ def test_non_params_array_in_last_place(): result = MethodTest.TestNonParamsArrayInLastPlace(1, 2, 3) assert result + def test_params_methods_with_no_params(): """Tests that passing no arguments to a params method passes an empty array""" result = MethodTest.TestValueParamsArg() assert len(result) == 0 - result = MethodTest.TestOneArgWithParams('Some String') + result = MethodTest.TestOneArgWithParams("Some String") assert len(result) == 0 - result = MethodTest.TestTwoArgWithParams('Some String', 'Some Other String') + result = MethodTest.TestTwoArgWithParams("Some String", "Some Other String") assert len(result) == 0 + def test_params_methods_with_non_lists(): """Tests that passing single parameters to a params method will convert into an array on the .NET side""" - result = MethodTest.TestOneArgWithParams('Some String', [1, 2, 3, 4]) + result = MethodTest.TestOneArgWithParams("Some String", [1, 2, 3, 4]) assert len(result) == 4 - result = MethodTest.TestOneArgWithParams('Some String', 1, 2, 3, 4) + result = MethodTest.TestOneArgWithParams("Some String", 1, 2, 3, 4) assert len(result) == 4 - result = MethodTest.TestOneArgWithParams('Some String', [5]) + result = MethodTest.TestOneArgWithParams("Some String", [5]) assert len(result) == 1 - result = MethodTest.TestOneArgWithParams('Some String', 5) + result = MethodTest.TestOneArgWithParams("Some String", 5) assert len(result) == 1 + def test_params_method_with_lists(): """Tests passing multiple lists to a params object[] method""" - result = MethodTest.TestObjectParamsArg([],[]) + result = MethodTest.TestObjectParamsArg([], []) assert len(result) == 2 + def test_string_out_params(): """Test use of string out-parameters.""" result = MethodTest.TestStringOutParams("hi", "there") @@ -468,15 +493,13 @@ def test_two_default_param(): def test_explicit_selection_with_out_modifier(): """Check explicit overload selection with out modifiers.""" refstr = System.String("").GetType().MakeByRefType() - result = MethodTest.TestStringOutParams.__overloads__[str, refstr]( - "hi", "there") + result = MethodTest.TestStringOutParams.__overloads__[str, refstr]("hi", "there") assert isinstance(result, tuple) assert len(result) == 2 assert result[0] is True assert result[1] == "output string" - result = MethodTest.TestStringOutParams.__overloads__[str, refstr]( - "hi", None) + result = MethodTest.TestStringOutParams.__overloads__[str, refstr]("hi", None) assert isinstance(result, tuple) assert len(result) == 2 assert result[0] is True @@ -486,15 +509,13 @@ def test_explicit_selection_with_out_modifier(): def test_explicit_selection_with_ref_modifier(): """Check explicit overload selection with ref modifiers.""" refstr = System.String("").GetType().MakeByRefType() - result = MethodTest.TestStringRefParams.__overloads__[str, refstr]( - "hi", "there") + result = MethodTest.TestStringRefParams.__overloads__[str, refstr]("hi", "there") assert isinstance(result, tuple) assert len(result) == 2 assert result[0] is True assert result[1] == "output string" - result = MethodTest.TestStringRefParams.__overloads__[str, refstr]( - "hi", None) + result = MethodTest.TestStringRefParams.__overloads__[str, refstr]("hi", None) assert isinstance(result, tuple) assert len(result) == 2 assert result[0] is True @@ -520,8 +541,8 @@ def test_explicit_overload_selection(): value = MethodTest.Overloaded.__overloads__[System.SByte](127) assert value == 127 - value = MethodTest.Overloaded.__overloads__[System.Char](u'A') - assert value == u'A' + value = MethodTest.Overloaded.__overloads__[System.Char]("A") + assert value == "A" value = MethodTest.Overloaded.__overloads__[System.Char](65535) assert value == chr(65535) @@ -535,37 +556,28 @@ def test_explicit_overload_selection(): value = MethodTest.Overloaded.__overloads__[int](2147483647) assert value == 2147483647 - value = MethodTest.Overloaded.__overloads__[System.Int64]( - 9223372036854775807 - ) + value = MethodTest.Overloaded.__overloads__[System.Int64](9223372036854775807) assert value == 9223372036854775807 value = MethodTest.Overloaded.__overloads__[System.UInt16](65000) assert value == 65000 - value = MethodTest.Overloaded.__overloads__[System.UInt32]( - 4294967295 - ) + value = MethodTest.Overloaded.__overloads__[System.UInt32](4294967295) assert value == 4294967295 - value = MethodTest.Overloaded.__overloads__[System.UInt64]( - 18446744073709551615 - ) + value = MethodTest.Overloaded.__overloads__[System.UInt64](18446744073709551615) assert value == 18446744073709551615 value = MethodTest.Overloaded.__overloads__[System.Single](3.402823e38) assert value == System.Single(3.402823e38) - value = MethodTest.Overloaded.__overloads__[System.Double]( - 1.7976931348623157e308) + value = MethodTest.Overloaded.__overloads__[System.Double](1.7976931348623157e308) assert value == 1.7976931348623157e308 - value = MethodTest.Overloaded.__overloads__[float]( - 1.7976931348623157e308) + value = MethodTest.Overloaded.__overloads__[float](1.7976931348623157e308) assert value == 1.7976931348623157e308 - value = MethodTest.Overloaded.__overloads__[System.Decimal]( - System.Decimal.One) + value = MethodTest.Overloaded.__overloads__[System.Decimal](System.Decimal.One) assert value == System.Decimal.One value = MethodTest.Overloaded.__overloads__[System.String]("spam") @@ -590,7 +602,8 @@ def test_explicit_overload_selection(): atype = Array[System.Object] value = MethodTest.Overloaded.__overloads__[str, int, atype]( - "one", 1, atype([1, 2, 3])) + "one", 1, atype([1, 2, 3]) + ) assert value == 3 value = MethodTest.Overloaded.__overloads__[str, int]("one", 1) @@ -632,10 +645,10 @@ def test_overload_selection_with_array_types(): assert value[1] == 127 vtype = Array[System.Char] - input_ = vtype([u'A', u'Z']) + input_ = vtype(["A", "Z"]) value = MethodTest.Overloaded.__overloads__[vtype](input_) - assert value[0] == u'A' - assert value[1] == u'Z' + assert value[0] == "A" + assert value[1] == "Z" vtype = Array[System.Char] input_ = vtype([0, 65535]) @@ -769,7 +782,7 @@ def test_we_can_bind_to_encoding_get_string(): from System.Text import Encoding from System.IO import MemoryStream - my_bytes = Encoding.UTF8.GetBytes('Some testing string') + my_bytes = Encoding.UTF8.GetBytes("Some testing string") stream = MemoryStream() stream.Write(my_bytes, 0, my_bytes.Length) stream.Position = 0 @@ -784,8 +797,8 @@ def test_we_can_bind_to_encoding_get_string(): temp = Encoding.UTF8.GetString(buff, 0, read) data.append(temp) - data = ''.join(data) - assert data == 'Some testing string' + data = "".join(data) + assert data == "Some testing string" def test_wrong_overload(): @@ -833,6 +846,7 @@ def test_no_object_in_param(): with pytest.raises(TypeError): MethodTest.TestOverloadedNoObject(2147483648) + def test_object_in_param(): """Test regression introduced by #151 in which Object method overloads aren't being used. See #203 for issue.""" @@ -916,9 +930,10 @@ def test_object_in_multiparam_exception(): e = excinfo.value c = e.__cause__ - assert c.GetType().FullName == 'System.AggregateException' + assert c.GetType().FullName == "System.AggregateException" assert len(c.InnerExceptions) == 2 + def test_case_sensitive(): """Test that case-sensitivity is respected. GH#81""" @@ -931,26 +946,27 @@ def test_case_sensitive(): with pytest.raises(AttributeError): MethodTest.casesensitive() + def test_getting_generic_method_binding_does_not_leak_ref_count(): """Test that managed object is freed after calling generic method. Issue #691""" from PlainOldNamespace import PlainOldClass - import sys - refCount = sys.getrefcount(PlainOldClass().GenericMethod[str]) assert refCount == 1 -def test_getting_generic_method_binding_does_not_leak_memory(): + +def test_getting_generic_method_binding_does_not_leak_memory(memory_usage_tracking): """Test that managed object is freed after calling generic method. Issue #691""" from PlainOldNamespace import PlainOldClass - import psutil, os, gc, clr - - process = psutil.Process(os.getpid()) - processBytesBeforeCall = process.memory_info().rss - print("\n\nMemory consumption (bytes) at start of test: " + str(processBytesBeforeCall)) + tracemalloc.start() + processBytesBeforeCall = _get_total_memory_bytes() + print( + "\n\nMemory consumption (bytes) at start of test: " + + str(processBytesBeforeCall) + ) iterations = 500 for i in range(iterations): @@ -959,7 +975,7 @@ def test_getting_generic_method_binding_does_not_leak_memory(): gc.collect() System.GC.Collect() - processBytesAfterCall = process.memory_info().rss + processBytesAfterCall = _get_total_memory_bytes() print("Memory consumption (bytes) at end of test: " + str(processBytesAfterCall)) processBytesDelta = processBytesAfterCall - processBytesBeforeCall print("Memory delta: " + str(processBytesDelta)) @@ -967,31 +983,33 @@ def test_getting_generic_method_binding_does_not_leak_memory(): bytesAllocatedPerIteration = pow(2, 20) # 1MB bytesLeakedPerIteration = processBytesDelta / iterations - # Allow 50% threshold - this shows the original issue is fixed, which leaks the full allocated bytes per iteration - failThresholdBytesLeakedPerIteration = bytesAllocatedPerIteration / 2 + # Tight 10% threshold: with the fix the per-iteration leak is essentially + # zero, while the bug retains the bulk of the 1 MB payload (~600 KB/iter + # on 3.14 GIL). 100 KB/iter cleanly distinguishes the two states. + failThresholdBytesLeakedPerIteration = bytesAllocatedPerIteration * 0.1 assert bytesLeakedPerIteration < failThresholdBytesLeakedPerIteration + def test_getting_overloaded_method_binding_does_not_leak_ref_count(): """Test that managed object is freed after calling overloaded method. Issue #691""" from PlainOldNamespace import PlainOldClass - import sys - refCount = sys.getrefcount(PlainOldClass().OverloadedMethod.Overloads[int]) assert refCount == 1 -def test_getting_overloaded_method_binding_does_not_leak_memory(): + +def test_getting_overloaded_method_binding_does_not_leak_memory(memory_usage_tracking): """Test that managed object is freed after calling overloaded method. Issue #691""" from PlainOldNamespace import PlainOldClass - import psutil, os, gc, clr - - process = psutil.Process(os.getpid()) - processBytesBeforeCall = process.memory_info().rss - print("\n\nMemory consumption (bytes) at start of test: " + str(processBytesBeforeCall)) + processBytesBeforeCall = _get_total_memory_bytes() + print( + "\n\nMemory consumption (bytes) at start of test: " + + str(processBytesBeforeCall) + ) iterations = 500 for i in range(iterations): @@ -1000,7 +1018,7 @@ def test_getting_overloaded_method_binding_does_not_leak_memory(): gc.collect() System.GC.Collect() - processBytesAfterCall = process.memory_info().rss + processBytesAfterCall = _get_total_memory_bytes() print("Memory consumption (bytes) at end of test: " + str(processBytesAfterCall)) processBytesDelta = processBytesAfterCall - processBytesBeforeCall print("Memory delta: " + str(processBytesDelta)) @@ -1008,11 +1026,12 @@ def test_getting_overloaded_method_binding_does_not_leak_memory(): bytesAllocatedPerIteration = pow(2, 20) # 1MB bytesLeakedPerIteration = processBytesDelta / iterations - # Allow 50% threshold - this shows the original issue is fixed, which leaks the full allocated bytes per iteration - failThresholdBytesLeakedPerIteration = bytesAllocatedPerIteration / 2 + # Tight 10% threshold; see test_getting_generic_method_binding_does_not_leak_memory. + failThresholdBytesLeakedPerIteration = bytesAllocatedPerIteration * 0.1 assert bytesLeakedPerIteration < failThresholdBytesLeakedPerIteration + def test_getting_method_overloads_binding_does_not_leak_ref_count(): """Test that managed object is freed after calling overloaded method. Issue #691""" @@ -1023,17 +1042,17 @@ def test_getting_method_overloads_binding_does_not_leak_ref_count(): refCount = sys.getrefcount(PlainOldClass().OverloadedMethod.Overloads) assert refCount == 1 -@pytest.mark.xfail(reason="Fails locally, need to investigate later", strict=False) -def test_getting_method_overloads_binding_does_not_leak_memory(): + +def test_getting_method_overloads_binding_does_not_leak_memory(memory_usage_tracking): """Test that managed object is freed after calling overloaded method. Issue #691""" from PlainOldNamespace import PlainOldClass - import psutil, os, gc, clr - - process = psutil.Process(os.getpid()) - processBytesBeforeCall = process.memory_info().rss - print("\n\nMemory consumption (bytes) at start of test: " + str(processBytesBeforeCall)) + processBytesBeforeCall = _get_total_memory_bytes() + print( + "\n\nMemory consumption (bytes) at start of test: " + + str(processBytesBeforeCall) + ) iterations = 500 for i in range(iterations): @@ -1042,7 +1061,7 @@ def test_getting_method_overloads_binding_does_not_leak_memory(): gc.collect() System.GC.Collect() - processBytesAfterCall = process.memory_info().rss + processBytesAfterCall = _get_total_memory_bytes() print("Memory consumption (bytes) at end of test: " + str(processBytesAfterCall)) processBytesDelta = processBytesAfterCall - processBytesBeforeCall print("Memory delta: " + str(processBytesDelta)) @@ -1050,18 +1069,17 @@ def test_getting_method_overloads_binding_does_not_leak_memory(): bytesAllocatedPerIteration = pow(2, 20) # 1MB bytesLeakedPerIteration = processBytesDelta / iterations - # Allow 50% threshold - this shows the original issue is fixed, which leaks the full allocated bytes per iteration - failThresholdBytesLeakedPerIteration = bytesAllocatedPerIteration / 2 + # Tight 10% threshold; see test_getting_generic_method_binding_does_not_leak_memory. + failThresholdBytesLeakedPerIteration = bytesAllocatedPerIteration * 0.1 assert bytesLeakedPerIteration < failThresholdBytesLeakedPerIteration + def test_getting_overloaded_constructor_binding_does_not_leak_ref_count(): """Test that managed object is freed after calling overloaded constructor, constructorbinding.cs mp_subscript. Issue #691""" from PlainOldNamespace import PlainOldClass - import sys - # simple test refCount = sys.getrefcount(PlainOldClass.Overloads[int]) assert refCount == 1 @@ -1069,7 +1087,7 @@ def test_getting_overloaded_constructor_binding_does_not_leak_ref_count(): def test_default_params(): # all positional parameters - res = MethodTest.DefaultParams(1,2,3,4) + res = MethodTest.DefaultParams(1, 2, 3, 4) assert res == "1234" res = MethodTest.DefaultParams(1, 2, 3) @@ -1100,7 +1118,8 @@ def test_default_params(): assert res == "1037" with pytest.raises(TypeError): - MethodTest.DefaultParams(1,2,3,4,5) + MethodTest.DefaultParams(1, 2, 3, 4, 5) + def test_optional_params(): res = MethodTest.OptionalParams(1, 2, 3, 4) @@ -1139,10 +1158,10 @@ def test_optional_params(): res = MethodTest.OptionalParams_TestMissing(None) assert res == False - res = MethodTest.OptionalParams_TestMissing(a = None) + res = MethodTest.OptionalParams_TestMissing(a=None) assert res == False - res = MethodTest.OptionalParams_TestMissing(a='hi') + res = MethodTest.OptionalParams_TestMissing(a="hi") assert res == False res = MethodTest.OptionalParams_TestReferenceType() @@ -1154,12 +1173,13 @@ def test_optional_params(): res = MethodTest.OptionalParams_TestReferenceType(a=None) assert res == True - res = MethodTest.OptionalParams_TestReferenceType('hi') + res = MethodTest.OptionalParams_TestReferenceType("hi") assert res == False - res = MethodTest.OptionalParams_TestReferenceType(a='hi') + res = MethodTest.OptionalParams_TestReferenceType(a="hi") assert res == False + def test_optional_and_default_params(): res = MethodTest.OptionalAndDefaultParams() @@ -1177,12 +1197,13 @@ def test_optional_and_default_params(): res = MethodTest.OptionalAndDefaultParams2() assert res == "0012" - res = MethodTest.OptionalAndDefaultParams2(a=1,b=2,c=3,d=4) + res = MethodTest.OptionalAndDefaultParams2(a=1, b=2, c=3, d=4) assert res == "1234" res = MethodTest.OptionalAndDefaultParams2(b=2, c=3) assert res == "0232" + def test_default_params_overloads(): res = MethodTest.DefaultParamsWithOverloading(1, 2) assert res == "12" @@ -1208,16 +1229,19 @@ def test_default_params_overloads(): res = MethodTest.DefaultParamsWithOverloading(1, d=1) assert res == "1671XXX" + def test_default_params_overloads_ambiguous_call(): with pytest.raises(TypeError): MethodTest.DefaultParamsWithOverloading() + def test_keyword_arg_method_resolution(): from Python.Test import MethodArityTest ob = MethodArityTest() assert ob.Foo(1, b=2) == "Arity 2" + def test_params_array_overload(): res = MethodTest.ParamsArrayOverloaded() assert res == "without params-array" @@ -1243,6 +1267,7 @@ def test_params_array_overload(): res = MethodTest.ParamsArrayOverloaded(1, 2, 3, i=1) assert res == "with params-array" + @pytest.mark.skip(reason="FIXME: incorrectly failing") def test_params_array_overloaded_failing(): res = MethodTest.ParamsArrayOverloaded(1, 2, i=1) @@ -1251,13 +1276,16 @@ def test_params_array_overloaded_failing(): res = MethodTest.ParamsArrayOverloaded(paramsArray=[], i=1) assert res == "with params-array" + def test_method_encoding(): MethodTest.EncodingTestÅngström() + def test_method_with_pointer_array_argument(): with pytest.raises(TypeError): MethodTest.PointerArray([0]) + def test_method_call_implicit_conversion(): class IntAnswerMixin: diff --git a/uv.lock b/uv.lock index 9f73eb110..f007f9151 100644 --- a/uv.lock +++ b/uv.lock @@ -1,74 +1,275 @@ version = 1 revision = 3 -requires-python = ">=3.10, <3.14" +requires-python = ">=3.10, <3.15" +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] + +[[package]] +name = "accessible-pygments" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903, upload-time = "2024-05-10T11:23:08.421Z" }, +] + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, +] + +[[package]] +name = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, +] + +[[package]] +name = "breathe" +version = "4.36.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/56/99bf7d0799d95ad485d95596dc01c2a5b3dda58ebf50a94f6f73b33bacdf/breathe-4.36.0.tar.gz", hash = "sha256:14860b73118ac140b7a3f55446890c777d1b67149cb024279fe3710dad7f535c", size = 154842, upload-time = "2025-02-22T18:36:03.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/bc/d67ef1e11ed6e6343c135bf605aa9d5734ff0cc77eb42a2a41f182abc9d9/breathe-4.36.0-py3-none-any.whl", hash = "sha256:af85436f1f09e842bd1fd95617281211c635f8768d245ff830c59b979888d1d5", size = 97231, upload-time = "2025-02-22T18:36:01.087Z" }, +] + +[[package]] +name = "certifi" +version = "2026.4.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, +] [[package]] name = "cffi" -version = "1.17.1" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycparser" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191, upload-time = "2024-09-04T20:43:30.027Z" }, - { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592, upload-time = "2024-09-04T20:43:32.108Z" }, - { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024, upload-time = "2024-09-04T20:43:34.186Z" }, - { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188, upload-time = "2024-09-04T20:43:36.286Z" }, - { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571, upload-time = "2024-09-04T20:43:38.586Z" }, - { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687, upload-time = "2024-09-04T20:43:40.084Z" }, - { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211, upload-time = "2024-09-04T20:43:41.526Z" }, - { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325, upload-time = "2024-09-04T20:43:43.117Z" }, - { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784, upload-time = "2024-09-04T20:43:45.256Z" }, - { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564, upload-time = "2024-09-04T20:43:46.779Z" }, - { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804, upload-time = "2024-09-04T20:43:48.186Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299, upload-time = "2024-09-04T20:43:49.812Z" }, - { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264, upload-time = "2024-09-04T20:43:51.124Z" }, - { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651, upload-time = "2024-09-04T20:43:52.872Z" }, - { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" }, - { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" }, - { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721, upload-time = "2024-09-04T20:44:01.585Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242, upload-time = "2024-09-04T20:44:03.467Z" }, - { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" }, - { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" }, - { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727, upload-time = "2024-09-04T20:44:09.481Z" }, - { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" }, - { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178, upload-time = "2024-09-04T20:44:12.232Z" }, - { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload-time = "2024-09-04T20:44:13.739Z" }, - { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" }, - { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" }, - { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" }, - { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload-time = "2024-09-04T20:44:20.248Z" }, - { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" }, - { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" }, - { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" }, - { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" }, - { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" }, - { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" }, - { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" }, - { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" }, - { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" }, - { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" }, - { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" }, - { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" }, - { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" }, - { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, + { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, + { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, + { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, + { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, + { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, + { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, + { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, + { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/08/0f303cb0b529e456bb116f2d50565a482694fbb94340bf56d44677e7ed03/charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d", size = 315182, upload-time = "2026-04-02T09:25:40.673Z" }, + { url = "https://files.pythonhosted.org/packages/24/47/b192933e94b546f1b1fe4df9cc1f84fcdbf2359f8d1081d46dd029b50207/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8", size = 209329, upload-time = "2026-04-02T09:25:42.354Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b4/01fa81c5ca6141024d89a8fc15968002b71da7f825dd14113207113fabbd/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790", size = 231230, upload-time = "2026-04-02T09:25:44.281Z" }, + { url = "https://files.pythonhosted.org/packages/20/f7/7b991776844dfa058017e600e6e55ff01984a063290ca5622c0b63162f68/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc", size = 225890, upload-time = "2026-04-02T09:25:45.475Z" }, + { url = "https://files.pythonhosted.org/packages/20/e7/bed0024a0f4ab0c8a9c64d4445f39b30c99bd1acd228291959e3de664247/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393", size = 216930, upload-time = "2026-04-02T09:25:46.58Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ab/b18f0ab31cdd7b3ddb8bb76c4a414aeb8160c9810fdf1bc62f269a539d87/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153", size = 202109, upload-time = "2026-04-02T09:25:48.031Z" }, + { url = "https://files.pythonhosted.org/packages/82/e5/7e9440768a06dfb3075936490cb82dbf0ee20a133bf0dd8551fa096914ec/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af", size = 214684, upload-time = "2026-04-02T09:25:49.245Z" }, + { url = "https://files.pythonhosted.org/packages/71/94/8c61d8da9f062fdf457c80acfa25060ec22bf1d34bbeaca4350f13bcfd07/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34", size = 212785, upload-time = "2026-04-02T09:25:50.671Z" }, + { url = "https://files.pythonhosted.org/packages/66/cd/6e9889c648e72c0ab2e5967528bb83508f354d706637bc7097190c874e13/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1", size = 203055, upload-time = "2026-04-02T09:25:51.802Z" }, + { url = "https://files.pythonhosted.org/packages/92/2e/7a951d6a08aefb7eb8e1b54cdfb580b1365afdd9dd484dc4bee9e5d8f258/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752", size = 232502, upload-time = "2026-04-02T09:25:53.388Z" }, + { url = "https://files.pythonhosted.org/packages/58/d5/abcf2d83bf8e0a1286df55cd0dc1d49af0da4282aa77e986df343e7de124/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53", size = 214295, upload-time = "2026-04-02T09:25:54.765Z" }, + { url = "https://files.pythonhosted.org/packages/47/3a/7d4cd7ed54be99973a0dc176032cba5cb1f258082c31fa6df35cff46acfc/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616", size = 227145, upload-time = "2026-04-02T09:25:55.904Z" }, + { url = "https://files.pythonhosted.org/packages/1d/98/3a45bf8247889cf28262ebd3d0872edff11565b2a1e3064ccb132db3fbb0/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a", size = 218884, upload-time = "2026-04-02T09:25:57.074Z" }, + { url = "https://files.pythonhosted.org/packages/ad/80/2e8b7f8915ed5c9ef13aa828d82738e33888c485b65ebf744d615040c7ea/charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374", size = 148343, upload-time = "2026-04-02T09:25:58.199Z" }, + { url = "https://files.pythonhosted.org/packages/35/1b/3b8c8c77184af465ee9ad88b5aea46ea6b2e1f7b9dc9502891e37af21e30/charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943", size = 159174, upload-time = "2026-04-02T09:25:59.322Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/feb40dca40dbb21e0a908801782d9288c64fc8d8e562c2098e9994c8c21b/charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008", size = 147805, upload-time = "2026-04-02T09:26:00.756Z" }, + { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705, upload-time = "2026-04-02T09:26:02.191Z" }, + { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419, upload-time = "2026-04-02T09:26:03.583Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901, upload-time = "2026-04-02T09:26:04.738Z" }, + { url = "https://files.pythonhosted.org/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742, upload-time = "2026-04-02T09:26:06.36Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061, upload-time = "2026-04-02T09:26:08.347Z" }, + { url = "https://files.pythonhosted.org/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239, upload-time = "2026-04-02T09:26:09.823Z" }, + { url = "https://files.pythonhosted.org/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173, upload-time = "2026-04-02T09:26:10.953Z" }, + { url = "https://files.pythonhosted.org/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841, upload-time = "2026-04-02T09:26:12.142Z" }, + { url = "https://files.pythonhosted.org/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304, upload-time = "2026-04-02T09:26:13.711Z" }, + { url = "https://files.pythonhosted.org/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455, upload-time = "2026-04-02T09:26:14.941Z" }, + { url = "https://files.pythonhosted.org/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036, upload-time = "2026-04-02T09:26:16.478Z" }, + { url = "https://files.pythonhosted.org/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739, upload-time = "2026-04-02T09:26:17.751Z" }, + { url = "https://files.pythonhosted.org/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277, upload-time = "2026-04-02T09:26:18.981Z" }, + { url = "https://files.pythonhosted.org/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819, upload-time = "2026-04-02T09:26:20.295Z" }, + { url = "https://files.pythonhosted.org/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281, upload-time = "2026-04-02T09:26:21.74Z" }, + { url = "https://files.pythonhosted.org/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843, upload-time = "2026-04-02T09:26:22.901Z" }, + { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, + { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, + { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, + { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, ] [[package]] name = "clr-loader" -version = "0.2.7.post0" +version = "0.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d5/b3/8ae917e458394e2cebdbf17bed0a8204f8d4ffc79a093a7b1141c7731d3c/clr_loader-0.2.7.post0.tar.gz", hash = "sha256:b7a8b3f8fbb1bcbbb6382d887e21d1742d4f10b5ea209e4ad95568fe97e1c7c6", size = 56701, upload-time = "2024-12-12T20:15:15.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/46/7eea92b6aa2d68af78e049cbecec5f757f1aad44ecdecdc16bbad7eead51/clr_loader-0.3.1.tar.gz", hash = "sha256:2e073e9aaf49d1ae2f56ecba27987ad5fb68be4bcd9dd34a5bed8f0e4e128366", size = 86805, upload-time = "2026-04-18T17:49:44.287Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/c0/06e64a54bced4e8b885c1e7ec03ee1869e52acf69e87da40f92391a214ad/clr_loader-0.2.7.post0-py3-none-any.whl", hash = "sha256:e0b9fcc107d48347a4311a28ffe3ae78c4968edb216ffb6564cb03f7ace0bb47", size = 50649, upload-time = "2024-12-12T20:15:13.714Z" }, + { url = "https://files.pythonhosted.org/packages/5e/da/ec1a6e36624000b6df0dd61183c42342ee5814c073315e802cadaad04d2f/clr_loader-0.3.1-py3-none-any.whl", hash = "sha256:cbad189de20d202a7d621956b0fc38049e13c9bf7ca2923441eff725cd121aa1", size = 55730, upload-time = "2026-04-18T17:49:42.99Z" }, ] [[package]] @@ -80,140 +281,381 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, +] + +[[package]] +name = "docutils" +version = "0.22.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, +] + [[package]] name = "exceptiongroup" -version = "1.2.2" +version = "1.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883, upload-time = "2024-07-12T22:26:00.161Z" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453, upload-time = "2024-07-12T22:25:58.476Z" }, + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, ] [[package]] name = "find-libpython" -version = "0.4.0" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/60/951b7ca316ab3ec928ed788de5fcb30b4a0292704e50b872c8edf24c11fe/find_libpython-0.5.1.tar.gz", hash = "sha256:12a0fb39ff8dcc64ad0fd554b1bd142ea4a8c4c18e5da6043a547ce7b25559fe", size = 9402, upload-time = "2026-02-11T03:18:04.844Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/1f/1d6079f4f0540aaa368aa20d89d98eda42f081c397a822c547340e32d1e3/find_libpython-0.5.1-py3-none-any.whl", hash = "sha256:723a8cfe6fed255a1f58b53c62ed556fb340ec0d456e9863ebc01a5cc047607d", size = 9201, upload-time = "2026-02-11T03:18:03.263Z" }, +] + +[[package]] +name = "furo" +version = "2025.12.19" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "accessible-pygments" }, + { name = "beautifulsoup4" }, + { name = "pygments" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "sphinx-basic-ng" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ec/20/5f5ad4da6a5a27c80f2ed2ee9aee3f9e36c66e56e21c00fde467b2f8f88f/furo-2025.12.19.tar.gz", hash = "sha256:188d1f942037d8b37cd3985b955839fea62baa1730087dc29d157677c857e2a7", size = 1661473, upload-time = "2025-12-19T17:34:40.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/b2/50e9b292b5cac13e9e81272c7171301abc753a60460d21505b606e15cf21/furo-2025.12.19-py3-none-any.whl", hash = "sha256:bb0ead5309f9500130665a26bee87693c41ce4dbdff864dbfb6b0dae4673d24f", size = 339262, upload-time = "2025-12-19T17:34:38.905Z" }, +] + +[[package]] +name = "idna" +version = "3.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/db/86/b1d3a9c49d907cac74f9d8bcead2c8e807a878c0e218d8ef1d38e6a4f59a/find_libpython-0.4.0.tar.gz", hash = "sha256:46f9cdcd397ddb563b2d7592ded3796a41c1df5222443bd9d981721c906c03e6", size = 8979, upload-time = "2024-03-13T17:01:10.727Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/89/6b4624122d5c61a86e8aebcebd377866338b705ce4f115c45b046dc09b99/find_libpython-0.4.0-py3-none-any.whl", hash = "sha256:034a4253bd57da3408aefc59aeac1650150f6c1f42e10fdd31615cf1df0842e3", size = 8670, upload-time = "2024-03-13T17:01:09.712Z" }, + { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, +] + +[[package]] +name = "imagesize" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z" }, ] [[package]] name = "iniconfig" -version = "2.1.0" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] [[package]] name = "numpy" -version = "2.2.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/78/31103410a57bc2c2b93a3597340a8119588571f6a4539067546cb9a0bfac/numpy-2.2.4.tar.gz", hash = "sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f", size = 20270701, upload-time = "2025-03-16T18:27:00.648Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/89/a79e86e5c1433926ed7d60cb267fb64aa578b6101ab645800fd43b4801de/numpy-2.2.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8146f3550d627252269ac42ae660281d673eb6f8b32f113538e0cc2a9aed42b9", size = 21250661, upload-time = "2025-03-16T18:02:13.017Z" }, - { url = "https://files.pythonhosted.org/packages/79/c2/f50921beb8afd60ed9589ad880332cfefdb805422210d327fb48f12b7a81/numpy-2.2.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e642d86b8f956098b564a45e6f6ce68a22c2c97a04f5acd3f221f57b8cb850ae", size = 14389926, upload-time = "2025-03-16T18:02:39.022Z" }, - { url = "https://files.pythonhosted.org/packages/c7/b9/2c4e96130b0b0f97b0ef4a06d6dae3b39d058b21a5e2fa2decd7fd6b1c8f/numpy-2.2.4-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:a84eda42bd12edc36eb5b53bbcc9b406820d3353f1994b6cfe453a33ff101775", size = 5428329, upload-time = "2025-03-16T18:02:50.032Z" }, - { url = "https://files.pythonhosted.org/packages/7f/a5/3d7094aa898f4fc5c84cdfb26beeae780352d43f5d8bdec966c4393d644c/numpy-2.2.4-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:4ba5054787e89c59c593a4169830ab362ac2bee8a969249dc56e5d7d20ff8df9", size = 6963559, upload-time = "2025-03-16T18:03:02.523Z" }, - { url = "https://files.pythonhosted.org/packages/4c/22/fb1be710a14434c09080dd4a0acc08939f612ec02efcb04b9e210474782d/numpy-2.2.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7716e4a9b7af82c06a2543c53ca476fa0b57e4d760481273e09da04b74ee6ee2", size = 14368066, upload-time = "2025-03-16T18:03:27.146Z" }, - { url = "https://files.pythonhosted.org/packages/c2/07/2e5cc71193e3ef3a219ffcf6ca4858e46ea2be09c026ddd480d596b32867/numpy-2.2.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:adf8c1d66f432ce577d0197dceaac2ac00c0759f573f28516246351c58a85020", size = 16417040, upload-time = "2025-03-16T18:03:55.999Z" }, - { url = "https://files.pythonhosted.org/packages/1a/97/3b1537776ad9a6d1a41813818343745e8dd928a2916d4c9edcd9a8af1dac/numpy-2.2.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:218f061d2faa73621fa23d6359442b0fc658d5b9a70801373625d958259eaca3", size = 15879862, upload-time = "2025-03-16T18:04:23.56Z" }, - { url = "https://files.pythonhosted.org/packages/b0/b7/4472f603dd45ef36ff3d8e84e84fe02d9467c78f92cc121633dce6da307b/numpy-2.2.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:df2f57871a96bbc1b69733cd4c51dc33bea66146b8c63cacbfed73eec0883017", size = 18206032, upload-time = "2025-03-16T18:04:53.694Z" }, - { url = "https://files.pythonhosted.org/packages/0d/bd/6a092963fb82e6c5aa0d0440635827bbb2910da229545473bbb58c537ed3/numpy-2.2.4-cp310-cp310-win32.whl", hash = "sha256:a0258ad1f44f138b791327961caedffbf9612bfa504ab9597157806faa95194a", size = 6608517, upload-time = "2025-03-16T18:05:06.647Z" }, - { url = "https://files.pythonhosted.org/packages/01/e3/cb04627bc2a1638948bc13e818df26495aa18e20d5be1ed95ab2b10b6847/numpy-2.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:0d54974f9cf14acf49c60f0f7f4084b6579d24d439453d5fc5805d46a165b542", size = 12943498, upload-time = "2025-03-16T18:05:28.591Z" }, - { url = "https://files.pythonhosted.org/packages/16/fb/09e778ee3a8ea0d4dc8329cca0a9c9e65fed847d08e37eba74cb7ed4b252/numpy-2.2.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e9e0a277bb2eb5d8a7407e14688b85fd8ad628ee4e0c7930415687b6564207a4", size = 21254989, upload-time = "2025-03-16T18:06:04.092Z" }, - { url = "https://files.pythonhosted.org/packages/a2/0a/1212befdbecab5d80eca3cde47d304cad986ad4eec7d85a42e0b6d2cc2ef/numpy-2.2.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9eeea959168ea555e556b8188da5fa7831e21d91ce031e95ce23747b7609f8a4", size = 14425910, upload-time = "2025-03-16T18:06:29.062Z" }, - { url = "https://files.pythonhosted.org/packages/2b/3e/e7247c1d4f15086bb106c8d43c925b0b2ea20270224f5186fa48d4fb5cbd/numpy-2.2.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bd3ad3b0a40e713fc68f99ecfd07124195333f1e689387c180813f0e94309d6f", size = 5426490, upload-time = "2025-03-16T18:06:39.901Z" }, - { url = "https://files.pythonhosted.org/packages/5d/fa/aa7cd6be51419b894c5787a8a93c3302a1ed4f82d35beb0613ec15bdd0e2/numpy-2.2.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cf28633d64294969c019c6df4ff37f5698e8326db68cc2b66576a51fad634880", size = 6967754, upload-time = "2025-03-16T18:06:52.658Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ee/96457c943265de9fadeb3d2ffdbab003f7fba13d971084a9876affcda095/numpy-2.2.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fa8fa7697ad1646b5c93de1719965844e004fcad23c91228aca1cf0800044a1", size = 14373079, upload-time = "2025-03-16T18:07:16.297Z" }, - { url = "https://files.pythonhosted.org/packages/c5/5c/ceefca458559f0ccc7a982319f37ed07b0d7b526964ae6cc61f8ad1b6119/numpy-2.2.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4162988a360a29af158aeb4a2f4f09ffed6a969c9776f8f3bdee9b06a8ab7e5", size = 16428819, upload-time = "2025-03-16T18:07:44.188Z" }, - { url = "https://files.pythonhosted.org/packages/22/31/9b2ac8eee99e001eb6add9fa27514ef5e9faf176169057a12860af52704c/numpy-2.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:892c10d6a73e0f14935c31229e03325a7b3093fafd6ce0af704be7f894d95687", size = 15881470, upload-time = "2025-03-16T18:08:11.545Z" }, - { url = "https://files.pythonhosted.org/packages/f0/dc/8569b5f25ff30484b555ad8a3f537e0225d091abec386c9420cf5f7a2976/numpy-2.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db1f1c22173ac1c58db249ae48aa7ead29f534b9a948bc56828337aa84a32ed6", size = 18218144, upload-time = "2025-03-16T18:08:42.042Z" }, - { url = "https://files.pythonhosted.org/packages/5e/05/463c023a39bdeb9bb43a99e7dee2c664cb68d5bb87d14f92482b9f6011cc/numpy-2.2.4-cp311-cp311-win32.whl", hash = "sha256:ea2bb7e2ae9e37d96835b3576a4fa4b3a97592fbea8ef7c3587078b0068b8f09", size = 6606368, upload-time = "2025-03-16T18:08:55.074Z" }, - { url = "https://files.pythonhosted.org/packages/8b/72/10c1d2d82101c468a28adc35de6c77b308f288cfd0b88e1070f15b98e00c/numpy-2.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:f7de08cbe5551911886d1ab60de58448c6df0f67d9feb7d1fb21e9875ef95e91", size = 12947526, upload-time = "2025-03-16T18:09:16.844Z" }, - { url = "https://files.pythonhosted.org/packages/a2/30/182db21d4f2a95904cec1a6f779479ea1ac07c0647f064dea454ec650c42/numpy-2.2.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a7b9084668aa0f64e64bd00d27ba5146ef1c3a8835f3bd912e7a9e01326804c4", size = 20947156, upload-time = "2025-03-16T18:09:51.975Z" }, - { url = "https://files.pythonhosted.org/packages/24/6d/9483566acfbda6c62c6bc74b6e981c777229d2af93c8eb2469b26ac1b7bc/numpy-2.2.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dbe512c511956b893d2dacd007d955a3f03d555ae05cfa3ff1c1ff6df8851854", size = 14133092, upload-time = "2025-03-16T18:10:16.329Z" }, - { url = "https://files.pythonhosted.org/packages/27/f6/dba8a258acbf9d2bed2525cdcbb9493ef9bae5199d7a9cb92ee7e9b2aea6/numpy-2.2.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bb649f8b207ab07caebba230d851b579a3c8711a851d29efe15008e31bb4de24", size = 5163515, upload-time = "2025-03-16T18:10:26.19Z" }, - { url = "https://files.pythonhosted.org/packages/62/30/82116199d1c249446723c68f2c9da40d7f062551036f50b8c4caa42ae252/numpy-2.2.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:f34dc300df798742b3d06515aa2a0aee20941c13579d7a2f2e10af01ae4901ee", size = 6696558, upload-time = "2025-03-16T18:10:38.996Z" }, - { url = "https://files.pythonhosted.org/packages/0e/b2/54122b3c6df5df3e87582b2e9430f1bdb63af4023c739ba300164c9ae503/numpy-2.2.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3f7ac96b16955634e223b579a3e5798df59007ca43e8d451a0e6a50f6bfdfba", size = 14084742, upload-time = "2025-03-16T18:11:02.76Z" }, - { url = "https://files.pythonhosted.org/packages/02/e2/e2cbb8d634151aab9528ef7b8bab52ee4ab10e076509285602c2a3a686e0/numpy-2.2.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f92084defa704deadd4e0a5ab1dc52d8ac9e8a8ef617f3fbb853e79b0ea3592", size = 16134051, upload-time = "2025-03-16T18:11:32.767Z" }, - { url = "https://files.pythonhosted.org/packages/8e/21/efd47800e4affc993e8be50c1b768de038363dd88865920439ef7b422c60/numpy-2.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4e84a6283b36632e2a5b56e121961f6542ab886bc9e12f8f9818b3c266bfbb", size = 15578972, upload-time = "2025-03-16T18:11:59.877Z" }, - { url = "https://files.pythonhosted.org/packages/04/1e/f8bb88f6157045dd5d9b27ccf433d016981032690969aa5c19e332b138c0/numpy-2.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:11c43995255eb4127115956495f43e9343736edb7fcdb0d973defd9de14cd84f", size = 17898106, upload-time = "2025-03-16T18:12:31.487Z" }, - { url = "https://files.pythonhosted.org/packages/2b/93/df59a5a3897c1f036ae8ff845e45f4081bb06943039ae28a3c1c7c780f22/numpy-2.2.4-cp312-cp312-win32.whl", hash = "sha256:65ef3468b53269eb5fdb3a5c09508c032b793da03251d5f8722b1194f1790c00", size = 6311190, upload-time = "2025-03-16T18:12:44.46Z" }, - { url = "https://files.pythonhosted.org/packages/46/69/8c4f928741c2a8efa255fdc7e9097527c6dc4e4df147e3cadc5d9357ce85/numpy-2.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:2aad3c17ed2ff455b8eaafe06bcdae0062a1db77cb99f4b9cbb5f4ecb13c5146", size = 12644305, upload-time = "2025-03-16T18:13:06.864Z" }, - { url = "https://files.pythonhosted.org/packages/2a/d0/bd5ad792e78017f5decfb2ecc947422a3669a34f775679a76317af671ffc/numpy-2.2.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cf4e5c6a278d620dee9ddeb487dc6a860f9b199eadeecc567f777daace1e9e7", size = 20933623, upload-time = "2025-03-16T18:13:43.231Z" }, - { url = "https://files.pythonhosted.org/packages/c3/bc/2b3545766337b95409868f8e62053135bdc7fa2ce630aba983a2aa60b559/numpy-2.2.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1974afec0b479e50438fc3648974268f972e2d908ddb6d7fb634598cdb8260a0", size = 14148681, upload-time = "2025-03-16T18:14:08.031Z" }, - { url = "https://files.pythonhosted.org/packages/6a/70/67b24d68a56551d43a6ec9fe8c5f91b526d4c1a46a6387b956bf2d64744e/numpy-2.2.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:79bd5f0a02aa16808fcbc79a9a376a147cc1045f7dfe44c6e7d53fa8b8a79392", size = 5148759, upload-time = "2025-03-16T18:14:18.613Z" }, - { url = "https://files.pythonhosted.org/packages/1c/8b/e2fc8a75fcb7be12d90b31477c9356c0cbb44abce7ffb36be39a0017afad/numpy-2.2.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:3387dd7232804b341165cedcb90694565a6015433ee076c6754775e85d86f1fc", size = 6683092, upload-time = "2025-03-16T18:14:31.386Z" }, - { url = "https://files.pythonhosted.org/packages/13/73/41b7b27f169ecf368b52533edb72e56a133f9e86256e809e169362553b49/numpy-2.2.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f527d8fdb0286fd2fd97a2a96c6be17ba4232da346931d967a0630050dfd298", size = 14081422, upload-time = "2025-03-16T18:14:54.83Z" }, - { url = "https://files.pythonhosted.org/packages/4b/04/e208ff3ae3ddfbafc05910f89546382f15a3f10186b1f56bd99f159689c2/numpy-2.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce43e386c16898b91e162e5baaad90c4b06f9dcbe36282490032cec98dc8ae7", size = 16132202, upload-time = "2025-03-16T18:15:22.035Z" }, - { url = "https://files.pythonhosted.org/packages/fe/bc/2218160574d862d5e55f803d88ddcad88beff94791f9c5f86d67bd8fbf1c/numpy-2.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31504f970f563d99f71a3512d0c01a645b692b12a63630d6aafa0939e52361e6", size = 15573131, upload-time = "2025-03-16T18:15:48.546Z" }, - { url = "https://files.pythonhosted.org/packages/a5/78/97c775bc4f05abc8a8426436b7cb1be806a02a2994b195945600855e3a25/numpy-2.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:81413336ef121a6ba746892fad881a83351ee3e1e4011f52e97fba79233611fd", size = 17894270, upload-time = "2025-03-16T18:16:20.274Z" }, - { url = "https://files.pythonhosted.org/packages/b9/eb/38c06217a5f6de27dcb41524ca95a44e395e6a1decdc0c99fec0832ce6ae/numpy-2.2.4-cp313-cp313-win32.whl", hash = "sha256:f486038e44caa08dbd97275a9a35a283a8f1d2f0ee60ac260a1790e76660833c", size = 6308141, upload-time = "2025-03-16T18:20:15.297Z" }, - { url = "https://files.pythonhosted.org/packages/52/17/d0dd10ab6d125c6d11ffb6dfa3423c3571befab8358d4f85cd4471964fcd/numpy-2.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:207a2b8441cc8b6a2a78c9ddc64d00d20c303d79fba08c577752f080c4007ee3", size = 12636885, upload-time = "2025-03-16T18:20:36.982Z" }, - { url = "https://files.pythonhosted.org/packages/fa/e2/793288ede17a0fdc921172916efb40f3cbc2aa97e76c5c84aba6dc7e8747/numpy-2.2.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8120575cb4882318c791f839a4fd66161a6fa46f3f0a5e613071aae35b5dd8f8", size = 20961829, upload-time = "2025-03-16T18:16:56.191Z" }, - { url = "https://files.pythonhosted.org/packages/3a/75/bb4573f6c462afd1ea5cbedcc362fe3e9bdbcc57aefd37c681be1155fbaa/numpy-2.2.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a761ba0fa886a7bb33c6c8f6f20213735cb19642c580a931c625ee377ee8bd39", size = 14161419, upload-time = "2025-03-16T18:17:22.811Z" }, - { url = "https://files.pythonhosted.org/packages/03/68/07b4cd01090ca46c7a336958b413cdbe75002286295f2addea767b7f16c9/numpy-2.2.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ac0280f1ba4a4bfff363a99a6aceed4f8e123f8a9b234c89140f5e894e452ecd", size = 5196414, upload-time = "2025-03-16T18:17:34.066Z" }, - { url = "https://files.pythonhosted.org/packages/a5/fd/d4a29478d622fedff5c4b4b4cedfc37a00691079623c0575978d2446db9e/numpy-2.2.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:879cf3a9a2b53a4672a168c21375166171bc3932b7e21f622201811c43cdd3b0", size = 6709379, upload-time = "2025-03-16T18:17:47.466Z" }, - { url = "https://files.pythonhosted.org/packages/41/78/96dddb75bb9be730b87c72f30ffdd62611aba234e4e460576a068c98eff6/numpy-2.2.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f05d4198c1bacc9124018109c5fba2f3201dbe7ab6e92ff100494f236209c960", size = 14051725, upload-time = "2025-03-16T18:18:11.904Z" }, - { url = "https://files.pythonhosted.org/packages/00/06/5306b8199bffac2a29d9119c11f457f6c7d41115a335b78d3f86fad4dbe8/numpy-2.2.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f085ce2e813a50dfd0e01fbfc0c12bbe5d2063d99f8b29da30e544fb6483b8", size = 16101638, upload-time = "2025-03-16T18:18:40.749Z" }, - { url = "https://files.pythonhosted.org/packages/fa/03/74c5b631ee1ded596945c12027649e6344614144369fd3ec1aaced782882/numpy-2.2.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:92bda934a791c01d6d9d8e038363c50918ef7c40601552a58ac84c9613a665bc", size = 15571717, upload-time = "2025-03-16T18:19:04.512Z" }, - { url = "https://files.pythonhosted.org/packages/cb/dc/4fc7c0283abe0981e3b89f9b332a134e237dd476b0c018e1e21083310c31/numpy-2.2.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ee4d528022f4c5ff67332469e10efe06a267e32f4067dc76bb7e2cddf3cd25ff", size = 17879998, upload-time = "2025-03-16T18:19:32.52Z" }, - { url = "https://files.pythonhosted.org/packages/e5/2b/878576190c5cfa29ed896b518cc516aecc7c98a919e20706c12480465f43/numpy-2.2.4-cp313-cp313t-win32.whl", hash = "sha256:05c076d531e9998e7e694c36e8b349969c56eadd2cdcd07242958489d79a7286", size = 6366896, upload-time = "2025-03-16T18:19:43.55Z" }, - { url = "https://files.pythonhosted.org/packages/3e/05/eb7eec66b95cf697f08c754ef26c3549d03ebd682819f794cb039574a0a6/numpy-2.2.4-cp313-cp313t-win_amd64.whl", hash = "sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d", size = 12739119, upload-time = "2025-03-16T18:20:03.94Z" }, - { url = "https://files.pythonhosted.org/packages/b2/5c/f09c33a511aff41a098e6ef3498465d95f6360621034a3d95f47edbc9119/numpy-2.2.4-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7051ee569db5fbac144335e0f3b9c2337e0c8d5c9fee015f259a5bd70772b7e8", size = 21081956, upload-time = "2025-03-16T18:21:12.955Z" }, - { url = "https://files.pythonhosted.org/packages/ba/30/74c48b3b6494c4b820b7fa1781d441e94d87a08daa5b35d222f06ba41a6f/numpy-2.2.4-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:ab2939cd5bec30a7430cbdb2287b63151b77cf9624de0532d629c9a1c59b1d5c", size = 6827143, upload-time = "2025-03-16T18:21:26.748Z" }, - { url = "https://files.pythonhosted.org/packages/54/f5/ab0d2f48b490535c7a80e05da4a98902b632369efc04f0e47bb31ca97d8f/numpy-2.2.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0f35b19894a9e08639fd60a1ec1978cb7f5f7f1eace62f38dd36be8aecdef4d", size = 16233350, upload-time = "2025-03-16T18:21:53.902Z" }, - { url = "https://files.pythonhosted.org/packages/3b/3a/2f6d8c1f8e45d496bca6baaec93208035faeb40d5735c25afac092ec9a12/numpy-2.2.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b4adfbbc64014976d2f91084915ca4e626fbf2057fb81af209c1a6d776d23e3d", size = 12857565, upload-time = "2025-03-16T18:22:17.631Z" }, +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/c6/4218570d8c8ecc9704b5157a3348e486e84ef4be0ed3e38218ab473c83d2/numpy-2.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f983334aea213c99992053ede6168500e5f086ce74fbc4acc3f2b00f5762e9db", size = 16976799, upload-time = "2026-03-29T13:18:15.438Z" }, + { url = "https://files.pythonhosted.org/packages/dd/92/b4d922c4a5f5dab9ed44e6153908a5c665b71acf183a83b93b690996e39b/numpy-2.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72944b19f2324114e9dc86a159787333b77874143efcf89a5167ef83cfee8af0", size = 14971552, upload-time = "2026-03-29T13:18:18.606Z" }, + { url = "https://files.pythonhosted.org/packages/8a/dc/df98c095978fa6ee7b9a9387d1d58cbb3d232d0e69ad169a4ce784bde4fd/numpy-2.4.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:86b6f55f5a352b48d7fbfd2dbc3d5b780b2d79f4d3c121f33eb6efb22e9a2015", size = 5476566, upload-time = "2026-03-29T13:18:21.532Z" }, + { url = "https://files.pythonhosted.org/packages/28/34/b3fdcec6e725409223dd27356bdf5a3c2cc2282e428218ecc9cb7acc9763/numpy-2.4.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:ba1f4fc670ed79f876f70082eff4f9583c15fb9a4b89d6188412de4d18ae2f40", size = 6806482, upload-time = "2026-03-29T13:18:23.634Z" }, + { url = "https://files.pythonhosted.org/packages/68/62/63417c13aa35d57bee1337c67446761dc25ea6543130cf868eace6e8157b/numpy-2.4.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a87ec22c87be071b6bdbd27920b129b94f2fc964358ce38f3822635a3e2e03d", size = 15973376, upload-time = "2026-03-29T13:18:26.677Z" }, + { url = "https://files.pythonhosted.org/packages/cf/c5/9fcb7e0e69cef59cf10c746b84f7d58b08bc66a6b7d459783c5a4f6101a6/numpy-2.4.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df3775294accfdd75f32c74ae39fcba920c9a378a2fc18a12b6820aa8c1fb502", size = 16925137, upload-time = "2026-03-29T13:18:30.14Z" }, + { url = "https://files.pythonhosted.org/packages/7e/43/80020edacb3f84b9efdd1591120a4296462c23fd8db0dde1666f6ef66f13/numpy-2.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d4e437e295f18ec29bc79daf55e8a47a9113df44d66f702f02a293d93a2d6dd", size = 17329414, upload-time = "2026-03-29T13:18:33.733Z" }, + { url = "https://files.pythonhosted.org/packages/fd/06/af0658593b18a5f73532d377188b964f239eb0894e664a6c12f484472f97/numpy-2.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6aa3236c78803afbcb255045fbef97a9e25a1f6c9888357d205ddc42f4d6eba5", size = 18658397, upload-time = "2026-03-29T13:18:37.511Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ce/13a09ed65f5d0ce5c7dd0669250374c6e379910f97af2c08c57b0608eee4/numpy-2.4.4-cp311-cp311-win32.whl", hash = "sha256:30caa73029a225b2d40d9fae193e008e24b2026b7ee1a867b7ee8d96ca1a448e", size = 6239499, upload-time = "2026-03-29T13:18:40.372Z" }, + { url = "https://files.pythonhosted.org/packages/bd/63/05d193dbb4b5eec1eca73822d80da98b511f8328ad4ae3ca4caf0f4db91d/numpy-2.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:6bbe4eb67390b0a0265a2c25458f6b90a409d5d069f1041e6aff1e27e3d9a79e", size = 12614257, upload-time = "2026-03-29T13:18:42.95Z" }, + { url = "https://files.pythonhosted.org/packages/87/c5/8168052f080c26fa984c413305012be54741c9d0d74abd7fbeeccae3889f/numpy-2.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:fcfe2045fd2e8f3cb0ce9d4ba6dba6333b8fa05bb8a4939c908cd43322d14c7e", size = 10486775, upload-time = "2026-03-29T13:18:45.835Z" }, + { url = "https://files.pythonhosted.org/packages/28/05/32396bec30fb2263770ee910142f49c1476d08e8ad41abf8403806b520ce/numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b", size = 16689272, upload-time = "2026-03-29T13:18:49.223Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f3/a983d28637bfcd763a9c7aafdb6d5c0ebf3d487d1e1459ffdb57e2f01117/numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e", size = 14699573, upload-time = "2026-03-29T13:18:52.629Z" }, + { url = "https://files.pythonhosted.org/packages/9b/fd/e5ecca1e78c05106d98028114f5c00d3eddb41207686b2b7de3e477b0e22/numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842", size = 5204782, upload-time = "2026-03-29T13:18:55.579Z" }, + { url = "https://files.pythonhosted.org/packages/de/2f/702a4594413c1a8632092beae8aba00f1d67947389369b3777aed783fdca/numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8", size = 6552038, upload-time = "2026-03-29T13:18:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/7f/37/eed308a8f56cba4d1fdf467a4fc67ef4ff4bf1c888f5fc980481890104b1/numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121", size = 15670666, upload-time = "2026-03-29T13:19:00.341Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0d/0e3ecece05b7a7e87ab9fb587855548da437a061326fff64a223b6dcb78a/numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e", size = 16645480, upload-time = "2026-03-29T13:19:03.63Z" }, + { url = "https://files.pythonhosted.org/packages/34/49/f2312c154b82a286758ee2f1743336d50651f8b5195db18cdb63675ff649/numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44", size = 17020036, upload-time = "2026-03-29T13:19:07.428Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e9/736d17bd77f1b0ec4f9901aaec129c00d59f5d84d5e79bba540ef12c2330/numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d", size = 18368643, upload-time = "2026-03-29T13:19:10.775Z" }, + { url = "https://files.pythonhosted.org/packages/63/f6/d417977c5f519b17c8a5c3bc9e8304b0908b0e21136fe43bf628a1343914/numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827", size = 5961117, upload-time = "2026-03-29T13:19:13.464Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5b/e1deebf88ff431b01b7406ca3583ab2bbb90972bbe1c568732e49c844f7e/numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a", size = 12320584, upload-time = "2026-03-29T13:19:16.155Z" }, + { url = "https://files.pythonhosted.org/packages/58/89/e4e856ac82a68c3ed64486a544977d0e7bdd18b8da75b78a577ca31c4395/numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec", size = 10221450, upload-time = "2026-03-29T13:19:18.994Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d0a583ce4fefcc3308806a749a536c201ed6b5ad6e1322e227ee4848979d/numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50", size = 16684933, upload-time = "2026-03-29T13:19:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/c1/62/2b7a48fbb745d344742c0277f01286dead15f3f68e4f359fbfcf7b48f70f/numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115", size = 14694532, upload-time = "2026-03-29T13:19:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af", size = 5199661, upload-time = "2026-03-29T13:19:28.31Z" }, + { url = "https://files.pythonhosted.org/packages/cd/da/464d551604320d1491bc345efed99b4b7034143a85787aab78d5691d5a0e/numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c", size = 6547539, upload-time = "2026-03-29T13:19:30.97Z" }, + { url = "https://files.pythonhosted.org/packages/7d/90/8d23e3b0dafd024bf31bdec225b3bb5c2dbfa6912f8a53b8659f21216cbf/numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103", size = 15668806, upload-time = "2026-03-29T13:19:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83", size = 16632682, upload-time = "2026-03-29T13:19:37.336Z" }, + { url = "https://files.pythonhosted.org/packages/34/fb/14570d65c3bde4e202a031210475ae9cde9b7686a2e7dc97ee67d2833b35/numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed", size = 17019810, upload-time = "2026-03-29T13:19:40.963Z" }, + { url = "https://files.pythonhosted.org/packages/8a/77/2ba9d87081fd41f6d640c83f26fb7351e536b7ce6dd9061b6af5904e8e46/numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959", size = 18357394, upload-time = "2026-03-29T13:19:44.859Z" }, + { url = "https://files.pythonhosted.org/packages/a2/23/52666c9a41708b0853fa3b1a12c90da38c507a3074883823126d4e9d5b30/numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed", size = 5959556, upload-time = "2026-03-29T13:19:47.661Z" }, + { url = "https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf", size = 12317311, upload-time = "2026-03-29T13:19:50.67Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d8/11490cddd564eb4de97b4579ef6bfe6a736cc07e94c1598590ae25415e01/numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d", size = 10222060, upload-time = "2026-03-29T13:19:54.229Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/dab4339177a905aad3e2221c915b35202f1ec30d750dd2e5e9d9a72b804b/numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5", size = 14822302, upload-time = "2026-03-29T13:19:57.585Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e4/0564a65e7d3d97562ed6f9b0fd0fb0a6f559ee444092f105938b50043876/numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7", size = 5327407, upload-time = "2026-03-29T13:20:00.601Z" }, + { url = "https://files.pythonhosted.org/packages/29/8d/35a3a6ce5ad371afa58b4700f1c820f8f279948cca32524e0a695b0ded83/numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93", size = 6647631, upload-time = "2026-03-29T13:20:02.855Z" }, + { url = "https://files.pythonhosted.org/packages/f4/da/477731acbd5a58a946c736edfdabb2ac5b34c3d08d1ba1a7b437fa0884df/numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e", size = 15727691, upload-time = "2026-03-29T13:20:06.004Z" }, + { url = "https://files.pythonhosted.org/packages/e6/db/338535d9b152beabeb511579598418ba0212ce77cf9718edd70262cc4370/numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40", size = 16681241, upload-time = "2026-03-29T13:20:09.417Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a9/ad248e8f58beb7a0219b413c9c7d8151c5d285f7f946c3e26695bdbbe2df/numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e", size = 17085767, upload-time = "2026-03-29T13:20:13.126Z" }, + { url = "https://files.pythonhosted.org/packages/b5/1a/3b88ccd3694681356f70da841630e4725a7264d6a885c8d442a697e1146b/numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392", size = 18403169, upload-time = "2026-03-29T13:20:17.096Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c9/fcfd5d0639222c6eac7f304829b04892ef51c96a75d479214d77e3ce6e33/numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008", size = 6083477, upload-time = "2026-03-29T13:20:20.195Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e3/3938a61d1c538aaec8ed6fd6323f57b0c2d2d2219512434c5c878db76553/numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8", size = 12457487, upload-time = "2026-03-29T13:20:22.946Z" }, + { url = "https://files.pythonhosted.org/packages/97/6a/7e345032cc60501721ef94e0e30b60f6b0bd601f9174ebd36389a2b86d40/numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233", size = 10292002, upload-time = "2026-03-29T13:20:25.909Z" }, + { url = "https://files.pythonhosted.org/packages/6e/06/c54062f85f673dd5c04cbe2f14c3acb8c8b95e3384869bb8cc9bff8cb9df/numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0", size = 16684353, upload-time = "2026-03-29T13:20:29.504Z" }, + { url = "https://files.pythonhosted.org/packages/4c/39/8a320264a84404c74cc7e79715de85d6130fa07a0898f67fb5cd5bd79908/numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a", size = 14704914, upload-time = "2026-03-29T13:20:33.547Z" }, + { url = "https://files.pythonhosted.org/packages/91/fb/287076b2614e1d1044235f50f03748f31fa287e3dbe6abeb35cdfa351eca/numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a", size = 5210005, upload-time = "2026-03-29T13:20:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/63/eb/fcc338595309910de6ecabfcef2419a9ce24399680bfb149421fa2df1280/numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b", size = 6544974, upload-time = "2026-03-29T13:20:39.014Z" }, + { url = "https://files.pythonhosted.org/packages/44/5d/e7e9044032a716cdfaa3fba27a8e874bf1c5f1912a1ddd4ed071bf8a14a6/numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a", size = 15684591, upload-time = "2026-03-29T13:20:42.146Z" }, + { url = "https://files.pythonhosted.org/packages/98/7c/21252050676612625449b4807d6b695b9ce8a7c9e1c197ee6216c8a65c7c/numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d", size = 16637700, upload-time = "2026-03-29T13:20:46.204Z" }, + { url = "https://files.pythonhosted.org/packages/b1/29/56d2bbef9465db24ef25393383d761a1af4f446a1df9b8cded4fe3a5a5d7/numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252", size = 17035781, upload-time = "2026-03-29T13:20:50.242Z" }, + { url = "https://files.pythonhosted.org/packages/e3/2b/a35a6d7589d21f44cea7d0a98de5ddcbb3d421b2622a5c96b1edf18707c3/numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f", size = 18362959, upload-time = "2026-03-29T13:20:54.019Z" }, + { url = "https://files.pythonhosted.org/packages/64/c9/d52ec581f2390e0f5f85cbfd80fb83d965fc15e9f0e1aec2195faa142cde/numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc", size = 6008768, upload-time = "2026-03-29T13:20:56.912Z" }, + { url = "https://files.pythonhosted.org/packages/fa/22/4cc31a62a6c7b74a8730e31a4274c5dc80e005751e277a2ce38e675e4923/numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74", size = 12449181, upload-time = "2026-03-29T13:20:59.548Z" }, + { url = "https://files.pythonhosted.org/packages/70/2e/14cda6f4d8e396c612d1bf97f22958e92148801d7e4f110cabebdc0eef4b/numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb", size = 10496035, upload-time = "2026-03-29T13:21:02.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e8/8fed8c8d848d7ecea092dc3469643f9d10bc3a134a815a3b033da1d2039b/numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e", size = 14824958, upload-time = "2026-03-29T13:21:05.671Z" }, + { url = "https://files.pythonhosted.org/packages/05/1a/d8007a5138c179c2bf33ef44503e83d70434d2642877ee8fbb230e7c0548/numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113", size = 5330020, upload-time = "2026-03-29T13:21:08.635Z" }, + { url = "https://files.pythonhosted.org/packages/99/64/ffb99ac6ae93faf117bcbd5c7ba48a7f45364a33e8e458545d3633615dda/numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d", size = 6650758, upload-time = "2026-03-29T13:21:10.949Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6e/795cc078b78a384052e73b2f6281ff7a700e9bf53bcce2ee579d4f6dd879/numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d", size = 15729948, upload-time = "2026-03-29T13:21:14.047Z" }, + { url = "https://files.pythonhosted.org/packages/5f/86/2acbda8cc2af5f3d7bfc791192863b9e3e19674da7b5e533fded124d1299/numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f", size = 16679325, upload-time = "2026-03-29T13:21:17.561Z" }, + { url = "https://files.pythonhosted.org/packages/bc/59/cafd83018f4aa55e0ac6fa92aa066c0a1877b77a615ceff1711c260ffae8/numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0", size = 17084883, upload-time = "2026-03-29T13:21:21.106Z" }, + { url = "https://files.pythonhosted.org/packages/f0/85/a42548db84e65ece46ab2caea3d3f78b416a47af387fcbb47ec28e660dc2/numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150", size = 18403474, upload-time = "2026-03-29T13:21:24.828Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ad/483d9e262f4b831000062e5d8a45e342166ec8aaa1195264982bca267e62/numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871", size = 6155500, upload-time = "2026-03-29T13:21:28.205Z" }, + { url = "https://files.pythonhosted.org/packages/c7/03/2fc4e14c7bd4ff2964b74ba90ecb8552540b6315f201df70f137faa5c589/numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e", size = 12637755, upload-time = "2026-03-29T13:21:31.107Z" }, + { url = "https://files.pythonhosted.org/packages/58/78/548fb8e07b1a341746bfbecb32f2c268470f45fa028aacdbd10d9bc73aab/numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7", size = 10566643, upload-time = "2026-03-29T13:21:34.339Z" }, + { url = "https://files.pythonhosted.org/packages/6b/33/8fae8f964a4f63ed528264ddf25d2b683d0b663e3cba26961eb838a7c1bd/numpy-2.4.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:58c8b5929fcb8287cbd6f0a3fae19c6e03a5c48402ae792962ac465224a629a4", size = 16854491, upload-time = "2026-03-29T13:21:38.03Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d0/1aabee441380b981cf8cdda3ae7a46aa827d1b5a8cce84d14598bc94d6d9/numpy-2.4.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:eea7ac5d2dce4189771cedb559c738a71512768210dc4e4753b107a2048b3d0e", size = 14895830, upload-time = "2026-03-29T13:21:41.509Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b8/aafb0d1065416894fccf4df6b49ef22b8db045187949545bced89c034b8e/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:51fc224f7ca4d92656d5a5eb315f12eb5fe2c97a66249aa7b5f562528a3be38c", size = 5400927, upload-time = "2026-03-29T13:21:44.747Z" }, + { url = "https://files.pythonhosted.org/packages/d6/77/063baa20b08b431038c7f9ff5435540c7b7265c78cf56012a483019ca72d/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:28a650663f7314afc3e6ec620f44f333c386aad9f6fc472030865dc0ebb26ee3", size = 6715557, upload-time = "2026-03-29T13:21:47.406Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a8/379542d45a14f149444c5c4c4e7714707239ce9cc1de8c2803958889da14/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19710a9ca9992d7174e9c52f643d4272dcd1558c5f7af7f6f8190f633bd651a7", size = 15804253, upload-time = "2026-03-29T13:21:50.753Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c8/f0a45426d6d21e7ea3310a15cf90c43a14d9232c31a837702dba437f3373/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b2aec6af35c113b05695ebb5749a787acd63cafc83086a05771d1e1cd1e555f", size = 16753552, upload-time = "2026-03-29T13:21:54.344Z" }, + { url = "https://files.pythonhosted.org/packages/04/74/f4c001f4714c3ad9ce037e18cf2b9c64871a84951eaa0baf683a9ca9301c/numpy-2.4.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f2cf083b324a467e1ab358c105f6cad5ea950f50524668a80c486ff1db24e119", size = 12509075, upload-time = "2026-03-29T13:21:57.644Z" }, ] [[package]] name = "packaging" -version = "24.2" +version = "26.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/de/0d2b39fb4af88a0258f3bac87dfcbb48e73fbdea4a2ed0e2213f9a4c2f9a/packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de", size = 215519, upload-time = "2026-04-14T21:12:49.362Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c2/920ef838e2f0028c8262f16101ec09ebd5969864e5a64c4c05fad0617c56/packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f", size = 95831, upload-time = "2026-04-14T21:12:47.56Z" }, ] [[package]] name = "pluggy" -version = "1.5.0" +version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] [[package]] -name = "psutil" -version = "7.0.0" +name = "pycparser" +version = "3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" }, - { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" }, - { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" }, - { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" }, - { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, - { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] [[package]] -name = "pycparser" -version = "2.22" +name = "pygments" +version = "2.20.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] [[package]] name = "pytest" -version = "8.3.5" +version = "9.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -221,11 +663,12 @@ dependencies = [ { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, + { name = "pygments" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, ] [[package]] @@ -238,58 +681,319 @@ dependencies = [ [package.dev-dependencies] dev = [ { name = "find-libpython" }, - { name = "numpy" }, - { name = "psutil" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "pytest" }, ] +doc = [ + { name = "breathe" }, + { name = "furo" }, + { name = "pygments" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "sphinx-csharp" }, +] [package.metadata] -requires-dist = [{ name = "clr-loader", specifier = ">=0.2.7,<0.3.0" }] +requires-dist = [{ name = "clr-loader", specifier = ">=0.3.1,<0.4.0" }] [package.metadata.requires-dev] dev = [ { name = "find-libpython", specifier = ">=0.3" }, { name = "numpy", marker = "python_full_version < '3.10'", specifier = "<2" }, { name = "numpy", marker = "python_full_version >= '3.10'", specifier = ">=2" }, - { name = "psutil" }, { name = "pytest", specifier = ">=6" }, ] +doc = [ + { name = "breathe" }, + { name = "furo", specifier = ">=2025.12.19" }, + { name = "pygments", specifier = ">=2.20" }, + { name = "sphinx" }, + { name = "sphinx-csharp", git = "https://github.com/rogerbarton/sphinx-csharp.git" }, +] + +[[package]] +name = "requests" +version = "2.33.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, +] + +[[package]] +name = "roman-numerals" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, +] + +[[package]] +name = "snowballstemmer" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, +] + +[[package]] +name = "sphinx" +version = "8.1.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version < '3.11'" }, + { name = "babel", marker = "python_full_version < '3.11'" }, + { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, + { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "imagesize", marker = "python_full_version < '3.11'" }, + { name = "jinja2", marker = "python_full_version < '3.11'" }, + { name = "packaging", marker = "python_full_version < '3.11'" }, + { name = "pygments", marker = "python_full_version < '3.11'" }, + { name = "requests", marker = "python_full_version < '3.11'" }, + { name = "snowballstemmer", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.11'" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611, upload-time = "2024-10-13T20:27:13.93Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125, upload-time = "2024-10-13T20:27:10.448Z" }, +] + +[[package]] +name = "sphinx" +version = "9.0.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version == '3.11.*'" }, + { name = "babel", marker = "python_full_version == '3.11.*'" }, + { name = "colorama", marker = "python_full_version == '3.11.*' and sys_platform == 'win32'" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "imagesize", marker = "python_full_version == '3.11.*'" }, + { name = "jinja2", marker = "python_full_version == '3.11.*'" }, + { name = "packaging", marker = "python_full_version == '3.11.*'" }, + { name = "pygments", marker = "python_full_version == '3.11.*'" }, + { name = "requests", marker = "python_full_version == '3.11.*'" }, + { name = "roman-numerals", marker = "python_full_version == '3.11.*'" }, + { name = "snowballstemmer", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version == '3.11.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/50/a8c6ccc36d5eacdfd7913ddccd15a9cee03ecafc5ee2bc40e1f168d85022/sphinx-9.0.4.tar.gz", hash = "sha256:594ef59d042972abbc581d8baa577404abe4e6c3b04ef61bd7fc2acbd51f3fa3", size = 8710502, upload-time = "2025-12-04T07:45:27.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/3f/4bbd76424c393caead2e1eb89777f575dee5c8653e2d4b6afd7a564f5974/sphinx-9.0.4-py3-none-any.whl", hash = "sha256:5bebc595a5e943ea248b99c13814c1c5e10b3ece718976824ffa7959ff95fffb", size = 3917713, upload-time = "2025-12-04T07:45:24.944Z" }, +] + +[[package]] +name = "sphinx" +version = "9.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version >= '3.12'" }, + { name = "babel", marker = "python_full_version >= '3.12'" }, + { name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "imagesize", marker = "python_full_version >= '3.12'" }, + { name = "jinja2", marker = "python_full_version >= '3.12'" }, + { name = "packaging", marker = "python_full_version >= '3.12'" }, + { name = "pygments", marker = "python_full_version >= '3.12'" }, + { name = "requests", marker = "python_full_version >= '3.12'" }, + { name = "roman-numerals", marker = "python_full_version >= '3.12'" }, + { name = "snowballstemmer", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z" }, +] + +[[package]] +name = "sphinx-basic-ng" +version = "1.0.0b2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/0b/a866924ded68efec7a1759587a4e478aec7559d8165fac8b2ad1c0e774d6/sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9", size = 20736, upload-time = "2023-07-08T18:40:54.166Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/dd/018ce05c532a22007ac58d4f45232514cd9d6dd0ee1dc374e309db830983/sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b", size = 22496, upload-time = "2023-07-08T18:40:52.659Z" }, +] + +[[package]] +name = "sphinx-csharp" +version = "0.1.13" +source = { git = "https://github.com/rogerbarton/sphinx-csharp.git#1e4cf5d2cca28424ec836ed1989fd0f24b3e7172" } +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] [[package]] name = "tomli" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, - { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, - { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, - { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, - { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, - { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, - { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, - { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, - { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, - { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, - { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, - { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, - { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, - { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, - { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, - { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, - { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, - { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, - { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, - { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, - { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, - { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, - { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, - { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, - { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, - { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, - { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, - { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, ] diff --git a/version.txt b/version.txt index 0f9d6b15d..df4a76732 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.1.0-dev +3.2.0-dev