https://github.com/charmoniumq/charmonium.freeze

https://github.com/charmoniumq/charmonium.freeze

Science Score: 26.0%

This score indicates how likely this project is to be science-related based on various indicators:

  • CITATION.cff file
  • codemeta.json file
    Found codemeta.json file
  • .zenodo.json file
    Found .zenodo.json file
  • DOI references
  • Academic publication links
  • Academic email domains
  • Institutional organization owner
  • JOSS paper metadata
  • Scientific vocabulary similarity
    Low similarity (11.3%) to scientific vocabulary
Last synced: 10 months ago · JSON representation

Repository

Basic Info
  • Host: GitHub
  • Owner: charmoniumQ
  • License: mit
  • Language: Python
  • Default Branch: main
  • Size: 905 KB
Statistics
  • Stars: 2
  • Watchers: 2
  • Forks: 0
  • Open Issues: 0
  • Releases: 0
Created over 4 years ago · Last pushed 12 months ago
Metadata Files
Readme Contributing License Code of conduct

README.rst

==========================
charmonium.freeze
==========================

.. image:: https://img.shields.io/pypi/v/charmonium.freeze
   :alt: PyPI Package
   :target: https://pypi.org/project/charmonium.freeze
.. image:: https://img.shields.io/pypi/dm/charmonium.freeze
   :alt: PyPI Downloads
   :target: https://pypi.org/project/charmonium.freeze
.. image:: https://img.shields.io/pypi/l/charmonium.freeze
   :alt: License
   :target: https://github.com/charmoniumQ/charmonium.freeze/blob/main/LICENSE
.. image:: https://img.shields.io/pypi/pyversions/charmonium.freeze
   :alt: Python Versions
   :target: https://pypi.org/project/charmonium.freeze
.. image:: https://img.shields.io/librariesio/sourcerank/pypi/charmonium.freeze
   :alt: libraries.io sourcerank
   :target: https://libraries.io/pypi/charmonium.freeze
.. image:: https://img.shields.io/github/stars/charmoniumQ/charmonium.freeze?style=social
   :alt: GitHub stars
   :target: https://github.com/charmoniumQ/charmonium.freeze
.. image:: https://github.com/charmoniumQ/charmonium.freeze/actions/workflows/main.yaml/badge.svg
   :alt: CI status
   :target: https://github.com/charmoniumQ/charmonium.freeze/actions/workflows/main.yaml
.. image:: https://codecov.io/gh/charmoniumQ/charmonium.freeze/branch/main/graph/badge.svg?token=56A97FFTGZ
   :alt: Code Coverage
   :target: https://codecov.io/gh/charmoniumQ/charmonium.freeze
.. image:: https://img.shields.io/github/last-commit/charmoniumQ/charmonium.cache
   :alt: GitHub last commit
   :target: https://github.com/charmoniumQ/charmonium.freeze/commits
.. image:: http://www.mypy-lang.org/static/mypy_badge.svg
   :target: https://mypy.readthedocs.io/en/stable/
   :alt: Checked with Mypy
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
   :target: https://github.com/psf/black
   :alt: Code style: black

Injectively, deterministically maps arbitrary objects to hashable, immutable values


----------
Quickstart
----------

If you don't have ``pip`` installed, see the `pip install guide`_.

.. _`pip install guide`: https://pip.pypa.io/en/latest/installing/

.. code-block:: console

    $ pip install charmonium.freeze

For a related project, |charmonium.cache|_, I needed a function that
deterministically, injectively maps objects to hashable objects.

- "Injectively" means ``freeze(a) == freeze(b)`` implies ``a == b``
  (with the precondition that ``a`` and ``b`` are of the same type).

- "Deterministically" means it should return the same value **across
  subsequent process invocations** (with the same interpreter major
  and minor version), unlike Python's |hash|_ function, which is not
  deterministic between processes.

- "Hashable" means one can call ``hash(...)`` on it. All hashable
  values are immutable.

.. |hash| replace:: ``hash``
.. _`hash`: https://docs.python.org/3.8/reference/datamodel.html#object.__hash__
.. |charmonium.cache| replace:: ``charmonium.cache``
.. _`charmonium.cache`: https://github.com/charmoniumQ/charmonium.cache

Have you ever felt like you wanted to "freeze" a list of arbitrary
data into a hashable value? Now you can.

>>> obj = [1, 2, 3, {4, 5, 6}, object()]
>>> hash(obj)
Traceback (most recent call last):
  File "", line 1, in 
TypeError: unhashable type: 'list'

>>> from charmonium.freeze import freeze
>>> freeze(obj)
9561766455304166758

-------------
Configuration
-------------

By changing the configuration, we can see the exact data that gets hashed.

We can change the configuration in a few ways:

- Object-oriented (preferred)

  >>> from charmonium.freeze import Config
  >>> freeze(obj, Config(use_hash=False))
  (1, 2, 3, frozenset({4, 5, 6}), ((('builtins', 'object'),), b'copyreg.__newobj__'))

- Global variable, but in this case, we must also clear the cache when we mutate
  the config.

  >>> from charmonium.freeze import global_config
  >>> global_config.use_hash = False
  >>> global_config.memo.clear()
  >>> freeze(obj)
  (1, 2, 3, frozenset({4, 5, 6}), ((('builtins', 'object'),), b'copyreg.__newobj__'))

``use_hash=True`` will be faster and produce less data, but I will demonstrate
it with ``use_hash=False`` so you can see what data gets included in the state.

See the source code ``charmonium/freeze/config.py`` for other configuration
options.

------------------
Freezing Functions
------------------

``freeze`` on functions returns their bytecode, constants, and closure-vars. The
remarkable thing is that this is true across subsequent invocations of the same
process. If the user edits the script and changes the function, then it's
``freeze`` will change too. This tells you if it is safe to use the cached value
of the function.

  ::

    (freeze(f) == freeze(g)) implies (for all x, f(x) == g(x))

>>> from pprint import pprint
>>> i = 456
>>> func = lambda x: x + i + 123
>>> pprint(freeze(func))
(('',
  None,
  123,
  b'\x97\x00|\x00t\x00\x00\x00\x00\x00\x00\x00\x00\x00z\x00\x00\x00d\x01'
  b'z\x00\x00\x00S\x00'),
 (('i', 456),))

As promised, the frozen value includes the bytecode (``b'|x00t...``), the
constants (123), and the closure variables (456). When we change ``i``, we get a
different frozen value, indicating that the ``func`` might not be
computationally equivalent to what it was before.

>>> i = 789
>>> pprint(freeze(func))
(('',
  None,
  123,
  b'\x97\x00|\x00t\x00\x00\x00\x00\x00\x00\x00\x00\x00z\x00\x00\x00d\x01'
  b'z\x00\x00\x00S\x00'),
 (('i', 789),))

``freeze`` works for objects that use function as data.

>>> import functools
>>> pprint(freeze(functools.partial(print, 123)))
(('print',),
 ('print', (123,), (), None),
 (frozenset({'partial',
             (...,
              ('args', (b'member_descriptor', b'args')),
              ('func', (b'member_descriptor', b'func')),
              ('keywords', (b'member_descriptor', b'keywords')))}),
  ('builtins', 'object')))

``freeze`` works for methods.

>>> class Greeter:
...     def __init__(self, greeting):
...         self.greeting = greeting
...     def greet(self, name):
...         print(self.greeting + " " + name)
... 
>>> pprint(freeze(Greeter.greet))
(('greet',
  None,
  ' ',
  b'\x97\x00t\x01\x00\x00\x00\x00\x00\x00\x00\x00|\x00j\x02\x00\x00\x00\x00'
  b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x01z\x00\x00\x00'
  b'|\x01z\x00\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x01\x00y\x00'),)

----------------
Freezing Objects
----------------

``freeze`` works on objects by freezing their state and freezing their
methods. The state is found by the `pickle protocol`_, which the Python language
implements by default for all classes. To get an idea of what this returns, call
``obj.__reduce_ex__(4)``. Because we reuse an existing protocol, ``freeze`` work
correctly on most user-defined types.

.. _`pickle protocol`: https://docs.python.org/3/library/pickle.html#pickling-class-instances

>>> s = Greeter("hello")
>>> pprint(s.__reduce_ex__(4))
(,
 (,),
 {'greeting': 'hello'},
 None,
 None)
>>> pprint(freeze(s))
(((frozenset({'Greeter',
              (('__init__',
                (('__init__', None, b'|\x01|\x00_\x00d\x00S\x00'),)),
               ('greet',
                (('greet',
                  None,
                  ' ',
                  b't\x00|\x00j\x01d\x01\x17\x00|\x01\x17\x00\x83\x01'
                  b'\x01\x00d\x00S\x00'),)))}),
   ('builtins', 'object')),),
 (('greeting', 'hello'),),
 b'copyreg.__newobj__')

However, there can still be special cases: ``pickle`` may incorporate
non-deterministic values. In this case, there are three remedies:

- If you can tweak the definition of the class, add a method called
  ``__getfrozenstate__`` which returns a deterministic snapshot of the
  state. This takes precedence over the Pickle protocol, if it is defined.

  >>> class Greeter:
  ...     def __init__(self, greeting):
  ...         self.greeting = greeting
  ...     def greet(self, name):
  ...         print(self.greeting + " " + name)
  ...     def __getfrozenstate__(self):
  ...         return self.greeting
  ... 
  >>> pprint(freeze(Greeter("hello")))
  ((frozenset({'Greeter',
               (('__getfrozenstate__',
                 (('__getfrozenstate__', None, b'|\x00j\x00S\x00'),)),
                ('__init__', (('__init__', None, b'|\x01|\x00_\x00d\x00S\x00'),)),
                ('greet',
                 (('greet',
                   None,
                   ' ',
                   b't\x00|\x00j\x01d\x01\x17\x00|\x01\x17\x00\x83\x01'
                   b'\x01\x00d\x00S\x00'),)))}),
    ('builtins', 'object')),
   'hello')

- Otherwise, you can ignore certain attributes by changing the
  configuration. See the source code of ``charmonium/freeze/config.py`` for more
  details.

  >>> class Greeter:
  ...     def __init__(self, greeting):
  ...         self.greeting = greeting
  ...     def greet(self, name):
  ...         print(self.greeting + " " + name)
  ... 
  >>> config = Config(use_hash=False)
  >>> config.ignore_attributes.add(("__main__", "Greeter", "greeting"))
  >>> pprint(freeze(Greeter("hello"), config))
  (((frozenset({'Greeter',
                (('__init__',
                  (('__init__', None, b'|\x01|\x00_\x00d\x00S\x00'),)),
                 ('greet',
                  (('greet',
                    None,
                    ' ',
                    b't\x00|\x00j\x01d\x01\x17\x00|\x01\x17\x00\x83\x01'
                    b'\x01\x00d\x00S\x00'),)))}),
     ('builtins', 'object')),),
   (),
   b'copyreg.__newobj__')

  Note that ``'hello'`` is not present in the frozen object any more.

- If you cannot tweak the definition of the class or monkeypatch a
  ``__getfrozenstate__`` method, you can still register `single dispatch
  handler`_ for that type:

  .. _`single dispatch handler`: https://docs.python.org/3/library/functools.html#functools.singledispatch

  >>> from typing import Hashable, Optional, Dict, Tuple
  >>> from charmonium.freeze import _freeze_dispatch, _freeze
  >>> @_freeze_dispatch.register(Greeter)
  ... def _(
  ...         obj: Greeter,
  ...         config: Config,
  ...         tabu: Dict[int, Tuple[int, int]],
  ...         level: int,
  ...         index: int,
  ...     ) -> Tuple[Hashable, bool, Optional[int]]:
  ...     # Type annotations are optional.
  ...     # I have included them here for clarity.
  ... 
  ...     # `tabu` is for object cycle detection. It is handled for you.
  ...     # `level` is for logging and recursion limits. It is incremented for you.
  ...     # `index` is the "birth order" of the children.
  ...     frozen_greeting = _freeze(obj.greeting, config, tabu, level, 0)
  ... 
  ...     return (
  ...         frozen_greeting[0],
  ...         # Remember that _freeze returns a triple;
  ...         # we are only interested in the first element here.
  ... 
  ...         False,
  ...         # Whether the obj is immutable
  ...         # If the obj is immutable, it's frozen value need not be recomputed every time.
  ...         # This is handled for you.
  ... 
  ...         None,
  ...         # The depth of references contained here or None
  ...         # Currently, this doesn't do anything.
  ...     )
  ... 
  >>> freeze(Greeter("Hello"))
  'Hello'

----------------
Dictionary order
----------------

As of Python 3.7, dictionaries "remember" their insertion order. As such,

>>> freeze({"a": 1, "b": 2})
(('a', 1), ('b', 2))
>>> freeze({"b": 2, "a": 1})
(('b', 2), ('a', 1))

This behavior is controllable by ``Config.ignore_dict_order``, which emits a ``frozenset`` of pairs.

>>> config = Config(ignore_dict_order=True)
>>> freeze({"b": 2, "a": 1}, config) == freeze({"a": 1, "b": 2}, config)
True

--------------
Summarize diff
--------------

This enables a pretty neat utility to compare two arbitrary Python objects.

>>> from charmonium.freeze import summarize_diffs
>>> obj0 = [0, 1, 2, {3, 4}, {"a": 5, "b": 6, "c": 7}, 8]
>>> obj1 = [0, 8, 2, {3, 5}, {"a": 5, "b": 7, "d": 8}]
>>> print(summarize_diffs(obj0, obj1))
let obj0_sub = obj0
let obj1_sub = obj1
obj0_sub.__len__() == 6
obj1_sub.__len__() == 5
obj0_sub[1] == 1
obj1_sub[1] == 8
obj0_sub[3].has() == 4
obj1_sub[3].has() == no such element
obj0_sub[3].has() == no such element
obj1_sub[3].has() == 5
obj0_sub[4].keys().has() == c
obj1_sub[4].keys().has() == no such element
obj0_sub[4].keys().has() == no such element
obj1_sub[4].keys().has() == d
obj0_sub[4]['b'] == 6
obj1_sub[4]['b'] == 7

And if you don't like my printing style, you can get a programatic
access to this information.

>>> from charmonium.freeze import iterate_diffs
>>> for o1, o2 in iterate_diffs(obj0, obj1):
...    print(o1, o2, sep="\n")
ObjectLocation(labels=('obj0', '.__len__()'), objects=(..., 6))
ObjectLocation(labels=('obj1', '.__len__()'), objects=(..., 5))
ObjectLocation(labels=('obj0', '[1]'), objects=(..., 1))
ObjectLocation(labels=('obj1', '[1]'), objects=(..., 8))
ObjectLocation(labels=('obj0', '[3]', '.has()'), objects=(...), 4))
ObjectLocation(labels=('obj1', '[3]', '.has()'), objects=(..., 'no such element'))
ObjectLocation(labels=('obj0', '[3]', '.has()'), objects=(...), 'no such element'))
ObjectLocation(labels=('obj1', '[3]', '.has()'), objects=(..., 5))
ObjectLocation(labels=('obj0', '[4]', '.keys()', '.has()'), objects=(..., 'c'))
ObjectLocation(labels=('obj1', '[4]', '.keys()', '.has()'), objects=(..., 'no such element'))
ObjectLocation(labels=('obj0', '[4]', '.keys()', '.has()'), objects=(..., 'no such element'))
ObjectLocation(labels=('obj1', '[4]', '.keys()', '.has()'), objects=(..., 'd'))
ObjectLocation(labels=('obj0', '[4]', "['b']"), objects=(..., 6))
ObjectLocation(labels=('obj1', '[4]', "['b']"), objects=(..., 7))


---------
Debugging
---------

Use the following lines to see how ``freeze`` decomposes an object into
primitive values.

.. code:: python

    import logging, os
    logger = logging.getLogger("charmonium.freeze")
    logger.setLevel(logging.DEBUG)
    fh = logging.FileHandler("freeze.log")
    fh.setLevel(logging.DEBUG)
    fh.setFormatter(logging.Formatter("%(message)s"))
    logger.addHandler(fh)
    logger.debug("Program %d", os.getpid())

    i = 0
    def square_plus_i(x):
        # Value of global variable will be included in the function's frozen state.
        return x**2 + i

    from charmonium.freeze import freeze
    freeze(square_plus_i)


This produces a log such as in ``freeze.log``:

::

    freeze begin 
     function 
      tuple (('code', ), 'closure globals', {'i': 0})
       tuple ('code', )
        'code'
        code 
         tuple (None, 2)
          None
          2
         b'|\x00d\x01\x13\x00t\x00\x17\x00S\x00'
       'closure globals'
       dict {'i': 0}
        'i'
        0
    freeze end

I do this to find the differences between subsequent runs:

.. code:: shell

    $ python code.py
    $ mv freeze.log freeze.0.log

    $ python code.py
    $ mv freeze.log freeze.1.log

    $ sed -i -e 's/at 0x[0-9a-f]*/ptr/g' -e 's/memo hit for [0-9]*/memo hit/g' -e 's/tabu hit for [0-9]*/tabu hit/g' freeze.*.log
    # This removes pointer values that appear in the `repr(...)`.

    $ meld freeze.0.log freeze.1.log
    # Alternatively, use `icdiff` or `diff -u1`.

If ``freeze(obj)`` is taking a long time, try adding ``freeze(obj,
Config(recursion_limit=20))``. This causes an exception if ``freeze`` recurses
more than a certain number of times. If you hit this exception, consider adding
ignored class, functions, attributes, or objects in ``Config``.

----------
Developing
----------

See `CONTRIBUTING.md`_ for instructions on setting up a development environment.

.. _`CONTRIBUTING.md`: https://github.com/charmoniumQ/charmonium.freeze/tree/main/CONTRIBUTING.md


----
TODO
----

- ☐ Correctness

  - ☑ Test hashing sets with different orders. Assert tests fail.
  - ☑ Test hashing dicts with different orders. Assert tests fail.
  - ☑ Don't include properties in hash.
  - ☑ Test that freeze of an object includes freeze of its instance methods.
  - ☑ Test functions with minor changes.
  - ☑ Test set/dict with diff hash.
  - ☑ Test obj with slots.
  - ☑ Test hash for objects and classes more carefully.
  - ☑ Improve test coverage.
  - ☑ Investigate when modules are assumed constant.
  - ☐ For code and modules, determine if the module is a ~distribution package~. If so, use ``importlib.metadata.version``. Otherwise, module goes by attributes and code goes by bytecode.
  - ☐ Test that we are actually interpreting accesses to ``x.y`` as ``x.y`` and not ``x`` (overly conservative).
  - ☐ Support closures which include ``import x`` and ``from x import y``

- ☑ API

  - ☑ Use user-customizable multidispatch.
  - ☑ Bring hash into separate package.
  - ☑ Make it easier to register a freeze method for a type.
  - ☑ Encapsulate global config into object.
  - ☑ Make freeze object-oriented with a module-level instance, like ``random.random`` and ``random.Random``.
    - This makes it easier for different callers to have their own configuration options.
  - ☑ Add an option which returns a single 128-bit int instead of a structured object after a certain depth. This is what ``charmonium.determ_hash`` does. Use this configuration in ``charmonium.cache``.
  - ☑ Option to assume impure (explicitly mark as pure) or assume pure (explicitly mark as impure).
  - ☐ Make it easier to turn on logging and performance logging.
  - ☑ Document configuration options.
  - ☑ Document ``summarize_diff`` and ``iterate_diffs``.
  - ☑ Config object should cascade with ``with config.set(a=b)``
  - ☐ Use the class name when deciding whether to ignore a method (currently we just use the methods __module__ and __name__).
  - ☐ Support ~__getfrozenstate_for_type__~
  - ☐ Have a helper that can annotate methods, classes, and functions with ~__getfrozenstate__~.
  - ☐ Make it possible to ignore classes and functions by their package, rather than just module.

- ☑ Make ``freeze`` handle more types:

  - ☑ Module: freeze by name.
  - ☑ Objects: include the source-code of methods.
  - ☑ C extensions. freeze by name, like module
  - ☑ Methods
  - ☑ fastpath for numpy arrays
  - ☑ ``tqdm``
  - ☑ ``numpy.int64(1234)``
  - ☑ Pandas dataframe
  - ☑ Catch Pickle TypeError
  - ☑ Catch Pickle ImportError

- ☐ Performance

  - ☑ Memoize the hash of immutable data:
    - If function contains no locals or globals except other immutables, it is immutable.
    - If a collection is immutable and contains only immutables, it is immutable.
  - ☑ Make performance benchmarks.
  - ☐ Consider making a fast-path for dataclasses.
  - ☐ Consider a config option that states "all classes/functions from X.Y are stateless."
      - For example, most of the standard library is stateless (e.g., ``re``)

Owner

  • Name: Sam Grayson
  • Login: charmoniumQ
  • Kind: user
  • Location: /home/bedroom/bed
  • Company: University of Illinois at Urbana-Champaign

PhD student, on a crusade to improve scientific software

GitHub Events

Total
  • Push event: 4
  • Create event: 1
Last Year
  • Push event: 4
  • Create event: 1

Issues and Pull Requests

Last synced: 11 months ago

All Time
  • Total issues: 0
  • Total pull requests: 0
  • Average time to close issues: N/A
  • Average time to close pull requests: N/A
  • Total issue authors: 0
  • Total pull request authors: 0
  • Average comments per issue: 0
  • Average comments per pull request: 0
  • Merged pull requests: 0
  • Bot issues: 0
  • Bot pull requests: 0
Past Year
  • Issues: 0
  • Pull requests: 0
  • Average time to close issues: N/A
  • Average time to close pull requests: N/A
  • Issue authors: 0
  • Pull request authors: 0
  • Average comments per issue: 0
  • Average comments per pull request: 0
  • Merged pull requests: 0
  • Bot issues: 0
  • Bot pull requests: 0
Top Authors
Issue Authors
Pull Request Authors
Top Labels
Issue Labels
Pull Request Labels

Packages

  • Total packages: 1
  • Total downloads:
    • pypi 135 last-month
  • Total dependent packages: 0
  • Total dependent repositories: 0
  • Total versions: 25
  • Total maintainers: 1
pypi.org: charmonium.freeze

Injectively, deterministically maps arbitrary objects to hashable values

  • Versions: 25
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Downloads: 135 Last month
Rankings
Dependent packages count: 9.0%
Downloads: 24.9%
Average: 28.1%
Dependent repos count: 50.5%
Maintainers (1)
Last synced: 11 months ago

Dependencies

poetry.lock pypi
  • 105 dependencies
pyproject.toml pypi
  • autoflake ^1.7.7 develop
  • autoimport ^0.7.5 develop
  • black ^22.3 develop
  • bump2version ^1.0.1 develop
  • charmonium.async-subprocess ^0.1.7 develop
  • charmonium.determ-hash ^0.2.2 develop
  • coverage ^6.1.2 develop
  • icecream ^2.1.2 develop
  • ipdb ^0.13.9 develop
  • ipython ^7.29.0 develop
  • isort ^5.10.0 develop
  • matplotlib ^3.5.1 develop
  • mypy ^0.910 develop
  • numpy ^1.22.3 develop
  • pandas ^1.4.1 develop
  • proselint ^0.13.0 develop
  • pylint ^2.11.1 develop
  • pytest ^6.2.5 develop
  • pytest-cov ^3.0.0 develop
  • pytest-xdist ^2.4.0 develop
  • radon ^5.1.0 develop
  • rstcheck ^3.3.1 develop
  • termcolor ^1.1.0 develop
  • tox ^3.24.4 develop
  • twine ^3.5.0 develop
  • typer ^0.4.0 develop
  • types-setuptools ^57.4.10 develop
  • types-termcolor ^1.1.3 develop
  • types-toml ^0.10.4 develop
  • typing-extensions ^4.1.1 develop
  • python ^3.8
.github/workflows/main.yaml actions
  • actions/cache v2 composite
  • actions/checkout v2 composite
  • cachix/install-nix-action v15 composite
uv.lock pypi
  • annotated-types 0.7.0
  • asttokens 3.0.0
  • black 25.1.0
  • cachetools 6.1.0
  • certifi 2025.7.14
  • chardet 5.2.0
  • charmonium-freeze 0.8.5
  • charset-normalizer 3.4.2
  • click 8.1.8
  • click 8.2.1
  • colorama 0.4.6
  • contourpy 1.3.0
  • contourpy 1.3.2
  • coverage 7.9.2
  • cycler 0.12.1
  • decorator 5.2.1
  • distlib 0.4.0
  • docutils 0.21.2
  • exceptiongroup 1.3.0
  • execnet 2.1.1
  • executing 2.2.0
  • filelock 3.18.0
  • fonttools 4.59.0
  • idna 3.10
  • importlib-resources 6.5.2
  • iniconfig 2.1.0
  • ipython 8.18.1
  • ipython 8.37.0
  • ipython 9.4.0
  • ipython-pygments-lexers 1.1.1
  • isort 6.0.1
  • jedi 0.19.2
  • kiwisolver 1.4.7
  • kiwisolver 1.4.8
  • mando 0.7.1
  • markdown-it-py 3.0.0
  • matplotlib 3.9.4
  • matplotlib 3.10.3
  • matplotlib-inline 0.1.7
  • mdurl 0.1.2
  • mypy 1.17.0
  • mypy-extensions 1.1.0
  • numpy 2.0.2
  • numpy 2.2.6
  • numpy 2.3.1
  • packaging 25.0
  • pandas 2.3.1
  • parso 0.8.4
  • pathspec 0.12.1
  • pexpect 4.9.0
  • pillow 11.3.0
  • platformdirs 4.3.8
  • pluggy 1.6.0
  • prompt-toolkit 3.0.51
  • ptyprocess 0.7.0
  • pure-eval 0.2.3
  • py-cpuinfo 9.0.0
  • pydantic 2.11.7
  • pydantic-core 2.33.2
  • pygments 2.19.2
  • pyparsing 3.2.3
  • pyproject-api 1.9.1
  • pytest 8.4.1
  • pytest-benchmark 5.1.0
  • pytest-cov 6.2.1
  • pytest-xdist 3.8.0
  • python-dateutil 2.9.0.post0
  • pytz 2025.2
  • radon 6.0.1
  • requests 2.32.4
  • rich 14.0.0
  • rstcheck 6.2.5
  • rstcheck-core 1.2.2
  • ruff 0.12.4
  • shellingham 1.5.4
  • six 1.17.0
  • stack-data 0.6.3
  • tomli 2.2.1
  • tox 4.27.0
  • tqdm 4.67.1
  • traitlets 5.14.3
  • typer 0.16.0
  • typing-extensions 4.14.1
  • typing-inspection 0.4.1
  • tzdata 2025.2
  • urllib3 2.5.0
  • virtualenv 20.31.2
  • wcwidth 0.2.13
  • zipp 3.23.0