pax_global_header00006660000000000000000000000064150675673160014532gustar00rootroot0000000000000052 comment=d9454af38d2785bea3243d5b76018e5549a5eb58 atom-0.12.1/000077500000000000000000000000001506756731600125535ustar00rootroot00000000000000atom-0.12.1/.github/000077500000000000000000000000001506756731600141135ustar00rootroot00000000000000atom-0.12.1/.github/FUNDING.yml000066400000000000000000000012151506756731600157270ustar00rootroot00000000000000# These are supported funding model platforms github: [MatthieuDartiailh] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] atom-0.12.1/.github/dependabot.yml000066400000000000000000000002421506756731600167410ustar00rootroot00000000000000version: 2 updates: # Maintain dependencies for GitHub Actions - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly"atom-0.12.1/.github/workflows/000077500000000000000000000000001506756731600161505ustar00rootroot00000000000000atom-0.12.1/.github/workflows/ci.yml000066400000000000000000000077071506756731600173010ustar00rootroot00000000000000name: Continuous Integration on: schedule: - cron: '0 0 * * 3' push: branches: - main pull_request: branches: - main paths: - .github/workflows/ci.yml - "atom/**" - "tests/**" - "examples/**" - setup.py - pyproject.toml jobs: lint: name: Lint runs-on: ubuntu-latest strategy: matrix: python-version: ['3.11'] steps: - uses: actions/checkout@v5 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} cache: 'pip' cache-dependency-path: 'lint_requirements.txt' - name: Install dependencies run: | python -m pip install --upgrade pip - name: Install project run: | pip install -e . - name: Install dependencies run: | pip install -U -r lint_requirements.txt - name: Formatting if: always() run: | ruff format atom examples tests --check - name: Linting if: always() run: | ruff check atom examples tests - name: Typing if: always() run: | mypy atom examples types: name: Type checking runs-on: ubuntu-latest needs: - lint if: needs.lint.result == 'success' strategy: matrix: python-version: ['3.10'] steps: - uses: actions/checkout@v5 - name: Get history and tags for SCM versioning to work run: | git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip - name: Install project run: | pip install . - name: Run Mypy run: | pip install mypy pytest mypy atom - name: Test with pytest run: | pip install pytest-mypy-plugins regex python -X dev -m pytest tests/type_checking -v tests: name: Unit tests runs-on: ${{ matrix.os }} needs: - lint if: needs.lint.result == 'success' strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] python-version: ['3.10', '3.11', '3.12', '3.13', '3.14-dev'] steps: - uses: actions/checkout@v5 - name: Get history and tags for SCM versioning to work run: | git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip setuptools pip install setuptools-scm[toml] pip install git+https://github.com/nucleic/cppy@main - name: Install project env: CFLAGS: --coverage # Use legcay command to install the library to be able to measure # coverage of C++ code. run: | python setup.py develop - name: Test with pytest run: | pip install -r test_requirements.txt python -X dev -m pytest tests --ignore=tests/type_checking --cov --cov-report xml -v -W error # - name: Generate C++ coverage reports # if: (github.event_name != 'schedule' && matrix.os != 'windows-latest') # run: | # bash -c "find . -type f -name '*.gcno' -exec gcov -pb --all-blocks {} +" || true - name: Upload coverage to Codecov uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} flags: unittests name: codecov-umbrella fail_ci_if_error: true verbose: true atom-0.12.1/.github/workflows/docs.yml000066400000000000000000000022431506756731600176240ustar00rootroot00000000000000name: Documentation building on: schedule: - cron: '0 0 * * 2' push: branches: - main pull_request: branches: - main paths: - .github/workflows/docs.yml - "atom/**" - "docs/**" - "examples/**" - setup.py - pyproject.toml jobs: docs: name: Docs building runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - name: Get history and tags for SCM versioning to work run: | git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* - name: Set up Python uses: actions/setup-python@v6 with: python-version: '3.x' - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r docs/requirements.txt - name: Install project run: | pip install . - name: Install graphviz uses: ts-graphviz/setup-graphviz@v1 - name: Build documentation # Remove -W till the multiple reference can be fixed in Sphinx 4 run: | mkdir docs_output; sphinx-build docs/source docs_output -b html; atom-0.12.1/.github/workflows/release.yml000066400000000000000000000071641506756731600203230ustar00rootroot00000000000000name: Build and upload wheels on: workflow_dispatch: schedule: - cron: '0 0 * * 3' push: tags: - '*' jobs: build_sdist: name: Build sdist runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v5 - name: Get history and tags for SCM versioning to work run: | git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* - name: Setup Python uses: actions/setup-python@v6 with: python-version: '3.x' - name: Build sdist run: | pip install --upgrade pip pip install wheel build python -m build . -s - name: Test sdist run: | pip install pytest pip install dist/*.tar.gz cd .. python -m pytest atom/tests - name: Store artifacts uses: actions/upload-artifact@v4 with: name: cibw-sdist path: dist/* build_wheels: name: Build wheels on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: os: [windows-latest, ubuntu-latest, macos-latest] steps: - name: Checkout uses: actions/checkout@v5 - name: Get history and tags for SCM versioning to work run: | git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* - name: Setup Python uses: actions/setup-python@v6 with: python-version: '3.x' - name: Install cibuildwheel run: | python -m pip install --upgrade pip python -m pip install wheel cibuildwheel - name: Build wheels env: CIBW_BUILD: "cp310-* cp311-* cp312-* cp313-* cp314-*" CIBW_ARCHS_MACOS: x86_64 universal2 arm64 CIBW_ARCHS_WINDOWS: auto64 CIBW_TEST_REQUIRES: pytest CIBW_TEST_COMMAND: python -m pytest {package}/tests -v run: | python -m cibuildwheel . --output-dir dist - name: Store artifacts uses: actions/upload-artifact@v4 with: name: cibw-wheels-${{ runner.os }} path: dist/*.whl publish: if: github.event_name == 'push' needs: [build_wheels, build_sdist] runs-on: ubuntu-latest environment: name: pypi url: https://pypi.org/p/atom permissions: id-token: write steps: - name: Download all the dists uses: actions/download-artifact@v5.0.0 with: pattern: cibw-* path: dist merge-multiple: true - uses: pypa/gh-action-pypi-publish@release/v1 github-release: name: >- Sign the Python 🐍 distribution 📦 with Sigstore and create a GitHub Release runs-on: ubuntu-latest needs: - publish permissions: contents: write id-token: write steps: - name: Download all the dists uses: actions/download-artifact@v5.0.0 with: pattern: cibw-* path: dist merge-multiple: true - name: Sign the dists with Sigstore uses: sigstore/gh-action-sigstore-python@v3.0.1 with: inputs: >- ./dist/*.tar.gz ./dist/*.whl - name: Create GitHub Release env: GITHUB_TOKEN: ${{ github.token }} run: >- gh release create '${{ github.ref_name }}' --repo '${{ github.repository }}' --generate-notes - name: Upload artifact signatures to GitHub Release env: GITHUB_TOKEN: ${{ github.token }} run: >- gh release upload '${{ github.ref_name }}' dist/** --repo '${{ github.repository }}' atom-0.12.1/.gitignore000066400000000000000000000040171506756731600145450ustar00rootroot00000000000000# pycharm project files .idea # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging atom/version.py .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports reports/* !reports/pycallgraph !reports/pycallgraph/* htmlcov/ cov_html/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # VS Code .vscode # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # Data files *.csv # HTML files *.html # VSCodeCounter cache files .VSCodeCounter # Profiling folder used by pytest-profile prof/ atom-0.12.1/.pre-commit-config.yaml000066400000000000000000000004631506756731600170370ustar00rootroot00000000000000repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. rev: v0.1.6 hooks: # Run the linter. - id: ruff # Run the formatter. - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.7.0 hooks: - id: mypy additional_dependencies: []atom-0.12.1/.readthedocs.yaml000066400000000000000000000011761506756731600160070ustar00rootroot00000000000000# .readthedocs.yml# .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Set the version of Python and other tools you might need build: os: ubuntu-20.04 tools: python: "3.10" apt_packages: - graphviz # Build documentation in the docs/source directory with Sphinx sphinx: configuration: docs/source/conf.py # Enable epub output formats: - epub # Optionally declare the Python requirements required to build your docs python: install: - requirements: docs/requirements.txt - method: pip path: . atom-0.12.1/LICENSE000066400000000000000000000066011506756731600135630ustar00rootroot00000000000000========================= The Atom licensing terms ========================= Atom is licensed under the terms of the Modified BSD License (also known as New or Revised BSD), as follows: Copyright (c) 2013-2025, Nucleic Development Team Copyright (c) 2013, Enthought, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the Nucleic Development Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. About Atom ---------- Chris Colbert began the Atom project under the umbrella of Enthought, Inc. in February 2013. The project was forked into the Nucleic project in March 2013. Chris is still the project lead. The Nucleic Development Team is the set of all contributors to the Nucleic project and its subprojects. The core team that coordinates development on GitHub can be found here: http://github.com/nucleic. The current team consists of: Core Developers: * Chris Colbert * Matthieu Dartiailh Active Contributors: * Steven Silvester * Dave Willmer Our Copyright Policy -------------------- Nucleic uses a shared copyright model. Each contributor maintains copyright over their contributions to Nucleic. But, it is important to note that these contributions are typically only changes to the repositories. Thus, the Nucleic source code, in its entirety is not the copyright of any single person or institution. Instead, it is the collective copyright of the entire Nucleic Development Team. If individual contributors want to maintain a record of what changes/contributions they have specific copyright on, they should indicate their copyright in the commit message of the change, when they commit the change to one of the Nucleic repositories. With this in mind, the following banner should be used in any source code file to indicate the copyright and license terms: #------------------------------------------------------------------------------ # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. #------------------------------------------------------------------------------ atom-0.12.1/MANIFEST.in000066400000000000000000000010251506756731600143070ustar00rootroot00000000000000include MANIFEST.in include *.txt include *.rst include *.md include Makefile include LICENSE recursive-include docs/source *.rst recursive-include docs/source *.png recursive-include docs/source *.py include docs/make.bat include docs/Makefile recursive-include examples *.py recursive-include licenses *.txt include atom/py.typed recursive-include atom *.py recursive-include atom *.pyi recursive-include atom/src *.cpp recursive-include atom/src *.h recursive-include tests *.py prune .git prune docs/build prune dist prune build atom-0.12.1/README.rst000066400000000000000000000055621506756731600142520ustar00rootroot00000000000000Welcome to Atom =============== .. image:: https://github.com/nucleic/atom/workflows/Continuous%20Integration/badge.svg :target: https://github.com/nucleic/atom/actions .. image:: https://github.com/nucleic/atom/workflows/Documentation%20building/badge.svg :target: https://github.com/nucleic/atom/actions .. image:: https://codecov.io/gh/nucleic/atom/branch/main/graph/badge.svg :target: https://codecov.io/gh/nucleic/atom .. image:: https://readthedocs.org/projects/atom/badge/?version=latest :target: https://atom.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json :target: https://github.com/astral-sh/ruff :alt: Ruff Atom is a framework for creating memory efficient Python objects with enhanced features such as dynamic initialization, validation, and change notification for object attributes. It provides the default model binding behavior for the `Enaml `_ UI framework. Examples: .. code-block:: python from atom.api import Atom, Str, Range, Bool, observe class Person(Atom): """ A simple class representing a person object. """ last_name = Str() first_name = Str() age = Range(low=0) debug = Bool(False) @observe('age') def debug_print(self, change): """ Prints out a debug message whenever the person's age changes. """ if self.debug: templ = "{first} {last} is {age} years old." s = templ.format( first=self.first_name, last=self.last_name, age=self.age, ) print(s) def _default_first_name(self): return 'John' john = Person(last_name='Doe', age=42) john.debug = True john.age = 43 # prints message john.age = 'forty three' # raises TypeError Starting with atom 0.8.0 atom object can also be defined using type annotations. .. code-block:: python from atom.api import Atom, observe class InventoryItem(Atom): """Class for keeping track of an item in inventory.""" name: str unit_price: float quantity_on_hand: int = 0 def total_cost(self) -> float: return self.unit_price * self.quantity_on_hand @observe("unit_price") def check_for_price_reduction(self, change): savings = change.get("oldvalue", 0) - change.get("value") if savings > 0: print(f"Save ${savings} now on {self.name}s!") >>> w = InventoryItem(name="widget", unit_price=1.99, quantity_on_hand=10) >>> w.unit_price = 1.00 Save $0.99 now on widgets! For version information, see `the Revision History `_. atom-0.12.1/atom/000077500000000000000000000000001506756731600135135ustar00rootroot00000000000000atom-0.12.1/atom/__init__.py000066400000000000000000000005611506756731600156260ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- atom-0.12.1/atom/api.py000066400000000000000000000046231506756731600146430ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Module exporting the public interface to atom.""" from .atom import Atom from .catom import ( CAtom, ChangeType, DefaultValue, GetAttr, GetState, Member, PostGetAttr, PostSetAttr, PostValidate, SetAttr, Validate, atomclist, atomdict, atomlist, atomref, atomset, defaultatomdict, ) from .coerced import Coerced from .containerlist import ContainerList from .delegator import Delegator from .dict import DefaultDict, Dict from .enum import Enum from .event import Event from .instance import ForwardInstance, Instance from .list import List from .meta import ( AtomMeta, MissingMemberWarning, add_member, clone_if_needed, observe, set_default, ) from .property import Property, cached_property from .scalars import ( Bool, Bytes, Callable, Constant, Float, FloatRange, Int, Range, ReadOnly, Str, Value, ) from .set import Set from .signal import Signal from .subclass import ForwardSubclass, Subclass from .tuple import FixedTuple, Tuple from .typed import ForwardTyped, Typed from .typing_utils import ChangeDict __all__ = [ "Atom", "AtomMeta", "Bool", "Bytes", "CAtom", "Callable", "ChangeDict", "ChangeType", "Coerced", "Constant", "ContainerList", "DefaultDict", "DefaultValue", "Delegator", "Dict", "Enum", "Event", "FixedTuple", "Float", "FloatRange", "ForwardInstance", "ForwardSubclass", "ForwardTyped", "GetAttr", "GetState", "Instance", "Int", "List", "Member", "MissingMemberWarning", "PostGetAttr", "PostSetAttr", "PostValidate", "Property", "Range", "ReadOnly", "Set", "SetAttr", "Signal", "Str", "Subclass", "Tuple", "Typed", "Validate", "Value", "add_member", "atomclist", "atomdict", "atomlist", "atomref", "atomset", "cached_property", "clone_if_needed", "defaultatomdict", "observe", "set_default", ] atom-0.12.1/atom/atom.py000066400000000000000000000055441506756731600150350ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from contextlib import contextmanager from typing import ClassVar, Iterator, Mapping from .catom import CAtom, Member from .meta import AtomMeta, add_member, observe, set_default # noqa # The above are imported to avoid breaking code relying on internals def __newobj__(cls, *args): """A compatibility pickler function. This function is not part of the public Atom api. """ return cls.__new__(cls, *args) class Atom(CAtom, metaclass=AtomMeta): """The base class for defining atom objects. `Atom` objects are special Python objects which never allocate an instance dictionary unless one is explicitly requested. The storage for an atom is instead computed from the `Member` objects declared on the class. Memory is reserved for these members with no over allocation. This restriction make atom objects a bit less flexible than normal Python objects, but they are between 3x-10x more memory efficient than normal objects depending on the number of attributes. """ __atom_members__: ClassVar[Mapping[str, Member]] @classmethod def members(cls) -> Mapping[str, Member]: """Get the members dictionary for the type. Returns ------- result : Mapping[str, Member] The dictionary of members defined on the class. User code should not modify the contents of the dict. """ return cls.__atom_members__ @contextmanager def suppress_notifications(self) -> Iterator[None]: """Disable member notifications within in a context. Returns ------- result : contextmanager A context manager which disables atom notifications for the duration of the context. When the context exits, the state is restored to its previous value. """ old = self.set_notifications_enabled(False) yield self.set_notifications_enabled(old) def __reduce_ex__(self, proto): """An implementation of the reduce protocol. This method creates a reduction tuple for Atom instances. This method should not be overridden by subclasses unless the author fully understands the rammifications. """ args = (type(self), *self.__getnewargs__()) return (__newobj__, args, self.__getstate__()) def __getnewargs__(self): """Get the argument tuple to pass to __new__ on unpickling. See the Python.org docs for more information. """ return () atom-0.12.1/atom/catom.pyi000066400000000000000000000411061506756731600153430ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from enum import IntEnum, IntFlag from typing import ( Any, Callable, Dict, Generic, List, Literal, Optional, Sequence, Set, Tuple, Type, TypeVar, overload, ) from typing_extensions import Self from .atom import Atom from .property import Property from .typing_utils import ChangeDict class ChangeType(IntFlag): CREATE = ... UPDATE = ... DELETE = ... EVENT = ... PROPERTY = ... CONTAINER = ... ANY = ... def reset_property(prop: Property[Any, Any], owner: Atom) -> None: ... class CAtom: def __init__(self, **kwargs: Any) -> None: ... def freeze(self) -> None: ... def get_member(self, member: str) -> Member[Any, Any]: ... def has_observer( self, member: str, func: Callable[[Dict[str, Any]], None] ) -> bool: ... def has_observers(self, member: str) -> bool: ... def notifications_enabled(self) -> bool: ... def notify(self, member_name: str, *args: Any, **kwargs: Any) -> None: ... def observe( self, member: str, func: Callable[[ChangeDict], None], change_types: ChangeType = ChangeType.ANY, ) -> None: ... def set_notifications_enabled(self, enabled: bool) -> bool: ... def unobserve(self, member: str, func: Callable[[ChangeDict], None]) -> None: ... def __sizeof__(self) -> int: ... T = TypeVar("T") S = TypeVar("S") class Member(Generic[T, S]): index: int = ... metadata: Optional[Dict[str, Any]] = ... name: str = ... # Mode accessors default_value_mode: Tuple[DefaultValue, Any] = ... delattr_mode: Tuple[DelAttr, Any] = ... getattr_mode: Tuple[GetAttr, Any] = ... post_getattr_mode: Tuple[PostGetAttr, Any] = ... post_setattr_mode: Tuple[PostSetAttr, Any] = ... post_validate_mode: Tuple[PostValidate, Any] = ... setattr_mode: Tuple[SetAttr, Any] = ... validate_mode: Tuple[Validate, Any] = ... getstate_mode: Tuple[GetState, Any] = ... def __init__(self) -> None: ... @overload def __get__(self, instance: None, owner: Type[Atom]) -> Self: ... @overload def __get__(self, instance: Atom, owner: Type[Atom]) -> T: ... def __set__(self, instance: Atom, value: S) -> None: ... def __delete__(self, instance: Atom) -> None: ... def tag(self, **kwargs: Any) -> Self: ... def clone(self) -> Self: ... def add_static_observer( self, observer: str | Callable[[ChangeDict], None], change_types: ChangeType = ChangeType.ANY, ) -> Any: ... def remove_static_observer( self, observer: str | Callable[[ChangeDict], None] ) -> Any: ... def static_observers(self) -> Tuple[str | Callable[[ChangeDict], None], ...]: ... def has_observer( self, observer: str | Callable[[ChangeDict], None], change_types: ChangeType = ChangeType.ANY, ) -> Any: ... def has_observers(self, change_types: ChangeType = ChangeType.ANY) -> bool: ... def copy_static_observers(self, other: Member[Any, Any]) -> None: ... def notify(self, owner: CAtom, *args: Any, **kwargs: Any) -> None: ... def get_slot(self, owner: CAtom) -> Any: ... def set_slot(self, owner: CAtom, value: Any) -> None: ... def del_slot(self, owner: CAtom) -> None: ... # Manual operation of the member def do_default_value(self, owner: CAtom) -> Any: ... def do_delattr(self, owner: CAtom) -> None: ... def do_full_validate(self, owner: CAtom, old: T, new: Any) -> T: ... def do_getattr(self, owner: CAtom) -> Any: ... def do_post_getattr(self, owner: CAtom, value: Any) -> T: ... def do_post_setattr(self, owner: CAtom, old: T, new: T) -> None: ... def do_post_validate(self, owner: CAtom, old: T, new: T) -> T: ... def do_setattr(self, owner: CAtom, value: Any) -> Any: ... def do_validate(self, owner: CAtom, old: T, new: Any) -> T: ... def do_should_getstate(self, owner: CAtom) -> bool: ... # Setter for the member def set_index(self, index: int) -> None: ... def set_name(self, name: str) -> None: ... # Default value mode @overload def set_default_value_mode( self, mode: Literal[DefaultValue.List], context: list[Any] ) -> None: ... @overload def set_default_value_mode( self, mode: Literal[DefaultValue.Set], context: set[Any] ) -> None: ... @overload def set_default_value_mode( self, mode: Literal[DefaultValue.Dict], context: dict[Any, Any] ) -> None: ... @overload def set_default_value_mode( self, mode: Literal[DefaultValue.Delegate], context: Member[T, S] ) -> None: ... @overload def set_default_value_mode( self, mode: Literal[DefaultValue.CallObject], context: Callable[[], Any] ) -> None: ... @overload def set_default_value_mode( self, mode: Literal[DefaultValue.CallObject_Object], context: Callable[[CAtom], Any], ) -> None: ... @overload def set_default_value_mode( self, mode: Literal[DefaultValue.CallObject_ObjectName], context: Callable[[CAtom, str], Any], ) -> None: ... @overload def set_default_value_mode( self, mode: Literal[DefaultValue.ObjectMethod] | Literal[DefaultValue.ObjectMethod_Name] | Literal[DefaultValue.MemberMethod_Object], context: str, ) -> None: ... @overload def set_default_value_mode( self, mode: Literal[DefaultValue.Static], context: Any ) -> None: ... @overload def set_default_value_mode( self, mode: Literal[DefaultValue.NonOptional], context: None ) -> None: ... @overload def set_default_value_mode( self, mode: Literal[DefaultValue.NoOp], context: None ) -> None: ... # Delattr mode @overload def set_delattr_mode( self, mode: Literal[DelAttr.Delegate], context: Member[T, S] ) -> None: ... @overload def set_delattr_mode( self, mode: Literal[DelAttr.Property], context: Optional[Callable[[CAtom], None]], ) -> None: ... @overload def set_delattr_mode( self, mode: Literal[DelAttr.Constant] | Literal[DelAttr.Event] | Literal[DelAttr.NoOp] | Literal[DelAttr.ReadOnly] | Literal[DelAttr.Signal] | Literal[DelAttr.Slot], context: None, ) -> None: ... # Getattr mode @overload def set_getattr_mode( self, mode: Literal[GetAttr.Delegate], context: Member[T, S] ) -> None: ... @overload def set_getattr_mode( self, mode: Literal[GetAttr.Property] | Literal[GetAttr.CachedProperty], context: Optional[Callable[[CAtom], Any]], ) -> None: ... @overload def set_getattr_mode( self, mode: Literal[GetAttr.CallObject_Object], context: Callable[[CAtom], Any] ) -> None: ... @overload def set_getattr_mode( self, mode: Literal[GetAttr.CallObject_ObjectName], context: Callable[[CAtom, str], Any], ) -> None: ... @overload def set_getattr_mode( self, mode: Literal[GetAttr.ObjectMethod] | Literal[GetAttr.ObjectMethod_Name] | Literal[GetAttr.MemberMethod_Object], context: str, ) -> None: ... @overload def set_getattr_mode( self, mode: Literal[GetAttr.Event] | Literal[GetAttr.NoOp] | Literal[GetAttr.Signal] | Literal[GetAttr.Slot], context: str, ) -> None: ... # Post getattr mode @overload def set_post_getattr_mode( self, mode: Literal[PostGetAttr.NoOp], context: None ) -> None: ... @overload def set_post_getattr_mode( self, mode: Literal[PostGetAttr.Delegate], context: Member[T, S] ) -> None: ... @overload def set_post_getattr_mode( self, mode: Literal[PostGetAttr.ObjectMethod_Value] | Literal[PostGetAttr.ObjectMethod_NameValue] | Literal[PostGetAttr.MemberMethod_ObjectValue], context: str, ) -> None: ... # Post setattr mode @overload def set_post_setattr_mode( self, mode: Literal[PostSetAttr.NoOp], context: None ) -> None: ... @overload def set_post_setattr_mode( self, mode: Literal[PostSetAttr.Delegate], context: Member[T, S] ) -> None: ... @overload def set_post_setattr_mode( self, mode: Literal[PostSetAttr.ObjectMethod_OldNew] | Literal[PostSetAttr.ObjectMethod_NameOldNew] | Literal[PostSetAttr.MemberMethod_ObjectOldNew], context: str, ) -> None: ... # Post validate mode @overload def set_post_validate_mode( self, mode: Literal[PostValidate.NoOp], context: None ) -> None: ... @overload def set_post_validate_mode( self, mode: Literal[PostValidate.Delegate], context: Member[T, S] ) -> None: ... @overload def set_post_validate_mode( self, mode: Literal[PostValidate.ObjectMethod_OldNew] | Literal[PostValidate.ObjectMethod_NameOldNew] | Literal[PostValidate.MemberMethod_ObjectOldNew], context: str, ) -> None: ... # Setattr mode @overload def set_setattr_mode( self, mode: Literal[SetAttr.Constant] | Literal[SetAttr.Event] | Literal[SetAttr.NoOp] | Literal[SetAttr.ReadOnly] | Literal[SetAttr.Signal] | Literal[SetAttr.Slot], context: None, ) -> None: ... @overload def set_setattr_mode( self, mode: Literal[SetAttr.Delegate], context: Member[T, S] ) -> None: ... @overload def set_setattr_mode( self, mode: Literal[SetAttr.Property], context: Optional[Callable[[CAtom], Any]], ) -> None: ... @overload def set_setattr_mode( self, mode: Literal[SetAttr.CallObject_ObjectValue], context: Callable[[CAtom, Any], Any], ) -> None: ... @overload def set_setattr_mode( self, mode: Literal[SetAttr.CallObject_ObjectNameValue], context: Callable[[CAtom, str, Any], Any], ) -> None: ... @overload def set_setattr_mode( self, mode: Literal[SetAttr.ObjectMethod_Value] | Literal[SetAttr.ObjectMethod_NameValue] | Literal[SetAttr.MemberMethod_ObjectValue], context: str, ) -> None: ... # Validate mode @overload def set_validate_mode( self, mode: Literal[Validate.Bool] | Literal[Validate.Bytes] | Literal[Validate.BytesPromote] | Literal[Validate.Callable] | Literal[Validate.Float] | Literal[Validate.FloatPromote] | Literal[Validate.Int] | Literal[Validate.IntPromote] | Literal[Validate.NoOp] | Literal[Validate.Str] | Literal[Validate.StrPromote], context: None, ) -> None: ... @overload def set_validate_mode( self, mode: Literal[Validate.Tuple] | Literal[Validate.List] | Literal[Validate.ContainerList] | Literal[Validate.Set], context: Optional[Member[Any, Any]], ) -> None: ... @overload def set_validate_mode( self, mode: Literal[Validate.Dict], context: Tuple[Optional[Member[Any, Any]], Optional[Member[Any, Any]]], ) -> None: ... @overload def set_validate_mode( self, mode: Literal[Validate.Instance] | Literal[Validate.OptionalInstance] | Literal[Validate.Subclass], context: type | Tuple[type, ...], ) -> None: ... @overload def set_validate_mode( self, mode: Literal[Validate.Typed] | Literal[Validate.OptionalTyped], context: type, ) -> None: ... @overload def set_validate_mode( self, mode: Literal[Validate.Enum], context: Sequence[Any] ) -> None: ... @overload def set_validate_mode( self, mode: Literal[Validate.FloatRange], context: Tuple[Optional[float], Optional[float]], ) -> None: ... @overload def set_validate_mode( self, mode: Literal[Validate.Range], context: Tuple[Optional[int], Optional[int]], ) -> None: ... @overload def set_validate_mode( self, mode: Literal[Validate.Coerced], context: Tuple[Type[T], Callable[[Any], T]], ) -> None: ... @overload def set_validate_mode( self, mode: Literal[Validate.Delegate], context: Member[T, S] ) -> None: ... @overload def set_validate_mode( self, mode: Literal[Validate.ObjectMethod_OldNew] | Literal[Validate.ObjectMethod_NameOldNew] | Literal[Validate.MemberMethod_ObjectOldNew], context: str, ) -> None: ... # Getstate mode @overload def set_getstate_mode( self, mode: Literal[GetState.Exclude] | Literal[GetState.Include] | Literal[GetState.IncludeNonDefault] | Literal[GetState.Property], context: None, ) -> None: ... @overload def set_getstate_mode( self, mode: Literal[GetState.MemberMethod_Object] | Literal[GetState.ObjectMethod_Name], context: str, ) -> None: ... KT = TypeVar("KT") VT = TypeVar("VT") class atomlist(List[T]): ... class atomclist(atomlist[T]): ... class atomset(Set[T]): ... class atomdict(Dict[KT, VT]): ... class defaultatomdict(Dict[KT, VT]): ... A = TypeVar("A", bound=CAtom) class atomref(Generic[A]): def __new__(cls, atom: A) -> atomref[A]: ... def __bool__(self) -> bool: ... def __call__(self) -> Optional[A]: ... def __sizeof__(self) -> int: ... class SignalConnector: def __call__(self, *args: Any, **kwargs: Any) -> None: ... def emit(self, *args: Any, **kwargs: Any) -> None: ... def connect(self, slot: Callable[..., Any]) -> None: ... def disconnect(self, slot: Callable[..., Any]) -> None: ... class EventBinder: def bind(self, observer: Callable[[Dict[str, Any]], None]) -> None: ... def unbind(self, observer: Callable[[Dict[str, Any]], None]) -> None: ... class DefaultValue(IntEnum): CallObject = ... CallObject_Object = ... CallObject_ObjectName = ... Delegate = ... Dict = ... DefaultDict = ... List = ... MemberMethod_Object = ... NonOptional = ... NoOp = ... ObjectMethod = ... ObjectMethod_Name = ... Set = ... Static = ... class DelAttr(IntEnum): Constant = ... Delegate = ... Event = ... NoOp = ... Property = ... ReadOnly = ... Signal = ... Slot = ... class GetAttr(IntEnum): CachedProperty = ... CallObject_Object = ... CallObject_ObjectName = ... Delegate = ... Event = ... MemberMethod_Object = ... NoOp = ... ObjectMethod = ... ObjectMethod_Name = ... Property = ... Signal = ... Slot = ... class PostGetAttr(IntEnum): Delegate = ... MemberMethod_ObjectValue = ... NoOp = ... ObjectMethod_NameValue = ... ObjectMethod_Value = ... class PostSetAttr(IntEnum): Delegate = ... MemberMethod_ObjectOldNew = ... NoOp = ... ObjectMethod_NameOldNew = ... ObjectMethod_OldNew = ... class PostValidate(IntEnum): Delegate = ... MemberMethod_ObjectOldNew = ... NoOp = ... ObjectMethod_NameOldNew = ... ObjectMethod_OldNew = ... class SetAttr(IntEnum): CallObject_ObjectNameValue = ... CallObject_ObjectValue = ... Constant = ... Delegate = ... Event = ... MemberMethod_ObjectValue = ... NoOp = ... ObjectMethod_NameValue = ... ObjectMethod_Value = ... Property = ... ReadOnly = ... Signal = ... Slot = ... class Validate(IntEnum): Bool = ... Bytes = ... BytesPromote = ... Callable = ... Coerced = ... ContainerList = ... Delegate = ... Dict = ... DefaultDict = ... Enum = ... FixedTuple = ... Float = ... FloatPromote = ... FloatRange = ... Instance = ... Int = ... IntPromote = ... List = ... MemberMethod_ObjectOldNew = ... OptionalInstance = ... OptionalTyped = ... NoOp = ... ObjectMethod_NameOldNew = ... ObjectMethod_OldNew = ... Range = ... Set = ... Str = ... StrPromote = ... Subclass = ... Tuple = ... Typed = ... class GetState(IntEnum): Exclude = ... Include = ... IncludeNonDefault = ... Property = ... MemberMethod_Object = ... ObjectMethod_Name = ... atom-0.12.1/atom/coerced.py000066400000000000000000000052431506756731600154750ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from .catom import DefaultValue, Member, Validate from .typing_utils import extract_types, is_optional class Coerced(Member): """A member which will coerce a value to a given instance type. Unlike Typed or Instance, a Coerced value is not intended to be set to None. """ __slots__ = () def __init__(self, kind, args=None, kwargs=None, *, factory=None, coercer=None): """Initialize a Coerced. Parameters ---------- kind : type or tuple of types The allowable types for the value. args : tuple, optional If 'factory' is None, then 'kind' is a callable type and these arguments will be passed to the constructor to create the default value. kwargs : dict, optional If 'factory' is None, then 'kind' is a callable type and these keywords will be passed to the constructor to create the default value. factory : callable, optional An optional callable which takes no arguments and returns the default value for the member. If this is not provided then 'args' and 'kwargs' should be provided, as 'kind' will be used to generate the default value. coercer : callable, optional An optional callable which takes the value and returns the coerced value. If this is not given, then 'kind' must be a callable type which will be called with the value to coerce the value to the appropriate type. """ origin = kind kind = extract_types(kind) opt, temp = is_optional(kind) if factory is not None: self.set_default_value_mode(DefaultValue.CallObject, factory) else: args = args or () kwargs = kwargs or {} if opt: def factory(): return None else: def factory(): return kind[0](*args, **kwargs) self.set_default_value_mode(DefaultValue.CallObject, factory) if not coercer and (isinstance(origin, tuple) or len(temp) > 1): raise ValueError(f"No coercer was provided but {origin} is not callable.") self.set_validate_mode(Validate.Coerced, (kind, coercer or temp[0])) atom-0.12.1/atom/coerced.pyi000066400000000000000000000120751506756731600156470ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from typing import Any, Callable, Dict, Optional, Tuple, Type, TypeVar, overload from .catom import Member T = TypeVar("T") T1 = TypeVar("T1") T2 = TypeVar("T2") S = TypeVar("S") class Coerced(Member[T, S]): # No default # - type @overload def __new__( cls, kind: Type[T], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: None = None, coercer: None = None, ) -> Coerced[T, T]: ... @overload def __new__( cls, kind: Type[T], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: None = None, coercer: Callable[[S], T], ) -> Coerced[T, T | S]: ... # - 1-Tuple[Any, ...] @overload def __new__( cls, kind: Tuple[Type[T]], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: None = None, coercer: None = None, ) -> Coerced[T, T]: ... @overload def __new__( cls, kind: Tuple[Type[T]], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: None = None, coercer: Callable[[S], T], ) -> Coerced[T, T | S]: ... # - 2-Tuple[Any, ...] @overload def __new__( cls, kind: Tuple[Type[T], Type[T1]], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: None = None, coercer: None = None, ) -> Coerced[T | T1, T | T1]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1]], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: None = None, coercer: Callable[[S], T | T1], ) -> Coerced[T | T1, T | T1 | S]: ... # - 3-Tuple[Any, ...] @overload def __new__( cls, kind: Tuple[Type[T], Type[T1], Type[T2]], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: None = None, coercer: None = None, ) -> Coerced[T | T1 | T2, T | T1 | T2]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1], Type[T2]], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: None = None, coercer: Callable[[S], T | T1 | T2], ) -> Coerced[T | T1 | T2, T | T1 | T2 | S]: ... # Default with factory # - type @overload def __new__( cls, kind: Type[T], args: None = None, kwargs: None = None, *, factory: Callable[[], T], coercer: None = None, ) -> Coerced[T, T]: ... @overload def __new__( cls, kind: Type[T], args: None = None, kwargs: None = None, *, factory: Callable[[], T | S], coercer: Callable[[S], T], ) -> Coerced[T, T | S]: ... # - 1-Tuple[Any, ...] @overload def __new__( cls, kind: Tuple[Type[T]], args: None = None, kwargs: None = None, *, factory: Callable[[], T], coercer: None = None, ) -> Coerced[T, T]: ... @overload def __new__( cls, kind: Tuple[Type[T]], args: None = None, kwargs: None = None, *, factory: Callable[[], T | S], coercer: Callable[[S], T], ) -> Coerced[T, T | S]: ... # - 2-Tuple[Any, ...] @overload def __new__( cls, kind: Tuple[Type[T], Type[T1]], args: None = None, kwargs: None = None, *, factory: Callable[[], T | T1], coercer: None = None, ) -> Coerced[T | T1, T | T1]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1]], args: None = None, kwargs: None = None, *, factory: Callable[[], T | T1 | S], coercer: Callable[[S], T | T1], ) -> Coerced[T | T1, T | T1 | S]: ... # - 3-Tuple[Any, ...] @overload def __new__( cls, kind: Tuple[Type[T], Type[T1], Type[T2]], args: None = None, kwargs: None = None, *, factory: Callable[[], T | T1 | T2], coercer: None = None, ) -> Coerced[T | T1 | T2, T | T1 | T2]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1], Type[T2]], args: None = None, kwargs: None = None, *, factory: Callable[[], S], coercer: Callable[[S], T | T1 | T2 | S], ) -> Coerced[T | T1 | T2, T | T1 | T2 | S]: ... atom-0.12.1/atom/containerlist.py000066400000000000000000000013561506756731600167500ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from .catom import Validate from .list import List class ContainerList(List): """A List member which supports container notifications.""" __slots__ = () def __init__(self, item=None, default=None): """Initialize a ContainerList.""" super(ContainerList, self).__init__(item, default) self.set_validate_mode(Validate.ContainerList, self.item) atom-0.12.1/atom/containerlist.pyi000066400000000000000000000047061506756731600171230ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from typing import Any, List, Optional, Tuple, Type, TypeVar, overload from .catom import Member T = TypeVar("T") T1 = TypeVar("T1") T2 = TypeVar("T2") class ContainerList(Member[List[T], List[T]]): # No default @overload def __new__( cls, kind: None = None, default: Optional[List[Any]] = None ) -> ContainerList[Any]: ... @overload def __new__(cls, kind: Type[T], default: None = None) -> ContainerList[T]: ... @overload def __new__( cls, kind: Tuple[Type[T]], default: None = None ) -> ContainerList[T]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1]], default: None = None ) -> ContainerList[T | T1]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1], Type[T2]], default: None = None, ) -> ContainerList[T | T1 | T2]: ... @overload def __new__( cls, kind: Member[T, Any], default: None = None ) -> ContainerList[T]: ... # With default @overload def __new__(cls, kind: Type[T], default: List[T]) -> ContainerList[T]: ... @overload def __new__(cls, kind: Tuple[Type[T]], default: List[T]) -> ContainerList[T]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1]], default: List[T | T1] ) -> ContainerList[T | T1]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1]], default: List[T] | List[T1] ) -> ContainerList[T | T1]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1], Type[T2]], default: List[T | T1 | T2] ) -> ContainerList[T | T1 | T2]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1], Type[T2]], default: List[T | T1] | List[T | T2] | List[T1 | T2], ) -> ContainerList[T | T1 | T2]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1], Type[T2]], default: List[T] | List[T1] | List[T2], ) -> ContainerList[T | T1 | T2]: ... @overload def __new__( cls, kind: Member[T, Any], default: Optional[List[T]] = None ) -> ContainerList[T]: ... atom-0.12.1/atom/datastructures/000077500000000000000000000000001506756731600165705ustar00rootroot00000000000000atom-0.12.1/atom/datastructures/__init__.py000066400000000000000000000005611506756731600207030ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- atom-0.12.1/atom/datastructures/api.py000066400000000000000000000006531506756731600177170ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from .sortedmap import sortedmap __all__ = ["sortedmap"] atom-0.12.1/atom/datastructures/sortedmap.pyi000066400000000000000000000023511506756731600213120ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from typing import Generic, List, Optional, Tuple, TypeVar, Union, overload K = TypeVar("K") V = TypeVar("V") D = TypeVar("D") class sortedmap(Generic[K, V]): @overload def get(self, key: K, default: None = None) -> Optional[V]: ... @overload def get(self, key: K, default: D) -> Union[V, D]: ... @overload def pop(self, key: K, default: None = None) -> V: ... @overload def pop(self, key: K, default: D) -> Union[V, D]: ... def clear(self) -> None: ... def keys(self) -> List[K]: ... def values(self) -> List[V]: ... def items(self) -> List[Tuple[K, V]]: ... def copy(self) -> "sortedmap[K, V]": ... def __contains__(self, key: K) -> bool: ... def __getitem__(self, key: K) -> V: ... def __setitem__(self, key: K, value: V) -> None: ... def __delitem__(self, key: K) -> None: ... def __sizeof__(self) -> int: ... atom-0.12.1/atom/delegator.py000066400000000000000000000114701506756731600160360ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from .catom import ( DefaultValue, Member, PostGetAttr, PostSetAttr, PostValidate, Validate, ) class Delegator(Member): """A member subclass which delegates all work to a wrapped member. The only behaviors not delegated are GetAttr and SetAttr. Subclasses should override behavior as needed to suit their needs. In order to change a particular mode, the relevant change method must be called via super(Delegator, ...). """ __slots__ = "delegate" def __init__(self, delegate): """Initialize a DeclarativeProperty. Parameters ---------- delegate : Member The Atom Member which provides the behavior for the property. The member should use standard slot behavior semantics. """ self.delegate = delegate sup = super(Delegator, self) sup.set_post_getattr_mode(PostGetAttr.Delegate, delegate) sup.set_post_setattr_mode(PostSetAttr.Delegate, delegate) sup.set_default_value_mode(DefaultValue.Delegate, delegate) sup.set_validate_mode(Validate.Delegate, delegate) sup.set_post_validate_mode(PostValidate.Delegate, delegate) def add_static_observer(self, observer): """Add a static observer to the member. This method also adds the static observer to the delegate. """ super(Delegator, self).add_static_observer(observer) self.delegate.add_static_observer(observer) def remove_static_observer(self, observer): """Remove a static observer from the member. This method also removes the static observer from the delegate. """ super(Delegator, self).remove_static_observer(observer) self.delegate.remove_static_observer(observer) def set_name(self, name): """Assign the name to this member. This method keeps the name of the delegate member in sync. """ super(Delegator, self).set_name(name) self.delegate.set_name(name) def set_index(self, index): """Assign the index to this member. This method keeps the index of the delegate member in sync. """ super(Delegator, self).set_index(index) self.delegate.set_index(index) def set_post_getattr_mode(self, mode, context): """Set the post getattr mode for the member. This method proxies the change to the delegate member. """ self.delegate.set_post_getattr_mode(mode, context) def set_post_setattr_mode(self, mode, context): """Set the post getattr mode for the member. This method proxies the change to the delegate member. """ self.delegate.set_post_setattr_mode(mode, context) def set_default_value_mode(self, mode, context): """Set the default value mode for the member. This method proxies the change to the delegate member. """ self.delegate.set_default_value_mode(mode, context) def set_validate_mode(self, mode, context): """Set the default value mode for the member. This method proxies the change to the delegate member. """ self.delegate.set_validate_mode(mode, context) def set_post_validate_mode(self, mode, context): """Set the default value mode for the member. This method proxies the change to the delegate member. """ self.delegate.set_post_validate_mode(mode, context) def clone(self): """Create a clone of the declarative property. This method also creates a clone of the internal delegate for mode handlers which use the original delegate as the context. """ clone = super(Delegator, self).clone() delegate = self.delegate clone.delegate = delegate_clone = delegate.clone() mode, old = clone.post_getattr_mode if old is delegate: clone.set_post_getattr_mode(mode, delegate_clone) mode, old = clone.post_setattr_mode if old is delegate: clone.set_post_setattr_mode(mode, delegate_clone) mode, old = clone.default_value_mode if old is delegate: clone.set_default_value_mode(mode, delegate_clone) mode, old = clone.validate_mode if old is delegate: clone.set_validate_mode(mode, delegate_clone) mode, old = clone.post_validate_mode if old is delegate: clone.set_post_validate_mode(mode, delegate_clone) return clone atom-0.12.1/atom/delegator.pyi000066400000000000000000000010551506756731600162050ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from typing import TypeVar from .catom import Member T = TypeVar("T") S = TypeVar("S") class Delegator(Member[T, S]): def __new__(cls, member: Member[T, S]) -> Delegator[T, S]: ... atom-0.12.1/atom/dict.py000066400000000000000000000204271506756731600150150ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from collections import defaultdict from .catom import DefaultValue, Member, Validate from .instance import Instance from .typing_utils import extract_types, is_optional class Dict(Member): """A value of type `dict`.""" __slots__ = () def __init__(self, key=None, value=None, default=None): """Initialize a Dict. Parameters ---------- key : Member, type, tuple of types, or None, optional A member to use for validating the types of keys allowed in the dict. This can also be a type or a tuple of types, which will be wrapped with an Instance member. If this is not given, no key validation is performed. value : Member, type, tuple of types, or None, optional A member to use for validating the types of values allowed in the dict. This can also be a type or a tuple of types, which will be wrapped with an Instance member. If this is not given, no value validation is performed. default : dict, optional The default dict of items. A new copy of this dict will be created for each atom instance. """ self.set_default_value_mode(DefaultValue.Dict, default) if key is not None and not isinstance(key, Member): opt, types = is_optional(extract_types(key)) key = Instance(types, optional=opt) if value is not None and not isinstance(value, Member): opt, types = is_optional(extract_types(value)) value = Instance(types, optional=opt) self.set_validate_mode(Validate.Dict, (key, value)) def set_name(self, name): """Assign the name to this member. This method is called by the Atom metaclass when a class is created. This makes sure the name of the internal members are also updated. """ super(Dict, self).set_name(name) key, value = self.validate_mode[1] if key is not None: key.set_name(name + "|key") if value is not None: value.set_name(name + "|value") def set_index(self, index): """Assign the index to this member. This method is called by the Atom metaclass when a class is created. This makes sure the index of the internal members are also updated. """ super(Dict, self).set_index(index) key, value = self.validate_mode[1] if key is not None: key.set_index(index) if value is not None: value.set_index(index) def clone(self): """Create a clone of the member. This will clone the internal dict key and value members if they exist. """ clone = super(Dict, self).clone() key, value = self.validate_mode[1] if key is not None or value is not None: key_clone = key.clone() if key is not None else None value_clone = value.clone() if value is not None else None mode, _ = self.validate_mode clone.set_validate_mode(mode, (key_clone, value_clone)) return clone class _DefaultWrapper: __slots__ = ("wrapped",) def __init__(self, wrapped): self.wrapped = wrapped def __call__(self, atom): return self.wrapped() def __repr__(self): return repr(self.wrapped) class DefaultDict(Member): """A value of type `dict` implementing __missing__""" __slots__ = () def __init__(self, key=None, value=None, default=None, *, missing=None): """Initialize a DefaultDict. Parameters ---------- key : Member, type, tuple of types, or None, optional A member to use for validating the types of keys allowed in the dict. This can also be a type or a tuple of types, which will be wrapped with an Instance member. If this is not given, no key validation is performed. value : Member, type, tuple of types, or None, optional A member to use for validating the types of values allowed in the dict. This can also be a type or a tuple of types, which will be wrapped with an Instance member. If this is not given, no value validation is performed. default : dict or None, optional The default dict of items. A new copy of this dict will be created for each atom instance. missing : Callable[[], Any] or None, optional Factory to build a default value for a missing key in the dictionary. """ self.set_default_value_mode(DefaultValue.DefaultDict, default) if key is not None and not isinstance(key, Member): opt, types = is_optional(extract_types(key)) key = Instance(types, optional=opt) if value is not None and not isinstance(value, Member): opt, types = is_optional(extract_types(value)) # Assume a default value can be created to avoid the need to specify a # missing factory in simple case even for custom types. value = Instance(types, optional=opt, args=()) if missing is not None: if not callable(missing): raise ValueError( f"The missing argument expect a callable, got {missing}" ) try: missing() except Exception as e: raise ValueError( "The missing argument expect a callable taking no argument. " "Trying to call it with not argument failed with the chained " "exception." ) from e missing = _DefaultWrapper(missing) if isinstance(default, defaultdict): if missing is not None: raise ValueError( "Both a missing factory and a default value which is a default " "dictionary were specified. When using a default dict as default " "value missing should be omitted." ) missing = _DefaultWrapper(default.default_factory) if ( missing is None and value is not None and value.default_value_mode[0] not in (DefaultValue.NoOp, DefaultValue.NonOptional) ): missing = value.do_default_value if missing is None: raise ValueError( "No missing value factory was specified and none could be " "deduced from the value member." ) self.set_validate_mode(Validate.DefaultDict, (key, value, missing)) def set_name(self, name): """Assign the name to this member. This method is called by the Atom metaclass when a class is created. This makes sure the name of the internal members are also updated. """ super().set_name(name) key, value, _ = self.validate_mode[1] if key is not None: key.set_name(name + "|key") if value is not None: value.set_name(name + "|value") def set_index(self, index): """Assign the index to this member. This method is called by the Atom metaclass when a class is created. This makes sure the index of the internal members are also updated. """ super().set_index(index) key, value, _ = self.validate_mode[1] if key is not None: key.set_index(index) if value is not None: value.set_index(index) def clone(self): """Create a clone of the member. This will clone the internal dict key and value members if they exist. """ clone = super().clone() mode, (key, value, missing) = self.validate_mode key_clone = key.clone() if key is not None else None value_clone = value.clone() if value is not None else None clone.set_validate_mode(mode, (key_clone, value_clone, missing)) return clone atom-0.12.1/atom/dict.pyi000066400000000000000000000466021506756731600151710ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from typing import ( Any, Callable, DefaultDict as TDefaultDict, Dict as TDict, Optional, Tuple, Type, TypeVar, overload, ) from .catom import Member T = TypeVar("T") KT = TypeVar("KT") VT = TypeVar("VT") KT1 = TypeVar("KT1") VT1 = TypeVar("VT1") KT2 = TypeVar("KT2") VT2 = TypeVar("VT2") class Dict(Member[TDict[KT, VT], TDict[KT, VT]]): # Untyped @overload def __new__( cls, key: None = None, value: None = None, default: Optional[TDict[Any, Any]] = None, ) -> Dict[Any, Any]: ... # No default # Typed keys # - type @overload def __new__( cls, key: Type[KT], value: None = None, default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT, Any]: ... # - 1-tuple @overload def __new__( cls, key: Tuple[Type[KT]], value: None = None, default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT, Any]: ... # - 2-tuple @overload def __new__( cls, key: Tuple[Type[KT], Type[KT1]], value: None = None, default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT | KT1, Any]: ... # - 3-tuple @overload def __new__( cls, key: Tuple[Type[KT], Type[KT1], Type[KT2]], value: None = None, default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT | KT1 | KT2, Any]: ... # - member @overload def __new__( cls, key: Member[KT, Any], value: None = None, default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT, Any]: ... # Typed values # - type @overload def __new__( cls, key: None, value: Type[VT], default: Optional[TDict[Any, Any]] = None ) -> Dict[Any, VT]: ... # - 1-tuple @overload def __new__( cls, key: None, value: Tuple[Type[VT]], default: Optional[TDict[Any, Any]] = None, ) -> Dict[Any, VT]: ... # - 2-tuple @overload def __new__( cls, key: None, value: Tuple[Type[VT], Type[VT1]], default: Optional[TDict[Any, Any]] = None, ) -> Dict[Any, VT | VT1]: ... # - 3-tuple @overload def __new__( cls, key: None, value: Tuple[Type[VT], Type[VT1], Type[VT2]], default: Optional[TDict[Any, Any]] = None, ) -> Dict[Any, VT | VT1 | VT2]: ... # - member @overload def __new__( cls, key: None, value: Member[VT, Any], default: Optional[TDict[Any, Any]] = None, ) -> Dict[Any, VT]: ... # Typed value through keyword # - type @overload def __new__( cls, key: None = None, *, value: Type[VT], default: Optional[TDict[Any, Any]] = None, ) -> Dict[Any, VT]: ... # - 1-tuple @overload def __new__( cls, key: None = None, *, value: Tuple[Type[VT]], default: Optional[TDict[Any, Any]] = None, ) -> Dict[Any, VT]: ... # - 2-tuple @overload def __new__( cls, key: None = None, *, value: Tuple[Type[VT], Type[VT1]], default: Optional[TDict[Any, Any]] = None, ) -> Dict[Any, VT | VT1]: ... # - 3-tuple @overload def __new__( cls, key: None = None, *, value: Tuple[Type[VT], Type[VT1], Type[VT2]], default: Optional[TDict[Any, Any]] = None, ) -> Dict[Any, VT | VT1 | VT2]: ... # - member @overload def __new__( cls, key: None = None, *, value: Member[VT, Any], default: Optional[TDict[Any, Any]] = None, ) -> Dict[Any, VT]: ... # Typed key and value # - value simple type # - key type @overload def __new__( cls, key: Type[KT], value: Type[VT], default: Optional[TDict[Any, Any]] = None ) -> Dict[KT, VT]: ... # - key 1-tuple @overload def __new__( cls, key: Tuple[Type[KT]], value: Type[VT], default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT, VT]: ... # - key 2-tuple @overload def __new__( cls, key: Tuple[Type[KT], Type[KT1]], value: Type[VT], default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT | KT1, VT]: ... # - key 3-tuple @overload def __new__( cls, key: Tuple[Type[KT], Type[KT1], Type[KT2]], value: Type[VT], default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT | KT1 | KT2, VT]: ... # - key member @overload def __new__( cls, key: Member[KT, Any], value: Type[VT], default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT, VT]: ... # - Value as single element tuple # - key type @overload def __new__( cls, key: Type[KT], value: Tuple[Type[VT]], default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT, VT]: ... # - key 1-tuple @overload def __new__( cls, key: Tuple[Type[KT]], value: Tuple[Type[VT]], default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT, VT]: ... # - key 2-tuple @overload def __new__( cls, key: Tuple[Type[KT], Type[KT1]], value: Tuple[Type[VT]], default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT | KT1, VT]: ... # - key 3-tuple @overload def __new__( cls, key: Tuple[Type[KT], Type[KT1], Type[KT2]], value: Tuple[Type[VT]], default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT | KT1 | KT2, VT]: ... # - key member @overload def __new__( cls, key: Member[KT, Any], value: Tuple[Type[VT]], default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT, VT]: ... # - Value as 2-tuple # - key type @overload def __new__( cls, key: Type[KT], value: Tuple[Type[VT], Type[VT1]], default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT, VT | VT1]: ... # - key 1-tuple @overload def __new__( cls, key: Tuple[Type[KT]], value: Tuple[Type[VT], Type[VT1]], default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT, VT | VT1]: ... # - key 2-tuple @overload def __new__( cls, key: Tuple[Type[KT], Type[KT1]], value: Tuple[Type[VT], Type[VT1]], default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT | KT1, VT | VT1]: ... # - key 3-tuple @overload def __new__( cls, key: Tuple[Type[KT], Type[KT1], Type[KT2]], value: Tuple[Type[VT], Type[VT1]], default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT | KT1 | KT2, VT | VT1]: ... # - key member @overload def __new__( cls, key: Member[KT, Any], value: Tuple[Type[VT], Type[VT1]], default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT, VT | VT1]: ... # - Value as 3-tuple # - key type @overload def __new__( cls, key: Type[KT], value: Tuple[Type[VT], Type[VT1], Type[VT2]], default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT, VT | VT1 | VT2]: ... # - key 1-tuple @overload def __new__( cls, key: Tuple[Type[KT]], value: Tuple[Type[VT], Type[VT1], Type[VT2]], default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT, VT | VT1 | VT2]: ... # - key 2-tuple @overload def __new__( cls, key: Tuple[Type[KT], Type[KT1]], value: Tuple[Type[VT], Type[VT1], Type[VT2]], default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT | KT1, VT | VT1 | VT2]: ... # - key 3-tuple @overload def __new__( cls, key: Tuple[Type[KT], Type[KT1], Type[KT2]], value: Tuple[Type[VT], Type[VT1], Type[VT2]], default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT | KT1 | KT2, VT | VT1 | VT2]: ... # - key member @overload def __new__( cls, key: Member[KT, Any], value: Tuple[Type[VT], Type[VT1], Type[VT2]], default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT, VT | VT1 | VT2]: ... # - value as member # - key type @overload def __new__( cls, key: Type[KT], value: Member[VT, Any], default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT, VT]: ... # - key 1-tuple @overload def __new__( cls, key: Tuple[Type[KT]], value: Member[VT, Any], default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT, VT]: ... # - key 2-tuple @overload def __new__( cls, key: Tuple[Type[KT], Type[KT1]], value: Member[VT, Any], default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT | KT1, VT]: ... # - key 3-tuple @overload def __new__( cls, key: Tuple[Type[KT], Type[KT1], Type[KT2]], value: Member[VT, VT], default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT | KT1 | KT2, VT]: ... # - key member @overload def __new__( cls, key: Member[KT, KT], value: Member[VT, VT], default: Optional[TDict[Any, Any]] = None, ) -> Dict[KT, VT]: ... class DefaultDict(Member[TDefaultDict[KT, VT], TDefaultDict[KT, VT]]): # Untyped @overload def __new__( cls, key: None = None, value: None = None, default: Optional[TDict[Any, Any]] = None, *, missing: None = None, ) -> DefaultDict[Any, Any]: ... # Typed by missing @overload def __new__( cls, key: None = None, value: None = None, default: Optional[TDict[Any, Any]] = None, *, missing: Callable[[], VT], ) -> DefaultDict[Any, VT]: ... # Typed by defaultdict default value @overload def __new__( cls, key: None = None, value: None = None, *, default: TDefaultDict[Any, VT], missing: Callable[[], VT], ) -> DefaultDict[Any, VT]: ... # No default # Typed keys # - type @overload def __new__( cls, key: Type[KT], value: None = None, default: Optional[TDict[Any, Any]] = None, *, missing: None = None, ) -> DefaultDict[KT, Any]: ... # - 1-tuple @overload def __new__( cls, key: Tuple[Type[KT]], value: None = None, default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT, Any]: ... # - 2-tuple @overload def __new__( cls, key: Tuple[Type[KT], Type[KT1]], value: None = None, default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT | KT1, Any]: ... # - 3-tuple @overload def __new__( cls, key: Tuple[Type[KT], Type[KT1], Type[KT2]], value: None = None, default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT | KT1 | KT2, Any]: ... # - member @overload def __new__( cls, key: Member[KT, Any], value: None = None, default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT, Any]: ... # Typed values # - type @overload def __new__( cls, key: None, value: Type[VT], default: Optional[TDict[Any, Any]] = None ) -> DefaultDict[Any, VT]: ... # - 1-tuple @overload def __new__( cls, key: None, value: Tuple[Type[VT]], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[Any, VT]: ... # - 2-tuple @overload def __new__( cls, key: None, value: Tuple[Type[VT], Type[VT1]], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[Any, VT | VT1]: ... # - 3-tuple @overload def __new__( cls, key: None, value: Tuple[Type[VT], Type[VT1], Type[VT2]], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[Any, VT | VT1 | VT2]: ... # - member @overload def __new__( cls, key: None, value: Member[VT, Any], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[Any, VT]: ... # Typed value through keyword # - type @overload def __new__( cls, key: None = None, *, value: Type[VT], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[Any, VT]: ... # - 1-tuple @overload def __new__( cls, key: None = None, *, value: Tuple[Type[VT]], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[Any, VT]: ... # - 2-tuple @overload def __new__( cls, key: None = None, *, value: Tuple[Type[VT], Type[VT1]], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[Any, VT | VT1]: ... # - 3-tuple @overload def __new__( cls, key: None = None, *, value: Tuple[Type[VT], Type[VT1], Type[VT2]], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[Any, VT | VT1 | VT2]: ... # - member @overload def __new__( cls, key: None = None, *, value: Member[VT, Any], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[Any, VT]: ... # Typed key and value # - value simple type # - key type @overload def __new__( cls, key: Type[KT], value: Type[VT], default: Optional[TDict[Any, Any]] = None ) -> DefaultDict[KT, VT]: ... # - key 1-tuple @overload def __new__( cls, key: Tuple[Type[KT]], value: Type[VT], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT, VT]: ... # - key 2-tuple @overload def __new__( cls, key: Tuple[Type[KT], Type[KT1]], value: Type[VT], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT | KT1, VT]: ... # - key 3-tuple @overload def __new__( cls, key: Tuple[Type[KT], Type[KT1], Type[KT2]], value: Type[VT], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT | KT1 | KT2, VT]: ... # - key member @overload def __new__( cls, key: Member[KT, Any], value: Type[VT], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT, VT]: ... # - Value as single element tuple # - key type @overload def __new__( cls, key: Type[KT], value: Tuple[Type[VT]], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT, VT]: ... # - key 1-tuple @overload def __new__( cls, key: Tuple[Type[KT]], value: Tuple[Type[VT]], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT, VT]: ... # - key 2-tuple @overload def __new__( cls, key: Tuple[Type[KT], Type[KT1]], value: Tuple[Type[VT]], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT | KT1, VT]: ... # - key 3-tuple @overload def __new__( cls, key: Tuple[Type[KT], Type[KT1], Type[KT2]], value: Tuple[Type[VT]], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT | KT1 | KT2, VT]: ... # - key member @overload def __new__( cls, key: Member[KT, Any], value: Tuple[Type[VT]], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT, VT]: ... # - Value as 2-tuple # - key type @overload def __new__( cls, key: Type[KT], value: Tuple[Type[VT], Type[VT1]], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT, VT | VT1]: ... # - key 1-tuple @overload def __new__( cls, key: Tuple[Type[KT]], value: Tuple[Type[VT], Type[VT1]], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT, VT | VT1]: ... # - key 2-tuple @overload def __new__( cls, key: Tuple[Type[KT], Type[KT1]], value: Tuple[Type[VT], Type[VT1]], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT | KT1, VT | VT1]: ... # - key 3-tuple @overload def __new__( cls, key: Tuple[Type[KT], Type[KT1], Type[KT2]], value: Tuple[Type[VT], Type[VT1]], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT | KT1 | KT2, VT | VT1]: ... # - key member @overload def __new__( cls, key: Member[KT, Any], value: Tuple[Type[VT], Type[VT1]], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT, VT | VT1]: ... # - Value as 3-tuple # - key type @overload def __new__( cls, key: Type[KT], value: Tuple[Type[VT], Type[VT1], Type[VT2]], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT, VT | VT1 | VT2]: ... # - key 1-tuple @overload def __new__( cls, key: Tuple[Type[KT]], value: Tuple[Type[VT], Type[VT1], Type[VT2]], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT, VT | VT1 | VT2]: ... # - key 2-tuple @overload def __new__( cls, key: Tuple[Type[KT], Type[KT1]], value: Tuple[Type[VT], Type[VT1], Type[VT2]], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT | KT1, VT | VT1 | VT2]: ... # - key 3-tuple @overload def __new__( cls, key: Tuple[Type[KT], Type[KT1], Type[KT2]], value: Tuple[Type[VT], Type[VT1], Type[VT2]], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT | KT1 | KT2, VT | VT1 | VT2]: ... # - key member @overload def __new__( cls, key: Member[KT, Any], value: Tuple[Type[VT], Type[VT1], Type[VT2]], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT, VT | VT1 | VT2]: ... # - value as member # - key type @overload def __new__( cls, key: Type[KT], value: Member[VT, Any], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT, VT]: ... # - key 1-tuple @overload def __new__( cls, key: Tuple[Type[KT]], value: Member[VT, Any], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT, VT]: ... # - key 2-tuple @overload def __new__( cls, key: Tuple[Type[KT], Type[KT1]], value: Member[VT, Any], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT | KT1, VT]: ... # - key 3-tuple @overload def __new__( cls, key: Tuple[Type[KT], Type[KT1], Type[KT2]], value: Member[VT, VT], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT | KT1 | KT2, VT]: ... # - key member @overload def __new__( cls, key: Member[KT, KT], value: Member[VT, VT], default: Optional[TDict[Any, Any]] = None, ) -> DefaultDict[KT, VT]: ... atom-0.12.1/atom/enum.py000066400000000000000000000054141506756731600150350ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from .catom import DefaultValue, Member, Validate class Enum(Member): """A member where the value can be one in a sequence of items.""" __slots__ = () def __init__(self, *items): """Initialize an Enum. Parameters ---------- *items The allowed values which can be assigned to the enum. """ if len(items) == 0: raise ValueError("an Enum requires at least 1 item") self.set_default_value_mode(DefaultValue.Static, items[0]) self.set_validate_mode(Validate.Enum, items) @property def items(self): """A readonly property which returns the items in the enum.""" return self.validate_mode[1] def added(self, *items): """Create a clone of the Enum with added items. Parameters ---------- *items Additional items to include in the Enum. Returns ------- result : Enum A new enum object which contains all of the original items plus the new items. """ olditems = self.items newitems = olditems + items clone = self.clone() clone.set_validate_mode(Validate.Enum, newitems) return clone def removed(self, *items): """Create a clone of the Enum with some items removed. Parameters ---------- *items The items to remove remove from the new enum. Returns ------- result : Enum A new enum object which contains all of the original items but with the given items removed. """ newitems = tuple(i for i in self.items if i not in items) if len(newitems) == 0: raise ValueError("an Enum requires at least 1 item") clone = self.clone() clone.set_default_value_mode(DefaultValue.Static, newitems[0]) clone.set_validate_mode(Validate.Enum, newitems) return clone def __call__(self, item): """Create a clone of the Enum item with a new default. Parameters ---------- item : object The item to use as the Enum default. The item must be one of the valid enum items. """ if item not in self.items: raise TypeError("invalid enum value") clone = self.clone() clone.set_default_value_mode(DefaultValue.Static, item) return clone atom-0.12.1/atom/enum.pyi000066400000000000000000000020101506756731600151730ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from typing import Any, Tuple, TypeVar, Union, overload from .catom import Member # We cannot type properly an enum, as it would require some dynamic literal so this is # just a best effort. T = TypeVar("T") T1 = TypeVar("T1") class Enum(Member[T, T]): @overload def __new__(cls, item: T) -> Enum[T]: ... # This is hacky but should do @overload def __new__(cls, *items: T) -> Enum[T]: ... @property def items(self) -> Tuple[T, ...]: ... def added(self: Enum[T], *items: T1) -> Enum[Union[T, T1]]: ... def removed(self: Enum[T], *items: Any) -> Enum[T]: ... def __call__(self: Enum[T], item: T1) -> Enum[Union[T, T1]]: ... atom-0.12.1/atom/event.py000066400000000000000000000040451506756731600152110ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from .catom import DelAttr, GetAttr, Member, SetAttr, Validate from .typing_utils import extract_types class Event(Member): """A member which acts like a stateless event.""" __slots__ = () def __init__(self, kind=None): """Initialize an Event. Parameters ---------- kind : type, tuple of types or Member, optional The type of argument which may be emitted by the event or a Member which will validate the argument which can be emitted. The default is None and indicates no validation will be performed. """ self.set_getattr_mode(GetAttr.Event, None) self.set_setattr_mode(SetAttr.Event, None) self.set_delattr_mode(DelAttr.Event, None) if kind is not None: if isinstance(kind, Member): self.set_validate_mode(Validate.Delegate, kind) else: self.set_validate_mode(Validate.Instance, extract_types(kind)) def set_name(self, name): """A reimplemented parent class method. This method ensures that the delegate name is also set, if a delegate validator is being used. """ super(Event, self).set_name(name) _mode, kind = self.validate_mode if isinstance(kind, Member): kind.set_name(name) def set_index(self, index): """A reimplemented parent class method. This method ensures that the delegate index is also set, if a delegate validator is being used. """ super(Event, self).set_index(index) _mode, kind = self.validate_mode if isinstance(kind, Member): kind.set_index(index) atom-0.12.1/atom/event.pyi000066400000000000000000000020571506756731600153630ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from typing import Any, Tuple, Type, TypeVar, Union, overload from .catom import EventBinder, Member T = TypeVar("T") T1 = TypeVar("T1") T2 = TypeVar("T2") class Event(Member[EventBinder, T]): @overload def __new__(cls, kind: None = None) -> Event[Any]: ... @overload def __new__(cls, kind: Type[T]) -> Event[T]: ... @overload def __new__(cls, kind: Tuple[Type[T]]) -> Event[T]: ... @overload def __new__(cls, kind: Tuple[Type[T], Type[T1]]) -> Event[Union[T, T1]]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1], Type[T2]] ) -> Event[Union[T, T1, T2]]: ... @overload def __new__(cls, kind: Member[Any, T]) -> Event[T]: ... atom-0.12.1/atom/instance.py000066400000000000000000000164421506756731600157000ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from .catom import DefaultValue, GetState, Member, Validate from .typing_utils import extract_types, is_optional class Instance(Member): """A value which allows objects of a given type or types. Values will be tested using the `PyObject_IsInstance` C API call. This call is equivalent to `isinstance(value, kind)` and all the same rules apply. If optional is True, the value of an Instance may be set to None, otherwise None is not considered as a valid value. By default, optional will be considered False if a default value is provided and True otherwise. """ __slots__ = () def __init__(self, kind, args=None, kwargs=None, *, factory=None, optional=None): """Initialize an Instance. Parameters ---------- kind : type or tuple of types The allowed type or types for the instance. args : tuple, optional If 'factory' is None, then 'kind' is a callable type and these arguments will be passed to the constructor to create the default value. kwargs : dict, optional If 'factory' is None, then 'kind' is a callable type and these keywords will be passed to the constructor to create the default value. factory : callable, optional An optional factory to use for creating the default value. If this is not provided and 'args' and 'kwargs' is None, then the default value will be None, which will raised if accessed when optional is False. optional : bool | None, optional Boolean indicating if None is a valid value for the member. By default, the value is inferred to be True if no args or factory is provided. """ opt, kind = is_optional(extract_types(kind)) # Since we fast track None it is relevant to identify it early. if opt and optional is False: raise ValueError( "The type passed to Instance is declared optional but optional was " "explicitly set to False" ) # If opt is False we preserve optional as None for backward compatibility. optional = optional if optional is not None else (opt or None) if factory is not None: self.set_default_value_mode(DefaultValue.CallObject, factory) elif args is not None or kwargs is not None: args = args or () kwargs = kwargs or {} def factory(): return kind[0](*args, **kwargs) self.set_default_value_mode(DefaultValue.CallObject, factory) elif optional is False: self.set_default_value_mode(DefaultValue.NonOptional, None) optional = ( optional if optional is not None else (factory is None and args is None and kwargs is None) ) if optional: self.set_validate_mode(Validate.OptionalInstance, kind) else: self.set_validate_mode(Validate.Instance, kind) # Allow to create a pickle with an unset typed value self.set_getstate_mode(GetState.IncludeNonDefault, None) class ForwardInstance(Instance): """An Instance which delays resolving the type definition. The first time the value is accessed or modified, the type will be resolved and the forward instance will behave identically to a normal instance. """ __slots__ = ("args", "kwargs", "optional", "resolve") def __init__(self, resolve, args=None, kwargs=None, *, factory=None, optional=None): """Initialize a ForwardInstance. resolve : callable A callable which takes no arguments and returns the type or tuple of types to use for validating the values. args : tuple, optional If 'factory' is None, then 'resolve' will return a callable type and these arguments will be passed to the constructor to create the default value. kwargs : dict, optional If 'factory' is None, then 'resolve' will return a callable type and these keywords will be passed to the constructor to create the default value. factory : callable, optional An optional factory to use for creating the default value. If this is not provided and 'args' and 'kwargs' is None, then the default value will be None, which will raised if accessed when optional is False. optional : bool | None, optional Boolean indicating if None is a valid value for the member. By default, the value is inferred to be True if no args or factory is provided. """ self.resolve = resolve self.args = args self.kwargs = kwargs if factory is not None: self.set_default_value_mode(DefaultValue.CallObject, factory) elif args is not None or kwargs is not None: mode = DefaultValue.MemberMethod_Object self.set_default_value_mode(mode, "default") elif optional is False: self.set_default_value_mode(DefaultValue.NonOptional, None) self.optional = ( optional if optional is not None else (factory is None and args is None and kwargs is None) ) if not self.optional: # Allow to create a pickle with an unset typed value self.set_getstate_mode(GetState.IncludeNonDefault, None) self.set_validate_mode(Validate.MemberMethod_ObjectOldNew, "validate") def default(self, owner): """Called to retrieve the default value. This is called the first time the default value is retrieved for the member. It resolves the type and updates the internal default handler to behave like a normal Instance member. """ kind = self.resolve() args = self.args or () kwargs = self.kwargs or {} def factory(): return kind(*args, **kwargs) self.set_default_value_mode(DefaultValue.CallObject, factory) return kind(*args, **kwargs) def validate(self, owner, old, new): """Called to validate the value. This is called the first time a value is validated for the member. It resolves the type and updates the internal validate handler to behave like a normal Instance member. """ kind = extract_types(self.resolve()) if self.optional: self.set_validate_mode(Validate.OptionalInstance, kind) else: self.set_validate_mode(Validate.Instance, kind) return self.do_validate(owner, old, new) def clone(self): """Create a clone of the ForwardInstance object.""" clone = super(ForwardInstance, self).clone() clone.resolve = self.resolve clone.args = self.args clone.kwargs = self.kwargs clone.optional = self.optional return clone atom-0.12.1/atom/instance.pyi000066400000000000000000000366741506756731600160620ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from typing import ( Any, Callable, Dict, Literal, Optional, Tuple, Type, TypeVar, overload, ) from .catom import Member T = TypeVar("T") T1 = TypeVar("T1") T2 = TypeVar("T2") class Instance(Member[T, T]): # Single Type @overload def __new__( cls, kind: Type[T], args: None = None, kwargs: None = None, *, factory: None = None, optional: None = None, ) -> Instance[Optional[T]]: ... @overload def __new__( cls, kind: Type[T], args: Tuple[Any, ...], kwargs: Optional[Dict[str, Any]] = None, *, factory: None = None, optional: None = None, ) -> Instance[T]: ... @overload def __new__( cls, kind: Type[T], args: None = None, *, kwargs: Dict[str, Any], factory: None = None, optional: None = None, ) -> Instance[T]: ... @overload def __new__( cls, kind: Type[T], args: None = None, kwargs: None = None, *, factory: Callable[[], T], optional: None = None, ) -> Instance[T]: ... @overload def __new__( cls, kind: Type[T], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: Optional[Callable[[], T]] = None, optional: Literal[True], ) -> Instance[Optional[T]]: ... @overload def __new__( cls, kind: Type[T], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: Optional[Callable[[], T]] = None, optional: Literal[False], ) -> Instance[T]: ... # 1-Tuple[Any, ...] @overload def __new__( cls, kind: Tuple[Type[T]], args: None = None, kwargs: None = None, *, factory: None = None, optional: None = None, ) -> Instance[Optional[T]]: ... @overload def __new__( cls, kind: Tuple[Type[T]], args: Tuple[Any, ...], kwargs: Optional[Dict[str, Any]] = None, *, factory: None = None, optional: None = None, ) -> Instance[T]: ... @overload def __new__( cls, kind: Tuple[Type[T]], args: None = None, *, kwargs: Dict[str, Any], factory: None = None, optional: None = None, ) -> Instance[T]: ... @overload def __new__( cls, kind: Tuple[Type[T]], args: None = None, kwargs: None = None, *, factory: Callable[[], T], optional: None = None, ) -> Instance[T]: ... @overload def __new__( cls, kind: Tuple[Type[T]], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: Optional[Callable[[], T]] = None, optional: Literal[True], ) -> Instance[Optional[T]]: ... @overload def __new__( cls, kind: Tuple[Type[T]], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: Optional[Callable[[], T]] = None, optional: Literal[False], ) -> Instance[T]: ... # 2-Tuple[Any, ...] @overload def __new__( cls, kind: Tuple[Type[T], Type[T1]], args: None = None, kwargs: None = None, *, factory: None = None, optional: None = None, ) -> Instance[Optional[T | T1]]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1]], args: Tuple[Any, ...], kwargs: Optional[Dict[str, Any]] = None, *, factory: None = None, optional: None = None, ) -> Instance[T | T1]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1]], args: None = None, *, kwargs: Dict[str, Any], factory: None = None, optional: None = None, ) -> Instance[T | T1]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1]], args: None = None, kwargs: None = None, *, factory: Callable[[], T] | Callable[[], T1] | Callable[[], T | T1], optional: None = None, ) -> Instance[T | T1]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1]], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: Optional[ Callable[[], T] | Callable[[], T1] | Callable[[], T | T1] ] = None, optional: Literal[True], ) -> Instance[Optional[T | T1]]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1]], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: Optional[ Callable[[], T] | Callable[[], T1] | Callable[[], T | T1] ] = None, optional: Literal[False], ) -> Instance[T | T1]: ... # 3-Tuple[Any, ...] @overload def __new__( cls, kind: Tuple[Type[T], Type[T1], Type[T2]], args: None = None, kwargs: None = None, *, factory: None = None, optional: None = None, ) -> Instance[Optional[T | T1 | T2]]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1], Type[T2]], args: Tuple[Any, ...], kwargs: Optional[Dict[str, Any]] = None, *, factory: None = None, optional: None = None, ) -> Instance[T | T1 | T2]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1], Type[T2]], args: None = None, *, kwargs: Dict[str, Any], factory: None = None, optional: None = None, ) -> Instance[T | T1 | T2]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1], Type[T2]], args: None = None, kwargs: None = None, *, factory: Callable[[], T] | Callable[[], T1] | Callable[[], T2] | Callable[[], T | T1] | Callable[[], T | T2] | Callable[[], T1 | T2] | Callable[[], T | T1 | T2], optional: None = None, ) -> Instance[T | T1 | T2]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1], Type[T2]], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: Optional[ Callable[[], T] | Callable[[], T1] | Callable[[], T2] | Callable[[], T | T1] | Callable[[], T | T2] | Callable[[], T1 | T2] | Callable[[], T | T1 | T2] ] = None, optional: Literal[True], ) -> Instance[Optional[T | T1 | T2]]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1], Type[T2]], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: Optional[ Callable[[], T] | Callable[[], T1] | Callable[[], T2] | Callable[[], T | T1] | Callable[[], T | T2] | Callable[[], T1 | T2] | Callable[[], T | T1 | T2] ] = None, optional: Literal[False], ) -> Instance[T | T1 | T2]: ... class ForwardInstance(Member[T, T]): # Single Type @overload def __new__( cls, kind: Callable[[], Type[T]], args: None = None, kwargs: None = None, *, factory: None = None, optional: None = None, ) -> ForwardInstance[Optional[T]]: ... @overload def __new__( cls, kind: Callable[[], Type[T]], args: Tuple[Any, ...], kwargs: Optional[Dict[str, Any]] = None, *, factory: None = None, optional: None = None, ) -> ForwardInstance[T]: ... @overload def __new__( cls, kind: Callable[[], Type[T]], args: None = None, *, kwargs: Dict[str, Any], factory: None = None, optional: None = None, ) -> ForwardInstance[T]: ... @overload def __new__( cls, kind: Callable[[], Type[T]], args: None = None, kwargs: None = None, *, factory: Callable[[], T], optional: None = None, ) -> ForwardInstance[T]: ... @overload def __new__( cls, kind: Callable[[], Type[T]], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: Optional[Callable[[], T]] = None, optional: Literal[True], ) -> ForwardInstance[Optional[T]]: ... @overload def __new__( cls, kind: Callable[[], Type[T]], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: Optional[Callable[[], T]] = None, optional: Literal[False], ) -> ForwardInstance[T]: ... # 1-Tuple[Any, ...] @overload def __new__( cls, kind: Callable[[], Tuple[Type[T]]], args: None = None, kwargs: None = None, *, factory: None = None, optional: None = None, ) -> ForwardInstance[Optional[T]]: ... @overload def __new__( cls, kind: Callable[[], Tuple[Type[T]]], args: Tuple[Any, ...], kwargs: Optional[Dict[str, Any]] = None, *, factory: None = None, optional: None = None, ) -> ForwardInstance[T]: ... @overload def __new__( cls, kind: Callable[[], Tuple[Type[T]]], args: None = None, *, kwargs: Dict[str, Any], factory: None = None, optional: None = None, ) -> ForwardInstance[T]: ... @overload def __new__( cls, kind: Callable[[], Tuple[Type[T]]], args: None = None, kwargs: None = None, *, factory: Callable[[], T], optional: None = None, ) -> ForwardInstance[T]: ... @overload def __new__( cls, kind: Callable[[], Tuple[Type[T]]], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: Optional[Callable[[], T]] = None, optional: Literal[True], ) -> ForwardInstance[Optional[T]]: ... @overload def __new__( cls, kind: Callable[[], Tuple[Type[T]]], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: Optional[Callable[[], T]] = None, optional: Literal[False], ) -> ForwardInstance[T]: ... # 2-Tuple[Any, ...] @overload def __new__( cls, kind: Callable[[], Tuple[Type[T], Type[T1]]], args: None = None, kwargs: None = None, *, factory: None = None, optional: None = None, ) -> ForwardInstance[Optional[T | T1]]: ... @overload def __new__( cls, kind: Callable[[], Tuple[Type[T], Type[T1]]], args: Tuple[Any, ...], kwargs: Optional[Dict[str, Any]] = None, *, factory: None = None, optional: None = None, ) -> ForwardInstance[T | T1]: ... @overload def __new__( cls, kind: Callable[[], Tuple[Type[T], Type[T1]]], args: None = None, *, kwargs: Dict[str, Any], factory: None = None, optional: None = None, ) -> ForwardInstance[T | T1]: ... @overload def __new__( cls, kind: Callable[[], Tuple[Type[T], Type[T1]]], args: None = None, kwargs: None = None, *, factory: Callable[[], T] | Callable[[], T1] | Callable[[], T | T1], optional: None = None, ) -> ForwardInstance[T | T1]: ... @overload def __new__( cls, kind: Callable[[], Tuple[Type[T], Type[T1]]], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: Optional[ Callable[[], T] | Callable[[], T1] | Callable[[], T | T1] ] = None, optional: Literal[True], ) -> ForwardInstance[Optional[T | T1]]: ... @overload def __new__( cls, kind: Callable[[], Tuple[Type[T], Type[T1]]], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: Optional[ Callable[[], T] | Callable[[], T1] | Callable[[], T | T1] ] = None, optional: Literal[False], ) -> ForwardInstance[T | T1]: ... # 3-Tuple[Any, ...] @overload def __new__( cls, kind: Callable[[], Tuple[Type[T], Type[T1], Type[T2]]], args: None = None, kwargs: None = None, *, factory: None = None, optional: None = None, ) -> ForwardInstance[Optional[T | T1 | T2]]: ... @overload def __new__( cls, kind: Callable[[], Tuple[Type[T], Type[T1], Type[T2]]], args: Tuple[Any, ...], kwargs: Optional[Dict[str, Any]] = None, *, factory: None = None, optional: None = None, ) -> ForwardInstance[T | T1 | T2]: ... @overload def __new__( cls, kind: Callable[[], Tuple[Type[T], Type[T1], Type[T2]]], args: None = None, *, kwargs: Dict[str, Any], factory: None = None, optional: None = None, ) -> ForwardInstance[T | T1 | T2]: ... @overload def __new__( cls, kind: Callable[[], Tuple[Type[T], Type[T1], Type[T2]]], args: None = None, kwargs: None = None, *, factory: Callable[[], T] | Callable[[], T1] | Callable[[], T2] | Callable[[], T | T1] | Callable[[], T | T2] | Callable[[], T1 | T2] | Callable[[], T | T1 | T2], optional: None = None, ) -> ForwardInstance[T | T1 | T2]: ... @overload def __new__( cls, kind: Callable[[], Tuple[Type[T], Type[T1], Type[T2]]], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: Optional[ Callable[[], T] | Callable[[], T1] | Callable[[], T2] | Callable[[], T | T1] | Callable[[], T | T2] | Callable[[], T1 | T2] | Callable[[], T | T1 | T2] ] = None, optional: Literal[True], ) -> ForwardInstance[Optional[T | T1 | T2]]: ... @overload def __new__( cls, kind: Callable[[], Tuple[Type[T], Type[T1], Type[T2]]], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: Optional[ Callable[[], T] | Callable[[], T1] | Callable[[], T2] | Callable[[], T | T1] | Callable[[], T | T2] | Callable[[], T1 | T2] | Callable[[], T | T1 | T2] ] = None, optional: Literal[False], ) -> ForwardInstance[T | T1 | T2]: ... atom-0.12.1/atom/list.py000066400000000000000000000052611506756731600150440ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from .catom import DefaultValue, Member, Validate from .instance import Instance from .typing_utils import extract_types, is_optional class List(Member): """A member which allows list values. Assigning to a list creates a copy. The orginal list will remain unmodified. This is similar to the semantics of the assignment operator on the C++ STL container classes. """ __slots__ = "item" def __init__(self, item=None, default=None): """Initialize a List. Parameters ---------- item : Member, type, or tuple of types, optional A member to use for validating the types of items allowed in the list. This can also be a type object or a tuple of types, in which case it will be wrapped with an non-optional Instance member. If this is not given, no item validation is performed. default : list, optional The default list of values. A new copy of this list will be created for each atom instance. """ if item is not None and not isinstance(item, Member): opt, types = is_optional(extract_types(item)) item = Instance(types, optional=opt) self.item = item self.set_default_value_mode(DefaultValue.List, default) self.set_validate_mode(Validate.List, item) def set_name(self, name): """Set the name of the member. This method ensures that the item member name is also updated. """ super(List, self).set_name(name) if self.item is not None: self.item.set_name(name + "|item") def set_index(self, index): """Assign the index to this member. This method ensures that the item member index is also updated. """ super(List, self).set_index(index) if self.item is not None: self.item.set_index(index) def clone(self): """Create a clone of the list. This will clone the internal list item if one is in use. """ clone = super(List, self).clone() item = self.item if item is not None: clone.item = item_clone = item.clone() mode, _ctxt = self.validate_mode clone.set_validate_mode(mode, item_clone) else: clone.item = None return clone atom-0.12.1/atom/list.pyi000066400000000000000000000044351506756731600152170ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from typing import Any, List as TList, Optional, Tuple, Type, TypeVar, overload from .catom import Member T = TypeVar("T") T1 = TypeVar("T1") T2 = TypeVar("T2") class List(Member[TList[T], TList[T]]): # No default @overload def __new__( cls, kind: None = None, default: Optional[TList[Any]] = None ) -> List[Any]: ... @overload def __new__(cls, kind: Type[T], default: None = None) -> List[T]: ... @overload def __new__(cls, kind: Tuple[Type[T]], default: None = None) -> List[T]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1]], default: None = None ) -> List[T | T1]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1], Type[T2]], default: None = None, ) -> List[T | T1 | T2]: ... @overload def __new__(cls, kind: Member[T, Any], default: None = None) -> List[T]: ... # With default @overload def __new__(cls, kind: Type[T], default: TList[T]) -> List[T]: ... @overload def __new__(cls, kind: Tuple[Type[T]], default: TList[T]) -> List[T]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1]], default: TList[T | T1] ) -> List[T | T1]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1]], default: TList[T] | TList[T1] ) -> List[T | T1]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1], Type[T2]], default: TList[T | T1 | T2] ) -> List[T | T1 | T2]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1], Type[T2]], default: TList[T | T1] | TList[T | T2] | TList[T1 | T2], ) -> List[T | T1 | T2]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1], Type[T2]], default: TList[T] | TList[T1] | TList[T2], ) -> List[T | T1 | T2]: ... @overload def __new__(cls, kind: Member[T, Any], default: TList[T]) -> List[T]: ... atom-0.12.1/atom/meta/000077500000000000000000000000001506756731600144415ustar00rootroot00000000000000atom-0.12.1/atom/meta/__init__.py000066400000000000000000000013251506756731600165530ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2023-2024, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Atom metaclass and tools used to create atom subclasses.""" from .atom_meta import AtomMeta, MissingMemberWarning, add_member, clone_if_needed from .member_modifiers import set_default from .observation import observe __all__ = [ "AtomMeta", "MissingMemberWarning", "add_member", "clone_if_needed", "observe", "set_default", ] atom-0.12.1/atom/meta/annotation_utils.py000066400000000000000000000163561506756731600204200ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- import collections.abc import sys from collections import defaultdict from typing import Any, ClassVar, Final, Literal, MutableMapping, Type from ..catom import Member, SetAttr from ..dict import DefaultDict, Dict as ADict from ..enum import Enum from ..instance import Instance from ..list import List as AList from ..scalars import Bool, Bytes, Callable as ACallable, Float, Int, Str, Value from ..set import Set as ASet from ..subclass import Subclass from ..tuple import FixedTuple, Tuple as ATuple from ..typed import Typed from ..typing_utils import extract_types, get_args, get_origin, is_optional from .member_modifiers import set_default _NO_DEFAULT = object() _TYPE_TO_MEMBER = { bool: Bool, int: Int, float: Float, str: Str, bytes: Bytes, list: AList, dict: ADict, defaultdict: DefaultDict, set: ASet, tuple: ATuple, collections.abc.Callable: ACallable, } def generate_member_from_type_or_generic( type_generic: Any, default: Any, annotate_type_containers: int ) -> Member: """Generate a member from a type or generic alias.""" types: tuple[type, ...] origin = get_origin(type_generic) # For final we create the member corresponding to the inner type and set # the access mode as ReadOnly if origin is Final: member = generate_member_from_type_or_generic( get_args(type_generic)[0], default, annotate_type_containers ) member.set_setattr_mode(SetAttr.ReadOnly, None) return member # Here we special case Literal to generate an Enum member. elif origin is Literal: types = () else: types = extract_types(type_generic) parameters = get_args(type_generic) m_kwargs = {} m_cls: Type[Member] if any( isinstance(t, type) and issubclass(t, Member) for t in types ) and not isinstance(default, Member): raise ValueError( "Member subclasses cannot be used as annotations without " "specifying a default value for the attribute." ) elif object in types or Any in types: m_cls = Value parameters = () # We are dealing with a Literal, so use an Enum member elif not types: m_cls = Enum if default is not _NO_DEFAULT: if default not in parameters: raise ValueError( f"Default value {default} does not appear in Literal: {parameters}" ) # Make the default value the first in the enum arguments. p = list(parameters) p.pop(p.index(default)) parameters = (default, *p) default = _NO_DEFAULT # Int, Float, Str, Bytes, List, Dict, Set, Tuple, Bool, Callable elif len(types) == 1 and types[0] in _TYPE_TO_MEMBER: t = types[0] m_cls = _TYPE_TO_MEMBER[t] if annotate_type_containers and t in ( list, dict, collections.defaultdict, set, tuple, ): if t is tuple: if (...) in parameters: parameters = (parameters[0],) else: m_cls = FixedTuple parameters = tuple( generate_member_from_type_or_generic( t, _NO_DEFAULT, annotate_type_containers - 1 ) if t not in (Any, object) else Value() for t in parameters ) else: parameters = () # The value was annotated with Type[T] so we use a subclass elif all(t is type for t in types): m_cls = Subclass assert len(parameters) == 1 parameters = extract_types(parameters[0]) else: # Only a metaclass can implement __instancecheck__ so we check for an # implementation differing from type.__instancecheck__ and use Instance # if we find one and otherwise Typed. opt, filtered_types = is_optional(types) if ( len(filtered_types) == 1 and type(filtered_types[0]).__instancecheck__ is type.__instancecheck__ ): m_cls = Typed else: m_cls = Instance parameters = (filtered_types,) m_kwargs["optional"] = opt if opt and default not in (_NO_DEFAULT, None): raise ValueError( "Members requiring Instance(optional=True) cannot have " "a non-None default value." ) elif not opt and default is not _NO_DEFAULT: raise ValueError("Members requiring Instance cannot have a default value.") # Instance does not have a default keyword so turn a None default into the # equivalent no default. default = _NO_DEFAULT if default is not _NO_DEFAULT: m_kwargs["default"] = default return m_cls(*parameters, **m_kwargs) def generate_members_from_cls_namespace( cls_name: str, namespace: MutableMapping[str, Any], annotate_type_containers: int ) -> None: """Generate the member corresponding to a type annotation.""" # On 3.14 use annotationlib # cf https://docs.python.org/3.14/library/annotationlib.html if sys.version_info >= (3, 14): import annotationlib if "__annotations__" in namespace: annotations = namespace["__annotations__"] else: annotate = annotationlib.get_annotate_from_class_namespace(namespace) if annotate is None: annotations = {} else: annotations = annotationlib.call_annotate_function( annotate, format=annotationlib.Format.FORWARDREF ) else: annotations = namespace.get("__annotations__", {}) # XXX handle forward refs for name, ann in annotations.items(): default = namespace.get(name, _NO_DEFAULT) # We skip field for which a member was already provided or annotations # corresponding to class variables. if isinstance(default, (Member, set_default)): # Allow string annotations for members if isinstance(ann, str): continue types = extract_types(ann) if len(types) != 1 or not issubclass(types[0], Member): raise TypeError( f"Field '{name}' of '{cls_name}' is assigned a Member-like value " "but its annotation is not Member compatible" ) continue # We also skip fields annotated as class variables. elif getattr(ann, "__origin__", None) is ClassVar: continue try: namespace[name] = generate_member_from_type_or_generic( ann, default, annotate_type_containers ) except ValueError as e: raise ValueError( "Encountered an issue when generating a member for field " f"'{name}' of '{cls_name}'." ) from e atom-0.12.1/atom/meta/atom_meta.py000066400000000000000000000461071506756731600167710ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2023-2024, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Metaclass implementing atom members customization.""" import copyreg import warnings from types import FunctionType from typing import ( Any, Callable, Dict, FrozenSet, List, Mapping, MutableMapping, Optional, Sequence, Set, Tuple, TypeVar, Union, ) from ..catom import ( CAtom, DefaultValue, GetState, Member, PostGetAttr, PostSetAttr, PostValidate, Validate, ) from .annotation_utils import generate_members_from_cls_namespace from .member_modifiers import set_default from .observation import ExtendedObserver, ObserveHandler OBSERVE_PREFIX = "_observe_" DEFAULT_PREFIX = "_default_" VALIDATE_PREFIX = "_validate_" POST_GETATTR_PREFIX = "_post_getattr_" POST_SETATTR_PREFIX = "_post_setattr_" POST_VALIDATE_PREFIX = "_post_validate_" GETSTATE_PREFIX = "_getstate_" M = TypeVar("M", bound=Member) def add_member(cls: "AtomMeta", name: str, member: Member) -> None: """Add or override a member after the class creation.""" existing = cls.__atom_members__.get(name) if existing is not None: member.set_index(existing.index) member.copy_static_observers(existing) else: member.set_index(len(cls.__atom_members__)) member.set_name(name) # The dict is mutable but we do not want to say it too loud cls.__atom_members__[name] = member # type: ignore cls.__atom_specific_members__ = frozenset( set(cls.__atom_specific_members__) | {name} ) setattr(cls, name, member) def clone_if_needed(cls: "AtomMeta", member: M) -> M: """Clone a member if is not owned by a class. This function is meant to be used in __init__subclass__ to safely customize members static behaviors. """ members = dict(cls.__atom_members__) specific_members = set(cls.__atom_specific_members__) # This may lead to cloning some members that do not need to be but it # should not be too costly owned_members = {members[k] for k in cls.__atom_specific_members__} m = _clone_if_needed(member, members, specific_members, owned_members) setattr(cls, m.name, m) cls.__atom_members__ = members cls.__atom_specific_members__ = frozenset(specific_members) return m class MissingMemberWarning(UserWarning): """Signal an expected member is not present.""" pass def _signal_missing_member( owner: str, method: str, members: Mapping[str, Member], prefix: str ) -> None: warnings.warn( f"{prefix} method {method} on class '{owner}' does not match any member " f"defined on the Atom object. Existing members are: {members}", MissingMemberWarning, stacklevel=3, ) def _compute_mro(bases: Sequence[type]) -> List[type]: """Compute the MRO from a sequence of base classes using the C3 algorithm. Adapted from https://www.python.org/download/releases/2.3/mro/. """ sequences = [b.mro() for b in bases] computed_mro: List[type] = [] while True: sequences = list(filter(None, sequences)) if not sequences: return computed_mro # Look for candidates in seq heads candidate: Optional[type] for s1 in sequences: candidate = s1[0] # Discard candidate if it appears in the tail of any of the sequence if any(candidate in s[1:] for s in sequences): candidate = None else: break if candidate is None: raise RuntimeError("Inconsistent hierarchy") computed_mro.append(candidate) # Remove the candidate from all sequences for seq in sequences: if seq[0] is candidate: del seq[0] def _clone_if_needed( member: M, members: Dict[str, Member], specific_members: Set[str], owned_members: Set[Member], ) -> M: """Clone a member if it cannot be safely modified.""" # The member may have been cloned due to slot conflicts but that # does not make it specific. However, each member on which function # is called is guaranteed to be specific. specific_members.add(member.name) if member not in owned_members: member = member.clone() members[member.name] = member owned_members.add(member) return member class _AtomMetaHelper: """Helper class to create a Atom class.""" #: All members that can be accessed from the class we are helping create. members: MutableMapping[str, Member] #: The set of members which live on this class as opposed to a #: base class. This enables the code which hooks up the various #: static behaviors to only clone a member when necessary. owned_members: Set[Member] #: Names of the members whose behavior is specific to this class. specific_members: Set[str] #: Decorated observers: @observe decorated: List[ObserveHandler] #: The set of seen @observe decorators seen_decorated: Set[ObserveHandler] #: set_default() sentinel set_defaults: List[set_default] #: Static observer methods: _observe_* observes: List[str] #: Default value methods: _default_* defaults: List[str] #: Validator methods: _validate_* validates: List[str] #: Post getattr methods: _post_getattr_* post_getattrs: List[str] #: Post setattr methods: _post_setattr_* post_setattrs: List[str] #: Post validate methods: _post_validate_* post_validates: List[str] # Getstate methods: _getstate_* getstates: List[str] __slots__ = ( "bases", "dct", "decorated", "defaults", "getstates", "members", "name", "observes", "owned_members", "post_getattrs", "post_setattrs", "post_validates", "seen_decorated", "set_defaults", "specific_members", "validates", ) def __init__(self, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> None: self.name = name self.bases = bases self.dct = dct self.members = {} self.owned_members = set() self.specific_members = set() self.observes = [] self.defaults = [] self.validates = [] self.decorated = [] self.set_defaults = [] self.post_getattrs = [] self.post_setattrs = [] self.post_validates = [] self.getstates = [] self.seen_decorated = set() def scan_and_clear_namespace(self) -> None: """Collect information necessary to implement the various behaviors. Some objects declared on the class only serve as sentinels, and they are removed from the dict before creating the class. """ dct = self.dct seen_sentinels = set() # The set of seen sentinels for key, value in dct.items(): if isinstance(value, set_default): if value in seen_sentinels: value = value.clone() value.name = key self.set_defaults.append(value) seen_sentinels.add(value) continue if isinstance(value, ObserveHandler): if value in self.seen_decorated: value = value.clone() self.seen_decorated.add(value) self.decorated.append(value) value.funcname = key value = value.func dct[key] = value # Coninue processing the unwrapped function if isinstance(value, FunctionType): if key.startswith(OBSERVE_PREFIX): self.observes.append(key) elif key.startswith(DEFAULT_PREFIX): self.defaults.append(key) elif key.startswith(VALIDATE_PREFIX): self.validates.append(key) elif key.startswith(POST_VALIDATE_PREFIX): self.post_validates.append(key) elif key.startswith(POST_GETATTR_PREFIX): self.post_getattrs.append(key) elif key.startswith(POST_SETATTR_PREFIX): self.post_setattrs.append(key) elif key.startswith(GETSTATE_PREFIX): self.getstates.append(key) # Remove the sentinels from the dict before creating the class. # The sentinels for the @observe decorators are already removed. for s in seen_sentinels: assert s.name del dct[s.name] def assign_members_indexes(self) -> None: """Walk the MRO to assign a unique index to each member.""" # Compute the class MRO. cls_mro = _compute_mro(self.bases) # Walk the mro of the class, excluding itself, in reverse order # collecting all of the members into a single dict. The reverse # update preserves the mro of overridden members. We use only known # specific members to also preserve the mro in presence of multiple # inheritance. members = self.members for base in reversed(cls_mro[:-1]): if base is not CAtom and issubclass(base, CAtom): # Except if somebody abuses the system and create a non-Atom subclass # of CAtom, this works # Mypy does not narrow the type from the above test hence the ignores members.update( { k: v for k, v in base.__atom_members__.items() # type: ignore if k in base.__atom_specific_members__ # type: ignore } ) # Resolve any conflicts with memory layout. Conflicts can occur # with multiple inheritance where the indices of multiple base # classes will overlap. When this happens, the members which # conflict must be cloned in order to occupy a unique index. conflicts = [] occupied = set() for member in members.values(): if member.index in occupied: conflicts.append(member) else: occupied.add(member.index) # Track the first available index i = 0 def get_first_free_index() -> int: nonlocal i while i in occupied: i += 1 occupied.add(i) return i # Clone the conflicting members and give them a unique index. # Do not blow away an overridden item on the current class. owned_members = self.owned_members cloned = {} for member in conflicts: clone = member.clone() clone.set_index(get_first_free_index()) owned_members.add(clone) members[clone.name] = clone if clone.name not in self.dct: cloned[clone.name] = clone # Walk the dict a second time to collect the class members. This # assigns the name and the index to the member. If a member is # overriding an existing member, the memory index of the old # member is reused and any static observers are copied over. specific_members = self.specific_members for key, value in self.dct.items(): if isinstance(value, Member): if value in owned_members: # foo = bar = Baz() value = value.clone() self.dct[key] = value value.set_name(key) owned_members.add(value) specific_members.add(value.name) if key in members: supermember = members[key] members[key] = value value.set_index(supermember.index) value.copy_static_observers(supermember) else: value.set_index(get_first_free_index()) members[key] = value # Ensure we have a contiguous array for members assert occupied == set(range(len(members))) # Add cloned members to the class dictionary self.dct.update(cloned) def apply_members_static_behaviors(self) -> None: """Add the special statically defined behaviors for the members. If the target member is defined on a subclass, it is cloned so that the behavior of the subclass is not modified. """ members = self.members def clone_if_needed(m): m = _clone_if_needed(m, members, self.specific_members, self.owned_members) self.dct[m.name] = m return m # set_default() sentinels for sd in self.set_defaults: assert sd.name # At this point the name has been set if sd.name not in members: msg = "Invalid call to set_default(). '%s' is not a member " msg += "on the '%s' class." raise TypeError(msg % (sd.name, self.name)) member = clone_if_needed(members[sd.name]) member.set_default_value_mode(DefaultValue.Static, sd.value) # _default_* methods for prefix, method_names, mode_setter in [ ( DEFAULT_PREFIX, self.defaults, lambda m, name: m.set_default_value_mode( DefaultValue.ObjectMethod, name ), ), ( VALIDATE_PREFIX, self.validates, lambda m, name: m.set_validate_mode(Validate.ObjectMethod_OldNew, name), ), ( POST_VALIDATE_PREFIX, self.post_validates, lambda m, name: m.set_post_validate_mode( PostValidate.ObjectMethod_OldNew, name ), ), ( POST_GETATTR_PREFIX, self.post_getattrs, lambda m, name: m.set_post_getattr_mode( PostGetAttr.ObjectMethod_Value, name ), ), ( POST_SETATTR_PREFIX, self.post_setattrs, lambda m, name: m.set_post_setattr_mode( PostSetAttr.ObjectMethod_OldNew, name ), ), ( GETSTATE_PREFIX, self.getstates, lambda m, name: m.set_getstate_mode(GetState.ObjectMethod_Name, name), ), ]: n = len(prefix) for mangled in method_names: target = mangled[n:] if target in members: member = clone_if_needed(members[target]) mode_setter(member, mangled) else: _signal_missing_member(self.name, mangled, members, prefix) # _observe_* methods n = len(OBSERVE_PREFIX) for mangled in self.observes: target = mangled[n:] if target in members: member = clone_if_needed(members[target]) member.add_static_observer(mangled) else: _signal_missing_member(self.name, mangled, members, OBSERVE_PREFIX) # @observe decorated methods for handler in self.decorated: assert handler.funcname # Set at this point change_types = handler.change_types for name, attr in handler.pairs: if name in members: member = clone_if_needed(members[name]) observer: Union[str, Callable[..., None]] observer = handler.funcname if attr is not None: observer = ExtendedObserver(observer, attr) member.add_static_observer(observer, change_types) else: _signal_missing_member( self.name, name, members, "observe decorated" ) def create_class(self, meta: type) -> type: """Create the class after adding class variables.""" # Put a reference to the members dict on the class. This is used # by CAtom to query for the members and member count as needed. self.dct["__atom_members__"] = self.members # Keep a reference to the specific members dict on the class. Specific # members are members which are defined or altered in the class. This # is used to ensure proper MRO resolution for members. self.dct["__atom_specific_members__"] = frozenset( m for m in self.specific_members ) # Create the class object. # We do it once everything else has been setup so that if users wants # to use __init__subclass__ they have access to fully initialized # Atom type. cls: type = type.__new__(meta, self.name, self.bases, self.dct) # Generate slotnames cache # (using a private function that mypy does not know about). copyreg._slotnames(cls) # type: ignore return cls class AtomMeta(type): """The metaclass for classes derived from Atom. This metaclass computes the memory layout of the members in a given class so that the CAtom class can allocate exactly enough space for the object data slots when it instantiates an object. All classes deriving from Atom will be automatically slotted, which will prevent the creation of an instance dictionary and also the ability of an Atom to be weakly referenceable. If that behavior is required, then a subclasss should declare the appropriate slots. """ __atom_members__: Mapping[str, Member] __atom_specific_members__: FrozenSet[str] def __new__( meta, name: str, bases: Tuple[type, ...], dct: Dict[str, Any], enable_weakrefs: bool = False, use_annotations: bool = True, type_containers: int = 1, ): # Ensure there is no weird mro calculation and that we can use our # re-implementation of C3 assert meta.mro is type.mro, "Custom MRO calculation are not supported" # Unless the developer requests slots, they are automatically # turned off. This prevents the creation of instance dicts and # other space consuming features unless explicitly requested. if "__slots__" not in dct: dct["__slots__"] = () if enable_weakrefs: dct["__slots__"] += ("__weakref__",) if use_annotations: generate_members_from_cls_namespace(name, dct, type_containers) # Create the helper used to analyze the namespace and customize members helper = _AtomMetaHelper(name, bases, dct) # Analyze and clean the namespace helper.scan_and_clear_namespace() # Assign each member a unique ID helper.assign_members_indexes() # Customize the members based on the specified static modifiers helper.apply_members_static_behaviors() return helper.create_class(meta) atom-0.12.1/atom/meta/member_modifiers.py000066400000000000000000000022041506756731600203210ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2023-2024, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Custom marker objects used to modify the default settings of a member.""" from typing import Any, Optional class set_default(object): """An object used to set the default value of a base class member.""" __slots__ = ("name", "value") #: Name of the member for which a new default value should be set. Used by #: the metaclass. name: Optional[str] #: New default value to be set. value: Any def __init__(self, value: Any) -> None: self.value = value self.name = None # storage for the metaclass def clone(self) -> "set_default": """Create a clone of the sentinel.""" return type(self)(self.value) # XXX add more sentinels here to allow customizing members without using the # members themselves: # - tag # atom-0.12.1/atom/meta/observation.py000066400000000000000000000132761506756731600173570ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2023-2024, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Tools to declare static observers in Atom subclasses""" from types import FunctionType from typing import ( TYPE_CHECKING, Any, Callable, List, Mapping, Optional, Tuple, TypeVar, Union, ) from ..catom import ChangeType from ..typing_utils import ChangeDict if TYPE_CHECKING: from ..atom import Atom def observe(*names: str, change_types: ChangeType = ChangeType.ANY) -> "ObserveHandler": """A decorator which can be used to observe members on a class. Parameters ---------- *names The str names of the attributes to observe on the object. These must be of the form 'foo' or 'foo.bar'. change_types The flag specifying the type of changes to observe. """ # backwards compatibility for a single tuple or list argument if len(names) == 1 and isinstance(names[0], (tuple, list)): names = names[0] pairs: List[Tuple[str, Optional[str]]] = [] for name in names: if not isinstance(name, str): msg = "observe attribute name must be a string, got '%s' instead" raise TypeError(msg % type(name).__name__) ndots = name.count(".") if ndots > 1: msg = "cannot observe '%s', only a single extension is allowed" raise TypeError(msg % name) if ndots == 1: name, attr = name.split(".") pairs.append((name, attr)) else: pairs.append((name, None)) return ObserveHandler(pairs, change_types) T = TypeVar("T", bound="Atom") class ObserveHandler(object): """An object used to temporarily store observe decorator state.""" __slots__ = ("change_types", "func", "funcname", "pairs") #: List of 2-tuples which stores the pair information for the observers. pairs: List[Tuple[str, Optional[str]]] #: Callable to be used as observer callback. func: Optional[Callable[[Mapping[str, Any]], None]] #: Name of the callable. Used by the metaclass. funcname: Optional[str] #: Types of changes to listen to. change_types: ChangeType def __init__( self, pairs: List[Tuple[str, Optional[str]]], change_types: ChangeType = ChangeType.ANY, ) -> None: """Initialize an ObserveHandler. Parameters ---------- pairs : list The list of 2-tuples which stores the pair information for the observers. """ self.pairs = pairs self.change_types = change_types self.func = None # set by the __call__ method self.funcname = None def __call__( self, func: Union[ Callable[[ChangeDict], None], Callable[[T, ChangeDict], None], # AtomMeta will replace ObserveHandler in the body of an atom # class allowing to access it for example in a subclass. We lie here by # giving ObserverHandler.__call__ a signature compatible with an # observer to mimic this behavior. ChangeDict, ], ) -> "ObserveHandler": """Called to decorate the function. Parameters ---------- func Should be either a callable taking as single argument the change dictionary or a method declared on an Atom object. """ assert isinstance(func, FunctionType), "func must be a function" self.func = func return self def clone(self) -> "ObserveHandler": """Create a clone of the sentinel.""" clone = type(self)(self.pairs, self.change_types) clone.func = self.func return clone class ExtendedObserver(object): """A callable object used to implement extended observers.""" __slots__ = ("attr", "funcname") #: Name of the function on the owner object which should be used as the observer. funcname: str #: Attribute name on the target object which should be observed. attr: str def __init__(self, funcname: str, attr: str) -> None: """Initialize an ExtendedObserver. Parameters ---------- funcname : str The function name on the owner object which should be used as the observer. attr : str The attribute name on the target object which should be observed. """ self.funcname = funcname self.attr = attr def __call__(self, change: ChangeDict) -> None: """Handle a change of the target object. This handler will remove the old observer and attach a new observer to the target attribute. If the target object is not an Atom object, an exception will be raised. """ from ..atom import Atom old = None new = None ctype = change["type"] if ctype == "create": new = change["value"] elif ctype == "update": old = change["oldvalue"] new = change["value"] elif ctype == "delete": old = change["value"] attr = self.attr owner = change["object"] handler = getattr(owner, self.funcname) if isinstance(old, Atom): old.unobserve(attr, handler) if isinstance(new, Atom): new.observe(attr, handler) elif new is not None: msg = "cannot attach observer '%s' to non-Atom %s" raise TypeError(msg % (attr, new)) atom-0.12.1/atom/property.py000066400000000000000000000111441506756731600157520ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from .catom import DelAttr, GetAttr, GetState, Member, SetAttr, reset_property class Property(Member): """A Member which behaves similar to a Python property.""" __slots__ = () def __init__(self, fget=None, fset=None, fdel=None, cached=False): """Initialize a Property member. Parameters ---------- fget : callable or None, optional The callable invoked to get the property value. It must accept a single argument which is the owner object. If not provided, the property cannot be read. The default is None. fset : callable or None, optional The callable invoked to set the property value. It must accept two arguments: the owner object and property value. If not provided, the property cannot be set. The default is None. fdel : callable or None, optional The callable invoked to delete the property value. It must accept a single argument which is the owner object. If not provided, the property cannot be deleted. The default is None. cached : bool, optional Whether or not the property caches the computed value. A cached property will only evaluate 'fget' once until the 'reset' method of the property is invoked. The default is False. """ gm = GetAttr.CachedProperty if cached else GetAttr.Property self.set_getattr_mode(gm, fget) if cached and fset is not None: raise ValueError( "Cached property are read-only, but a setter was specified." ) self.set_setattr_mode(SetAttr.Property, fset) self.set_delattr_mode(DelAttr.Property, fdel) self.set_getstate_mode(GetState.Property, None) @property def fget(self): """Get the getter function for the property. This will not find a specially named _get_* function. """ return self.getattr_mode[1] @property def fset(self): """Get the setter function for the property. This will not find a specially named _set_* function. """ return self.setattr_mode[1] @property def fdel(self): """Get the deleter function for the property. This will not find a specially named _del_* function. """ return self.delattr_mode[1] @property def cached(self): """Test whether or not this is a cached property.""" return self.getattr_mode[0] == GetAttr.CachedProperty def getter(self, func): """Use the given function as the property getter. This method is intended to be used as a decorator. The original function will still be callable. """ mode, _ignored = self.getattr_mode self.set_getattr_mode(mode, func) return func def setter(self, func): """Use the given function as the property setter. This method is intended to be used as a decorator. The original function will still be callable. """ if self.cached: raise ValueError( "Cached property are read-only, but a setter was specified." ) self.set_setattr_mode(SetAttr.Property, func) return func def deleter(self, func): """Use the given function as the property deleter. This method is intended to be used as a decorator. The original function will still be callable. """ self.set_delattr_mode(DelAttr.Property, func) return func def reset(self, owner): """Reset the value of the property. The old property value will be cleared and the notifiers will be run if the new value is different from the old value. If the property is not cached, notifiers will be unconditionally run using None as the old value. """ reset_property(self, owner) def cached_property(fget): """A decorator which converts a function into a cached Property. Parameters ---------- fget : callable The callable invoked to get the property value. It must accept a single argument which is the owner object. """ return Property(fget, cached=True) atom-0.12.1/atom/property.pyi000066400000000000000000000040521506756731600161230ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from typing import Callable, NoReturn, Optional, TypeVar, overload from .atom import Atom from .catom import Member A = TypeVar("A", bound=Atom) T = TypeVar("T") S = TypeVar("S") class Property(Member[T, S]): @overload def __new__( cls, fget: None = None, fset: None = None, fdel: Optional[Callable[[A], None]] = None, cached: bool = False, ) -> Property[NoReturn, NoReturn]: ... @overload def __new__( cls, fget: None, fset: Callable[[A, S], None], fdel: Optional[Callable[[A], None]] = None, cached: bool = False, ) -> Property[NoReturn, S]: ... @overload def __new__( cls, fget: Callable[[A], T], fset: None = None, fdel: Optional[Callable[[A], None]] = None, cached: bool = False, ) -> Property[T, NoReturn]: ... @overload def __new__( cls, fget: Callable[[A], T], fset: Callable[[A, S], None], fdel: Optional[Callable[[A], None]] = None, cached: bool = False, ) -> Property[T, S]: ... @property def fget(self) -> Optional[Callable[[Atom], T]]: ... @property def fset(self) -> Optional[Callable[[Atom, S], None]]: ... @property def fdel(self) -> Optional[Callable[[Atom], None]]: ... @property def cached(self) -> bool: ... def getter(self, func: Callable[[A], T]) -> Callable[[A], T]: ... def setter(self, func: Callable[[A, S], None]) -> Callable[[A, S], None]: ... def deleter(self, func: Callable[[A], None]) -> Callable[[A], None]: ... def reset(self, owner: Atom) -> None: ... def cached_property(fget: Callable[[A], T]) -> Property[T, NoReturn]: ... atom-0.12.1/atom/py.typed000066400000000000000000000000001506756731600152000ustar00rootroot00000000000000atom-0.12.1/atom/scalars.py000066400000000000000000000147021506756731600155210ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from .catom import DefaultValue, DelAttr, GetState, Member, SetAttr, Validate from .typing_utils import extract_types class Value(Member): """A member class which supports value initialization. A plain `Value` provides support for default values and factories, but does not perform any type checking or validation. It serves as a useful base class for scalar members and can be used for cases where type checking is not needed (like private attributes). """ __slots__ = () def __init__(self, default=None, *, factory=None): """Initialize a Value. Parameters ---------- default : object, optional The default value for the member. If this is provided, it should be an immutable value. The value will will not be copied between owner instances. factory : callable, optional A callable object which is called with zero arguments and returns a default value for the member. This will override any value given by `default`. """ if factory is not None: self.set_default_value_mode(DefaultValue.CallObject, factory) else: self.set_default_value_mode(DefaultValue.Static, default) class ReadOnly(Value): """A value which can be assigned once and is then read-only.""" __slots__ = () def __init__(self, kind=None, *, default=None, factory=None): super(ReadOnly, self).__init__(default, factory=factory) self.set_setattr_mode(SetAttr.ReadOnly, None) self.set_delattr_mode(DelAttr.ReadOnly, None) self.set_getstate_mode(GetState.IncludeNonDefault, None) if kind: self.set_validate_mode(Validate.Instance, extract_types(kind)) class Constant(Value): """A value which cannot be changed from its default.""" __slots__ = () def __init__(self, default=None, *, factory=None, kind=None): super(Constant, self).__init__(default, factory=factory) self.set_setattr_mode(SetAttr.Constant, None) self.set_delattr_mode(DelAttr.Constant, None) self.set_getstate_mode(GetState.Exclude, None) if kind: self.set_validate_mode(Validate.Instance, extract_types(kind)) class Callable(Value): """A value which is callable.""" __slots__ = () def __init__(self, default=None, *, factory=None): super(Callable, self).__init__(default, factory=factory) self.set_validate_mode(Validate.Callable, None) class Bool(Value): """A value of type `bool`.""" __slots__ = () def __init__(self, default=False, *, factory=None): super(Bool, self).__init__(default, factory=factory) self.set_validate_mode(Validate.Bool, None) class Int(Value): """A value of type `int`. By default, ints are strictly typed. Pass strict=False to the constructor to enable int casting for longs and floats. """ __slots__ = () def __init__(self, default=0, *, factory=None, strict=True): super(Int, self).__init__(default, factory=factory) if strict: self.set_validate_mode(Validate.Int, None) else: self.set_validate_mode(Validate.IntPromote, None) class FloatRange(Value): """A float value clipped to a range. By default, ints and longs will be promoted to floats. Pass strict=True to the constructor to enable strict float checking. """ __slots__ = () def __init__(self, low=None, high=None, value=None, *, strict=False): if low is not None and high is not None and low > high: low, high = high, low default = 0.0 if value is not None: default = value elif low is not None: default = low elif high is not None: default = high super(FloatRange, self).__init__(default) if strict: self.set_validate_mode(Validate.FloatRange, (low, high)) else: if low is not None: low = float(low) if high is not None: high = float(high) self.set_validate_mode(Validate.FloatRangePromote, (low, high)) class Range(Value): """An integer value clipped to a range.""" __slots__ = () def __init__(self, low=None, high=None, value=None): if low is not None and high is not None and low > high: low, high = high, low default = 0 if value is not None: default = value elif low is not None: default = low elif high is not None: default = high super(Range, self).__init__(default) self.set_validate_mode(Validate.Range, (low, high)) class Float(Value): """A value of type `float`. By default, ints and longs will be promoted to floats. Pass strict=True to the constructor to enable strict float checking. """ __slots__ = () def __init__(self, default=0.0, *, factory=None, strict=False): super(Float, self).__init__(default, factory=factory) if strict: self.set_validate_mode(Validate.Float, None) else: self.set_validate_mode(Validate.FloatPromote, None) class Bytes(Value): """A value of type `bytes`. By default, strings will NOT be promoted to bytes. Pass strict=False to the constructor to enable loose byte checking. """ __slots__ = () def __init__(self, default=b"", *, factory=None, strict=True): super(Bytes, self).__init__(default, factory=factory) if strict: self.set_validate_mode(Validate.Bytes, None) else: self.set_validate_mode(Validate.BytesPromote, None) class Str(Value): """A value of type `str`. By default, bytes will NOT be promoted to strings. Pass strict=False to the constructor to enable loose string checking. """ def __init__(self, default="", *, factory=None, strict=True): super(Str, self).__init__(default, factory=factory) if strict: self.set_validate_mode(Validate.Str, None) else: self.set_validate_mode(Validate.StrPromote, None) atom-0.12.1/atom/scalars.pyi000066400000000000000000000151131506756731600156670ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from typing import ( Any, Callable as TCallable, Literal, NoReturn, Optional, Tuple, Type, TypeVar, Union, overload, ) from .catom import Member T = TypeVar("T") T1 = TypeVar("T1") T2 = TypeVar("T2") S = TypeVar("S") class Value(Member[T, T]): def __new__( cls, default: Any = None, *, factory: Optional[TCallable[[], Any]] = None ) -> Value[Any]: ... class ReadOnly(Member[T, T]): @overload def __new__( cls, kind: None = None, *, default: None = None, factory: None = None ) -> ReadOnly[Any]: ... @overload def __new__( cls, kind: None = None, *, default: T, factory: None = None ) -> ReadOnly[T]: ... @overload def __new__( cls, kind: None = None, *, default: None = None, factory: TCallable[[], T] ) -> ReadOnly[T]: ... @overload def __new__( cls, kind: Type[T], *, default: Optional[T] = None, factory: Optional[TCallable[[], T]] = None, ) -> ReadOnly[T]: ... @overload def __new__( cls, kind: Tuple[Type[T]], *, default: Optional[T] = None, factory: Optional[TCallable[[], T]] = None, ) -> ReadOnly[T]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1]], *, default: Optional[T | T1] = None, factory: Optional[TCallable[[], T | T1]] = None, ) -> ReadOnly[T | T1]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1], Type[T2]], *, default: Optional[T | T1 | T2] = None, factory: Optional[TCallable[[], T | T1 | T2]] = None, ) -> ReadOnly[T | T1 | T2]: ... class Constant(Member[T, NoReturn]): # FIXME over-write del ? @overload def __new__( cls, default: None = None, *, factory: None = None, kind: None = None ) -> Constant[Any]: ... @overload def __new__( cls, default: None = None, *, factory: TCallable[[], T], kind: None = None ) -> Constant[T]: ... @overload def __new__( cls, default: T, *, factory: None = None, kind: None = None ) -> Constant[T]: ... @overload def __new__( cls, default: Optional[T] = None, *, factory: Optional[TCallable[[], T]] = None, kind: Type[T], ) -> Constant[T]: ... @overload def __new__( cls, default: Optional[T] = None, *, factory: Optional[TCallable[[], T]] = None, kind: Tuple[Type[T]], ) -> Constant[T]: ... @overload def __new__( cls, default: Optional[T | T1] = None, *, factory: Optional[TCallable[[], T | T1]] = None, kind: Tuple[Type[T], Type[T1]], ) -> Constant[T | T1]: ... @overload def __new__( cls, default: Optional[T | T1 | T2] = None, *, factory: Optional[TCallable[[], T | T1 | T2]] = None, kind: Tuple[Type[T], Type[T1], Type[T2]], ) -> Constant[T | T1 | T2]: ... C = TypeVar("C", bound=TCallable[..., Any]) class Callable(Member[T, T]): @overload def __new__( cls, default: None = None, *, factory: TCallable[[], C] ) -> Callable[C]: ... @overload def __new__(cls, default: C, *, factory: None = None) -> Callable[C]: ... @overload def __new__( cls, default: None = None, *, factory: None = None ) -> Callable[TCallable[..., Any]]: ... class Bool(Member[bool, T]): def __new__( cls, default: bool = False, *, factory: Optional[TCallable[[], bool]] = None ) -> Bool[bool]: ... class Int(Member[int, T]): @overload def __new__( cls, default: int = 0, *, factory: Optional[TCallable[[], int]] = None, strict: Literal[True] = True, ) -> Int[int]: ... @overload def __new__( cls, default: Union[int, float] = 0, *, factory: Optional[TCallable[[], Union[int, float]]] = None, strict: Literal[False], ) -> Int[Union[int, float]]: ... # NOTE this cannot be properly statically typed checked since Mypy will always accept # an int where a float is expected class FloatRange(Member[float, T]): @overload def __new__( cls, low: Optional[float] = None, high: Optional[float] = None, value: Optional[float] = None, *, strict: Literal[False] = False, ) -> FloatRange[int | float]: ... @overload def __new__( cls, low: Optional[float] = None, high: Optional[float] = None, value: Optional[float] = None, *, strict: Literal[True], ) -> FloatRange[float]: ... class Range(Member[int, T]): def __new__( cls, low: Optional[int] = None, high: Optional[int] = None, value: Optional[int] = None, ) -> Range[int]: ... class Float(Member[float, T]): @overload def __new__( cls, default: float = 0.0, *, factory: Optional[TCallable[[], Union[int, float]]] = None, strict: Literal[False] = False, ) -> Float[Union[int, float]]: ... @overload def __new__( cls, default: float = 0.0, *, factory: Optional[TCallable[[], float]] = None, strict: Literal[True], ) -> Float[float]: ... # FIXME we cannot encode that an int will be rejected class Bytes(Member[bytes, T]): @overload def __new__( cls, default: bytes = b"", *, factory: Optional[TCallable[[], bytes]] = None, strict: Literal[True] = True, ) -> Bytes[bytes]: ... @overload def __new__( cls, default: Union[str, bytes] = b"", *, factory: Optional[TCallable[[], Union[str, bytes]]] = None, strict: Literal[False], ) -> Bytes[Union[bytes, str]]: ... class Str(Member[str, T]): @overload def __new__( cls, default: str = "", *, factory: Optional[TCallable[[], str]] = None, strict: Literal[True] = True, ) -> Str[str]: ... @overload def __new__( cls, default: Union[str, bytes] = "", *, factory: Optional[TCallable[[], Union[str, bytes]]] = None, strict: Literal[False], ) -> Str[Union[str, bytes]]: ... atom-0.12.1/atom/set.py000066400000000000000000000055641506756731600146720ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2019-2024, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from .catom import DefaultValue, Member, Validate from .instance import Instance from .typing_utils import extract_types, is_optional class Set(Member): """A member which allows set values. Assigning to a set creates a copy. The original set will remain unmodified. This is similar to the semantics of the assignment operator on the C++ STL container classes. """ __slots__ = "item" def __init__(self, item=None, default=None): """Initialize a Set. Parameters ---------- item : Member, type, or tuple of types, optional A member to use for validating the types of items allowed in the set. This can also be a type object or a tuple of types, in which case it will be wrapped with an Instance member. If this is not given, no item validation is performed. default : list, optional The default list of values. A new copy of this list will be created for each atom instance. """ self.set_default_value_mode(DefaultValue.Set, default) if item is not None and not isinstance(item, Member): opt, types = is_optional(extract_types(item)) item = Instance(types, optional=opt) self.item = item self.set_validate_mode(Validate.Set, item) def set_name(self, name): """Assign the name to this member. This method is called by the Atom metaclass when a class is created. This makes sure the name of the internal members are also updated. """ super(Set, self).set_name(name) item = self.item if item is not None: item.set_name(name + "|item") def set_index(self, index): """Assign the index to this member. This method is called by the Atom metaclass when a class is created. This makes sure the index of the internal members are also updated. """ super(Set, self).set_index(index) item = self.item if item is not None: item.set_index(index) def clone(self): """Create a clone of the member. This will clone the internal set item members if they exist. """ clone = super(Set, self).clone() item = self.item if item is not None: clone.item = item_clone = item.clone() mode, _ctxt = self.validate_mode clone.set_validate_mode(mode, item_clone) else: clone.item = None return clone atom-0.12.1/atom/set.pyi000066400000000000000000000045011506756731600150310ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from typing import Any, Optional, Set as TSet, Tuple, Type, TypeVar, overload from .catom import Member T = TypeVar("T") T1 = TypeVar("T1") T2 = TypeVar("T2") class Set(Member[TSet[T], TSet[T]]): @overload def __new__( cls, item: None = None, default: Optional[TSet[Any]] = None ) -> Set[Any]: ... @overload def __new__(cls, item: Type[T], default: None = None) -> Set[T]: ... @overload def __new__(cls, item: Tuple[Type[T]], default: None = None) -> Set[T]: ... @overload def __new__( cls, item: Tuple[Type[T], Type[T1]], default: None = None ) -> Set[T | T1]: ... @overload def __new__( cls, item: Tuple[Type[T], Type[T1], Type[T2]], default: None = None, ) -> Set[T | T1 | T2]: ... @overload def __new__(cls, item: Member[T, Any], default: None = None) -> Set[T]: ... # With default # The splitting is necessary otherwise Mypy type inference fails @overload def __new__(cls, item: Type[T], default: TSet[T]) -> Set[T]: ... @overload def __new__(cls, item: Tuple[Type[T]], default: TSet[T]) -> Set[T]: ... @overload def __new__( cls, item: Tuple[Type[T], Type[T1]], default: TSet[T | T1] ) -> Set[T | T1]: ... @overload def __new__( cls, item: Tuple[Type[T], Type[T1]], default: TSet[T] | TSet[T1] ) -> Set[T | T1]: ... @overload def __new__( cls, item: Tuple[Type[T], Type[T1], Type[T2]], default: TSet[T | T1 | T2], ) -> Set[T | T1 | T2]: ... @overload def __new__( cls, item: Tuple[Type[T], Type[T1], Type[T2]], default: TSet[T | T1] | TSet[T | T2] | TSet[T1 | T2], ) -> Set[T | T1 | T2]: ... @overload def __new__( cls, item: Tuple[Type[T], Type[T1], Type[T2]], default: TSet[T] | TSet[T1] | TSet[T2], ) -> Set[T | T1 | T2]: ... @overload def __new__(cls, item: Member[T, Any], default: TSet[T]) -> Set[T]: ... atom-0.12.1/atom/signal.py000066400000000000000000000013401506756731600153400ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from .catom import DelAttr, GetAttr, Member, SetAttr class Signal(Member): """A member which acts similar to a Qt signal.""" __slots__ = () def __init__(self): """Initialize a Signal.""" self.set_getattr_mode(GetAttr.Signal, None) self.set_setattr_mode(SetAttr.Signal, None) self.set_delattr_mode(DelAttr.Signal, None) atom-0.12.1/atom/signal.pyi000066400000000000000000000007571506756731600155240ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from typing import NoReturn from .catom import Member, SignalConnector class Signal(Member[SignalConnector, NoReturn]): ... atom-0.12.1/atom/src/000077500000000000000000000000001506756731600143025ustar00rootroot00000000000000atom-0.12.1/atom/src/atomdict.cpp000066400000000000000000000303451506756731600166170ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2014-2024,, Nucleic | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #include #include #include #include "atomdict.h" #include "packagenaming.h" namespace atom { namespace { inline bool should_validate( AtomDict* dict ) { return dict->pointer->data() && ( pyobject_cast( dict->m_key_validator ) != Py_None || pyobject_cast( dict->m_value_validator ) != Py_None ); } PyObject* validate_key( AtomDict* dict, PyObject* key ) { CAtom* atom = dict->pointer->data(); Member* key_val = dict->m_key_validator; cppy::ptr key_item( cppy::incref( key ) ); if( key_val && atom ) { key_item = key_val->full_validate( atom, Py_None, key_item.get() ); if( !key_item ) return 0; } return key_item.release(); } PyObject* validate_value( AtomDict* dict, PyObject* value ) { CAtom* atom = dict->pointer->data(); Member* value_val = dict->m_value_validator; cppy::ptr value_item( cppy::incref( value ) ); if( value_val && atom ) { value_item = value_val->full_validate( atom, Py_None, value_item.get() ); if( !value_item ) return 0; } return value_item.release(); } int merge_items( PyObject* dict, PyObject* item, PyObject* kwargs ) { int ok = 0; if( item ) { if( PyObject_HasAttrString( item, "keys" ) ) { ok = PyDict_Merge( dict, item, 1 ); } else { ok = PyDict_MergeFromSeq2( dict, item, 1 ); } } if( ok == 0 && kwargs ) { ok = PyDict_Merge( dict, kwargs, 1 ); } return ok; } PyObject* AtomDict_new( PyTypeObject* type, PyObject* args, PyObject* kwargs ) { cppy::ptr self( PyDict_Type.tp_new( type, args, kwargs ) ); if( !self ) { return 0; // LCOV_EXCL_LINE (failed instance creation) } atomdict_cast( self.get() )->pointer = new CAtomPointer(); return self.release(); } int AtomDict_clear( AtomDict* self ) { Py_CLEAR( self->m_key_validator ); Py_CLEAR( self->m_value_validator ); return PyDict_Type.tp_clear( pyobject_cast( self ) ); } int AtomDict_traverse( AtomDict* self, visitproc visit, void* arg ) { Py_VISIT( self->m_key_validator ); Py_VISIT( self->m_value_validator ); #if PY_VERSION_HEX >= 0x03090000 // This was not needed before Python 3.9 (Python issue 35810 and 40217) Py_VISIT(Py_TYPE(self)); #endif // PyDict_type is not heap allocated so it does visit the type return PyDict_Type.tp_traverse( pyobject_cast( self ), visit, arg ); } void AtomDict_dealloc( AtomDict* self ) { PyObject_GC_UnTrack( self ); cppy::clear( &self->m_key_validator ); cppy::clear( &self->m_value_validator ); delete atomdict_cast( self )->pointer; atomdict_cast( self )->pointer = 0; PyDict_Type.tp_dealloc( pyobject_cast( self ) ); } int AtomDict_ass_subscript( AtomDict* self, PyObject* key, PyObject* value ) { cppy::ptr key_ptr( cppy::incref( key ) ); cppy::ptr value_ptr( cppy::xincref( value ) ); if( value && should_validate( self ) ) { key_ptr = validate_key( self, key_ptr.get() ); if( !key_ptr ) { return -1; } value_ptr = validate_value( self, value_ptr.get() ); if( !value_ptr ) { return -1; } } return PyDict_Type.tp_as_mapping->mp_ass_subscript( pyobject_cast( self ), key_ptr.get(), value_ptr.get() ); } PyObject* AtomDict_setdefault( AtomDict* self, PyObject* args ) { PyObject* key; PyObject* dfv = Py_None; if( !PyArg_UnpackTuple( args, "setdefault", 1, 2, &key, &dfv ) ) { return 0; } PyObject* value = PyDict_GetItem( pyobject_cast( self ), key ); if( value ) { return cppy::incref( value ); } if( AtomDict_ass_subscript( self, key, dfv ) < 0 ) { return 0; } // Get the dictionary from the dict itself in case it was coerced. return cppy::incref( PyDict_GetItem( pyobject_cast( self ), key ) ); } PyObject* AtomDict_update( AtomDict* dict, PyObject* args, PyObject* kwargs ) { PyObject* item = 0; if( !PyArg_UnpackTuple( args, "update", 0, 1, &item ) ) { return 0; } if( !should_validate( dict ) ) { if( merge_items( pyobject_cast( dict ), item, kwargs ) < 0 ) { return 0; } else { return cppy::incref( Py_None ); } } cppy::ptr temp( PyDict_New() ); if( !temp ) { return 0; } if( merge_items( temp.get(), item, kwargs ) < 0 ) { return 0; } if( AtomDict::Update( dict, temp.get() ) < 0 ) { return 0; } return cppy::incref( Py_None ); } static PyMethodDef AtomDict_methods[] = { { "setdefault", ( PyCFunction )AtomDict_setdefault, METH_VARARGS, "D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D" }, { "update", ( PyCFunction )AtomDict_update, METH_VARARGS | METH_KEYWORDS, "D.update([E, ]**F) -> None. Update D from dict/iterable E and F" }, { 0 } // sentinel }; static PyType_Slot AtomDict_Type_slots[] = { { Py_tp_dealloc, void_cast( AtomDict_dealloc ) }, /* tp_dealloc */ { Py_mp_ass_subscript, void_cast( AtomDict_ass_subscript ) }, /* mp_ass_subscript */ { Py_tp_traverse, void_cast( AtomDict_traverse ) }, /* tp_traverse */ { Py_tp_clear, void_cast( AtomDict_clear ) }, /* tp_clear */ { Py_tp_methods, void_cast( AtomDict_methods ) }, /* tp_methods */ { Py_tp_base, void_cast( &PyDict_Type ) }, /* tp_base */ { Py_tp_new, void_cast( AtomDict_new ) }, /* tp_new */ { 0, 0 }, }; // DefaultAtomDict int DefaultAtomDict_clear( DefaultAtomDict* self ) { Py_CLEAR( self->factory ); return AtomDict_clear( atomdict_cast( self ) ); } int DefaultAtomDict_traverse( DefaultAtomDict* self, visitproc visit, void* arg ) { Py_VISIT( self->factory ); return AtomDict_traverse( atomdict_cast( self ), visit, arg ); } void DefaultAtomDict_dealloc( DefaultAtomDict* self ) { cppy::clear( &self->factory ); AtomDict_dealloc( atomdict_cast( self ) ); } static PyObject* DefaultAtomDict_repr( DefaultAtomDict* self ) { std::ostringstream ostr; ostr << "defaultdict("; cppy::ptr repr( PyObject_Repr( pyobject_cast( self->factory ) ) ); if( !repr ) { return 0; } ostr << PyUnicode_AsUTF8( repr.get() ); ostr << ", "; repr = PyDict_Type.tp_repr( pyobject_cast( self ) ); if( !repr ) { return 0; } ostr << PyUnicode_AsUTF8( repr.get() ); ostr << ")"; return PyUnicode_FromString( ostr.str().c_str() ); } static PyObject* DefaultAtomDict_missing( DefaultAtomDict* self, PyObject* args ) { PyObject* key; if( !PyArg_UnpackTuple( args, "__missing__", 1, 1, &key ) ) { return 0; } CAtom* atom = self->dict.pointer->data(); if( !atom ) { return cppy::runtime_error( "Atom object to which this default dict is not alive anymore, " "so missing value cannot be built." ); } #if PY_VERSION_HEX >= 0x03090000 cppy::ptr value_ptr( PyObject_CallOneArg( self->factory, pyobject_cast( atom ) ) ); #else cppy::ptr temp( PyTuple_Pack(1, pyobject_cast( atom ) ) ); cppy::ptr value_ptr( PyObject_Call( self->factory, temp.get(), 0 ) ); #endif if( !value_ptr ) { return 0; } if( should_validate( atomdict_cast( self ) ) ) { // We cannot simply validate the value as it will be re-validated when // it is set which leads to creating a different object. if( AtomDict_ass_subscript( atomdict_cast( self ), key, value_ptr.get() ) < 0 ) { return 0; } // Get the dictionary from the dict itself in case it was coerced. value_ptr = cppy::incref( PyDict_GetItem( pyobject_cast( self ), key ) ); } return value_ptr.release(); } static PyMethodDef DefaultAtomDict_methods[] = { { "__missing__", ( PyCFunction )DefaultAtomDict_missing, METH_VARARGS, "Called when a key is absent from the dictionary" }, { 0 } // sentinel }; static PyType_Slot DefaultAtomDict_Type_slots[] = { { Py_tp_dealloc, void_cast( DefaultAtomDict_dealloc ) }, /* tp_dealloc */ { Py_tp_traverse, void_cast( DefaultAtomDict_traverse ) }, /* tp_traverse */ { Py_tp_clear, void_cast( DefaultAtomDict_clear ) }, /* tp_clear */ { Py_tp_repr, void_cast( DefaultAtomDict_repr ) }, /* tp_repr */ { Py_tp_methods, void_cast( DefaultAtomDict_methods ) }, /* tp_methods */ /* tp_base cannot be set at this stage */ { 0, 0 }, }; } // namespace // Initialize static variables (otherwise the compiler eliminates them) PyTypeObject* AtomDict::TypeObject = NULL; PyType_Spec AtomDict::TypeObject_Spec = { PACKAGE_TYPENAME( "atomdict" ), /* tp_name */ sizeof( AtomDict ), /* tp_basicsize */ 0, /* tp_itemsize */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_VERSION_TAG, /* tp_flags */ AtomDict_Type_slots /* slots */ }; PyObject* AtomDict::New( CAtom* atom, Member* key_validator, Member* value_validator ) { cppy::ptr self( PyDict_Type.tp_new( AtomDict::TypeObject, 0, 0 ) ); if( !self ) { return 0; } cppy::xincref( pyobject_cast( key_validator ) ); atomdict_cast( self.get() )->m_key_validator = key_validator; cppy::xincref( pyobject_cast( value_validator ) ); atomdict_cast( self.get() )->m_value_validator = value_validator; atomdict_cast( self.get() )->pointer = new CAtomPointer( atom ); return self.release(); } int AtomDict::Update( AtomDict* dict, PyObject* value ) { cppy::ptr validated_dict( PyDict_New() ); PyObject* key; PyObject* val; Py_ssize_t index = 0; while( PyDict_Next( value, &index, &key, &val ) ) { cppy::ptr key_ptr( cppy::incref( key ) ); key_ptr = validate_key( dict, key_ptr.get() ); if( !key_ptr ) { return -1; } cppy::ptr val_ptr( cppy::incref( val ) ); val_ptr = validate_value( dict, val_ptr.get() ); if( !val_ptr ) { return -1; } if( PyDict_SetItem( validated_dict.get(), key_ptr.get(), val_ptr.get() ) != 0 ) { return -1; } } if( PyDict_Update( pyobject_cast( dict ), validated_dict.get() ) < 0 ) { return -1; } return 0; } bool AtomDict::Ready() { // The reference will be handled by the module to which we will add the type TypeObject = pytype_cast( PyType_FromSpec( &TypeObject_Spec ) ); if( !TypeObject ) { return false; } return true; } // Initialize static variables (otherwise the compiler eliminates them) PyTypeObject* DefaultAtomDict::TypeObject = NULL; PyType_Spec DefaultAtomDict::TypeObject_Spec = { PACKAGE_TYPENAME( "defaultatomdict" ), /* tp_name */ sizeof( DefaultAtomDict ), /* tp_basicsize */ 0, /* tp_itemsize */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_VERSION_TAG, /* tp_flags */ DefaultAtomDict_Type_slots /* slots */ }; PyObject* DefaultAtomDict::New( CAtom* atom, Member* key_validator, Member* value_validator, PyObject* factory) { cppy::ptr self( PyDict_Type.tp_new( DefaultAtomDict::TypeObject, 0, 0 ) ); if( !self ) { return 0; // LCOV_EXCL_LINE (failed instance creation) } cppy::xincref( pyobject_cast( key_validator ) ); atomdict_cast( self.get() )->m_key_validator = key_validator; cppy::xincref( pyobject_cast( value_validator ) ); atomdict_cast( self.get() )->m_value_validator = value_validator; atomdict_cast( self.get() )->pointer = new CAtomPointer( atom ); cppy::incref( pyobject_cast( factory ) ); // XXX validate we do get a callable taking 0 arg defaultatomdict_cast( self.get() )->factory = factory; return self.release(); } bool DefaultAtomDict::Ready() { // This will work only if we create this type after the standard AtomDict // The reference will be handled by the module to which we will add the type PyObject* bases = PyTuple_New( 1 ); PyTuple_SET_ITEM( bases, 0, pyobject_cast( AtomDict::TypeObject ) ); TypeObject = pytype_cast( PyType_FromSpecWithBases( &TypeObject_Spec, bases ) ); if( !TypeObject ) { return false; // LCOV_EXCL_LINE (failed type creation) } return true; } } // namespace atom atom-0.12.1/atom/src/atomdict.h000066400000000000000000000030731506756731600162620ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2019-2024, Nucleic | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #pragma once #include #include "catom.h" #include "catompointer.h" #include "member.h" #define atomdict_cast( o ) ( reinterpret_cast( o ) ) #define defaultatomdict_cast( o ) ( reinterpret_cast( o ) ) namespace atom { // POD struct - all member fields are considered private struct AtomDict { PyDictObject dict; Member* m_key_validator; Member* m_value_validator; CAtomPointer* pointer; static PyType_Spec TypeObject_Spec; static PyTypeObject* TypeObject; static bool Ready(); static PyObject* New( CAtom* atom, Member* key_validator, Member* value_validator ); static int Update( AtomDict* dict, PyObject* value ); static bool TypeCheck( PyObject* ob ) { return PyObject_TypeCheck( ob, TypeObject ) != 0; } }; // POD struct - all member fields are considered private struct DefaultAtomDict { AtomDict dict; PyObject* factory; static PyType_Spec TypeObject_Spec; static PyTypeObject* TypeObject; static bool Ready(); static PyObject* New( CAtom* atom, Member* key_validator, Member* value_validator, PyObject* factory ); static bool TypeCheck( PyObject* ob ) { return PyObject_TypeCheck( ob, TypeObject ) != 0; } }; } // namespace atom atom-0.12.1/atom/src/atomlist.cpp000066400000000000000000001072211506756731600166450ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #include #include "atomlist.h" #include "packagenaming.h" #ifdef __clang__ #pragma clang diagnostic ignored "-Wdeprecated-writable-strings" #endif #ifdef __GNUC__ #pragma GCC diagnostic ignored "-Wwrite-strings" #endif namespace atom { typedef PyCFunction pycfunc; typedef PyCFunctionWithKeywords pycfunc_kw; typedef _PyCFunctionFast pycfunc_f; typedef _PyCFunctionFastWithKeywords pycfunc_fkw; namespace ListMethods { pycfunc extend = 0; static pycfunc_f pop = 0; static pycfunc remove = 0; inline PyCFunction lookup_method( PyTypeObject* type, const char* name ) { PyMethodDef* method = type->tp_methods; for( ; method->ml_name != 0; ++method ) { if( strcmp( method->ml_name, name ) == 0 ) return method->ml_meth; } return 0; // LCOV_EXCL_LINE (failed method lookup) } static bool init_methods() { extend = lookup_method( &PyList_Type, "extend" ); if( !extend ) { // LCOV_EXCL_START cppy::system_error( "failed to load list 'extend' method" ); return false; // LCOV_EXCL_STOP } pop = reinterpret_cast( lookup_method( &PyList_Type, "pop" ) ); if( !pop ) { // LCOV_EXCL_START cppy::system_error( "failed to load list 'pop' method" ); return false; // LCOV_EXCL_STOP } remove = lookup_method( &PyList_Type, "remove" ); if( !remove ) { // LCOV_EXCL_START cppy::system_error( "failed to load list 'remove' method" ); return false; // LCOV_EXCL_STOP } return true; } } // namespace ListMethods PyObject* ListSubtype_New( PyTypeObject* subtype, Py_ssize_t size ) { if( size < 0 ) return cppy::system_error( "negative list size" ); if( static_cast( size ) > PY_SSIZE_T_MAX / sizeof( PyObject* ) ) return PyErr_NoMemory(); // LCOV_EXCL_LINE (memory error) cppy::ptr ptr( PyType_GenericNew( subtype, 0, 0 ) ); if( !ptr ) return 0; // LCOV_EXCL_LINE (failed instance creation) PyListObject* op = reinterpret_cast( ptr.get() ); if( size > 0 ) { size_t nbytes = size * sizeof( PyObject* ); op->ob_item = reinterpret_cast( PyMem_Malloc( nbytes ) ); if( !op->ob_item ) return PyErr_NoMemory(); // LCOV_EXCL_LINE (memory error) memset( op->ob_item, 0, nbytes ); } #if PY_VERSION_HEX >= 0x03090000 Py_SET_SIZE( op, size ); #else Py_SIZE( op ) = size; #endif op->allocated = size; return ptr.release(); } /*----------------------------------------------------------------------------- | AtomList Type |----------------------------------------------------------------------------*/ namespace { class AtomListHandler { public: AtomListHandler( AtomList* list ) : m_list( cppy::incref( pyobject_cast( list ) ) ) {} PyObject* append( PyObject* value ) { cppy::ptr item( validate_single( value ) ); if( !item ) return 0; if( PyList_Append( m_list.get(), item.get() ) != 0 ) { return 0; // LCOV_EXCL_LINE (failed append, impossible) } return cppy::incref( Py_None ); } PyObject* insert( PyObject* args ) { Py_ssize_t index; PyObject* value; if( !PyArg_ParseTuple( args, "nO:insert", &index, &value ) ) return 0; cppy::ptr valptr( validate_single( value ) ); if( !valptr ) return 0; if( PyList_Insert( m_list.get(), index, valptr.get() ) != 0) { return 0; // LCOV_EXCL_LINE (failed insert, impossible) } return cppy::incref( Py_None ); } PyObject* extend( PyObject* value ) { cppy::ptr item( validate_sequence( value ) ); if( !item ) return 0; return ListMethods::extend( m_list.get(), item.get() ); } PyObject* iadd( PyObject* value ) { cppy::ptr item( validate_sequence( value ) ); if( !item ) return 0; return PyList_Type.tp_as_sequence->sq_inplace_concat( m_list.get(), item.get() ); } int setitem( Py_ssize_t index, PyObject* value ) { if( !value ) return PyList_Type.tp_as_sequence->sq_ass_item( m_list.get(), index, value ); cppy::ptr item( validate_single( value ) ); if( !item ) return -1; return PyList_Type.tp_as_sequence->sq_ass_item( m_list.get(), index, item.get() ); } int setitem( PyObject* key, PyObject* value ) { if( !value ) return PyList_Type.tp_as_mapping->mp_ass_subscript( m_list.get(), key, value ); cppy::ptr item; if( PyIndex_Check( key ) ) item = validate_single( value ); else if( PySlice_Check( key ) ) item = validate_sequence( value ); else item = cppy::incref( value ); if( !item ) return -1; return PyList_Type.tp_as_mapping->mp_ass_subscript( m_list.get(), key, item.get() ); } protected: AtomList* alist() { return atomlist_cast( m_list.get() ); } Member* validator() { return alist()->validator; } CAtom* atom() { return alist()->pointer->data(); } PyObject* validate_single( PyObject* value ) { cppy::ptr item( cppy::incref( value ) ); if( validator() && atom() ) { item = validator()->full_validate( atom(), Py_None, item.get() ); if( !item ) return 0; } m_validated = item; return item.release(); } PyObject* validate_sequence( PyObject* value ) { cppy::ptr item( cppy::incref( value ) ); if( validator() && atom() ) { // no validation needed for self[::-1] = self if( m_list.get() != value ) { cppy::ptr templist( PySequence_List( value ) ); if( !templist ) return 0; CAtom* atm = atom(); Member* vd = validator(); Py_ssize_t size = PyList_GET_SIZE( templist.get() ); for( Py_ssize_t i = 0; i < size; ++i ) { // Borrow a reference to an item in the list PyObject* b = PyList_GET_ITEM( templist.get(), i ); PyObject* val = vd->full_validate( atm, Py_None, b ); if( !val ) return 0; PyList_SET_ITEM( templist.get(), i, cppy::incref( val ) ); } item = templist; } } m_validated = item; return item.release(); } cppy::ptr m_list; cppy::ptr m_validated; private: AtomListHandler(); }; PyObject* AtomList_new( PyTypeObject* type, PyObject* args, PyObject* kwargs ) { cppy::ptr ptr( PyList_Type.tp_new( type, args, kwargs ) ); if( !ptr ) { return 0; // LCOV_EXCL_LINE (failed instance creation) } atomlist_cast( ptr.get() )->pointer = new CAtomPointer(); return ptr.release(); } int AtomList_clear( AtomList* self ) { Py_CLEAR( self->validator ); return PyList_Type.tp_clear( pyobject_cast( self ) ); } int AtomList_traverse( AtomList* self, visitproc visit, void* arg ) { Py_VISIT( self->validator ); #if PY_VERSION_HEX >= 0x03090000 // This was not needed before Python 3.9 (Python issue 35810 and 40217) Py_VISIT(Py_TYPE(self)); #endif // PyList_type is not heap allocated so it does visit the type return PyList_Type.tp_traverse( pyobject_cast( self ), visit, arg ); } void AtomList_dealloc( AtomList* self ) { PyObject_GC_UnTrack( self ); cppy::clear( &self->validator ); delete self->pointer; self->pointer = 0; PyList_Type.tp_dealloc( pyobject_cast( self ) ); } PyObject* AtomList_append( AtomList* self, PyObject* value ) { return AtomListHandler( self ).append( value ); } PyObject* AtomList_insert( AtomList* self, PyObject* args ) { return AtomListHandler( self ).insert( args ); } PyObject* AtomList_extend( AtomList* self, PyObject* value ) { return AtomListHandler( self ).extend( value ); } PyObject* AtomList_reduce_ex( AtomList* self, PyObject* proto ) { // An atomlist is pickled as a normal list. When the Atom class is // reconstituted, assigning the list to the attribute will create // a new atomlist with the proper owner. There is no need to try // to persist the validator and pointer information. cppy::ptr data( PySequence_List( pyobject_cast( self ) ) ); if( !data ) return 0; cppy::ptr res( PyTuple_New( 2 ) ); if( !res ) return 0; cppy::ptr args( PyTuple_New( 1 ) ); if( !args ) return 0; PyTuple_SET_ITEM( args.get(), 0, data.release() ); PyTuple_SET_ITEM( res.get(), 0, cppy::incref( pyobject_cast( &PyList_Type ) ) ); PyTuple_SET_ITEM( res.get(), 1, args.release() ); return res.release(); } int AtomList_ass_item( AtomList* self, Py_ssize_t index, PyObject* value ) { return AtomListHandler( self ).setitem( index, value ); } PyObject* AtomList_inplace_concat( AtomList* self, PyObject* value ) { return AtomListHandler( self ).iadd( value ); } int AtomList_ass_subscript( AtomList* self, PyObject* key, PyObject* value ) { return AtomListHandler( self ).setitem( key, value ); } PyDoc_STRVAR( a_append_doc, "L.append(object) -- append object to end" ); PyDoc_STRVAR( a_insert_doc, "L.insert(index, object) -- insert object before index" ); PyDoc_STRVAR( a_extend_doc, "L.extend(iterable) -- extend list by appending elements from the iterable" ); static PyMethodDef AtomList_methods[] = { { "append", ( PyCFunction )AtomList_append, METH_O, a_append_doc }, { "insert", ( PyCFunction )AtomList_insert, METH_VARARGS, a_insert_doc }, { "extend", ( PyCFunction )AtomList_extend, METH_O, a_extend_doc }, { "__reduce_ex__", ( PyCFunction )AtomList_reduce_ex, METH_O, "" }, { 0 } /* sentinel */ }; static PyType_Slot AtomList_Type_slots[] = { { Py_tp_base, void_cast( &PyList_Type ) }, /* tp_base */ { Py_tp_new, void_cast( AtomList_new ) }, /* tp_new */ { Py_tp_dealloc, void_cast( AtomList_dealloc ) }, /* tp_dealloc */ { Py_tp_traverse, void_cast( AtomList_traverse ) }, /* tp_traverse */ { Py_tp_clear, void_cast( AtomList_clear ) }, /* tp_clear */ { Py_tp_methods, void_cast( AtomList_methods ) }, /* tp_methods */ { Py_sq_ass_item, void_cast( AtomList_ass_item ) }, /* sq_ass_item */ { Py_sq_inplace_concat, void_cast( AtomList_inplace_concat ) }, /* sq_ass_item */ { Py_mp_ass_subscript, void_cast( AtomList_ass_subscript ) }, /* mp_ass_subscript */ { 0, 0 }, }; } // namespace PyTypeObject* AtomList::TypeObject = NULL; PyType_Spec AtomList::TypeObject_Spec = { PACKAGE_TYPENAME( "atomlist" ), /* tp_name */ sizeof( AtomList ), /* tp_basicsize */ 0, /* tp_itemsize */ Py_TPFLAGS_DEFAULT |Py_TPFLAGS_BASETYPE |Py_TPFLAGS_HAVE_GC, /* tp_flags */ AtomList_Type_slots /* slots */ }; PyObject* AtomList::New( Py_ssize_t size, CAtom* atom, Member* validator ) { cppy::ptr ptr( ListSubtype_New( AtomList::TypeObject, size ) ); if( !ptr ) return 0; cppy::xincref( pyobject_cast( validator ) ); atomlist_cast( ptr.get() )->validator = validator; atomlist_cast( ptr.get() )->pointer = new CAtomPointer( atom ); return ptr.release(); } bool AtomList::Ready() { if( !ListMethods::init_methods() ) { return false; // LCOV_EXCL_LINE (failed method lookup, impossible) } // The reference will be handled by the module to which we will add the type TypeObject = pytype_cast( PyType_FromSpec( &TypeObject_Spec ) ); if( !TypeObject ) { return false; // LCOV_EXCL_LINE (failed type creation) } return true; } /*----------------------------------------------------------------------------- | AtomCList Type |----------------------------------------------------------------------------*/ namespace PySStr { static PyObject* typestr; static PyObject* namestr; static PyObject* objectstr; static PyObject* valuestr ; static PyObject* operationstr ; static PyObject* itemstr ; static PyObject* itemsstr ; static PyObject* indexstr ; static PyObject* keystr ; static PyObject* reversestr ; static PyObject* containerstr ; static PyObject* __delitem__str ; static PyObject* __iadd__str ; static PyObject* __imul__str ; static PyObject* __setitem__str ; static PyObject* appendstr ; static PyObject* extendstr ; static PyObject* insertstr ; static PyObject* popstr ; static PyObject* removestr ; static PyObject* sortstr ; static PyObject* olditemstr ; static PyObject* newitemstr ; static PyObject* countstr ; } // namespace PySStr bool init_containerlistchange() { static bool alloced = false; if( alloced ) { return true; } PySStr::typestr = PyUnicode_InternFromString( "type" ); if( !PySStr::typestr ) { return false; // LCOV_EXCL_LINE (failed interned string creation) } PySStr::namestr = PyUnicode_InternFromString( "name" ); if( !PySStr::namestr ) { return false; // LCOV_EXCL_LINE (failed interned string creation) } PySStr::objectstr = PyUnicode_InternFromString( "object" ); if( !PySStr::objectstr ) { return false; // LCOV_EXCL_LINE (failed interned string creation) } PySStr::valuestr = PyUnicode_InternFromString( "value" ); if( !PySStr::valuestr ) { return false; // LCOV_EXCL_LINE (failed interned string creation) } PySStr::operationstr = PyUnicode_InternFromString( "operation" ); if( !PySStr::operationstr ) { return false; // LCOV_EXCL_LINE (failed interned string creation) } PySStr::itemstr = PyUnicode_InternFromString( "item" ); if( !PySStr::itemstr ) { return false; // LCOV_EXCL_LINE (failed interned string creation) } PySStr::itemsstr = PyUnicode_InternFromString( "items" ); if( !PySStr::itemsstr ) { return false; // LCOV_EXCL_LINE (failed interned string creation) } PySStr::indexstr = PyUnicode_InternFromString( "index" ); if( !PySStr::indexstr ) { return false; // LCOV_EXCL_LINE (failed interned string creation) } PySStr::keystr = PyUnicode_InternFromString( "key" ); if( !PySStr::keystr ) { return false; // LCOV_EXCL_LINE (failed interned string creation) } PySStr::reversestr = PyUnicode_InternFromString( "reverse" ); if( !PySStr::reversestr ) { return false; // LCOV_EXCL_LINE (failed interned string creation) } PySStr::containerstr = PyUnicode_InternFromString( "container" ); if( !PySStr::containerstr ) { return false; // LCOV_EXCL_LINE (failed interned string creation) } PySStr::__delitem__str = PyUnicode_InternFromString( "__delitem__" ); if( !PySStr::typestr ) { return false; // LCOV_EXCL_LINE (failed interned string creation) } PySStr::__iadd__str = PyUnicode_InternFromString( "__iadd__" ); if( !PySStr::__iadd__str ) { return false; // LCOV_EXCL_LINE (failed interned string creation) } PySStr::__imul__str = PyUnicode_InternFromString( "__imul__" ); if( !PySStr::__imul__str ) { return false; // LCOV_EXCL_LINE (failed interned string creation) } PySStr::__setitem__str = PyUnicode_InternFromString( "__setitem__" ); if( !PySStr::__setitem__str ) { return false; // LCOV_EXCL_LINE (failed interned string creation) } PySStr::appendstr = PyUnicode_InternFromString( "append" ); if( !PySStr::appendstr ) { return false; // LCOV_EXCL_LINE (failed interned string creation) } PySStr::extendstr = PyUnicode_InternFromString( "extend" ); if( !PySStr::extendstr ) { return false; // LCOV_EXCL_LINE (failed interned string creation) } PySStr::insertstr = PyUnicode_InternFromString( "insert" ); if( !PySStr::insertstr ) { return false; // LCOV_EXCL_LINE (failed interned string creation) } PySStr::popstr = PyUnicode_InternFromString( "pop" ); if( !PySStr::popstr ) { return false; // LCOV_EXCL_LINE (failed interned string creation) } PySStr::removestr = PyUnicode_InternFromString( "remove" ); if( !PySStr::removestr ) { return false; // LCOV_EXCL_LINE (failed interned string creation) } PySStr::sortstr = PyUnicode_InternFromString( "sort" ); if( !PySStr::sortstr ) { return false; // LCOV_EXCL_LINE (failed interned string creation) } PySStr::olditemstr = PyUnicode_InternFromString( "olditem" ); if( !PySStr::olditemstr ) { return false; // LCOV_EXCL_LINE (failed interned string creation) } PySStr::newitemstr = PyUnicode_InternFromString( "newitem" ); if( !PySStr::newitemstr ) { return false; // LCOV_EXCL_LINE (failed interned string creation) } PySStr::countstr = PyUnicode_InternFromString( "count" ); if( !PySStr::countstr ) { return false; // LCOV_EXCL_LINE (failed interned string creation) } alloced = true; return true; } namespace { class AtomCListHandler : public AtomListHandler { static void clip_index( Py_ssize_t& index, Py_ssize_t size ) { if( index < 0 ) { index += size; if( index < 0 ) index = 0; } if( index > size ) index = size; } // XXX should I add clear ? public: AtomCListHandler( AtomCList* list ) : AtomListHandler( atomlist_cast( list ) ), m_obsm( false ), m_obsa( false ) {} PyObject* append( PyObject* value ) { cppy::ptr res( AtomListHandler::append( value ) ); if( !res ) return 0; if( observer_check() ) { cppy::ptr c( prepare_change() ); if( !c ) return 0; // LCOV_EXCL_LINE if( PyDict_SetItem( c.get(), PySStr::operationstr, PySStr::appendstr ) != 0 ) return 0; if( PyDict_SetItem( c.get(), PySStr::itemstr, m_validated.get() ) != 0 ) return 0; if( !post_change( c ) ) return 0; } return res.release(); } PyObject* insert( PyObject* args ) { Py_ssize_t size = PyList_GET_SIZE( m_list.get() ); cppy::ptr res( AtomListHandler::insert( args ) ); if( !res ) return 0; if( observer_check() ) { cppy::ptr c( prepare_change() ); if( !c ) return 0; // LCOV_EXCL_LINE if( PyDict_SetItem( c.get(), PySStr::operationstr, PySStr::insertstr ) != 0 ) return 0; // if the superclass call succeeds, then this is safe. Py_ssize_t where = PyLong_AsSsize_t( PyTuple_GET_ITEM( args, 0 ) ); clip_index( where, size ); cppy::ptr index( PyLong_FromSsize_t( where ) ); if( PyDict_SetItem( c.get(), PySStr::indexstr, index.get() ) != 0 ) return 0; if( PyDict_SetItem( c.get(), PySStr::itemstr, m_validated.get() ) != 0) return 0; if( !post_change( c ) ) return 0; } return res.release(); } PyObject* extend( PyObject* value ) { cppy::ptr res( AtomListHandler::extend( value ) ); if( !res ) return 0; if( observer_check() ) { cppy::ptr c( prepare_change() ); if( !c ) return 0; // LCOV_EXCL_LINE if( PyDict_SetItem( c.get(), PySStr::operationstr, PySStr::extendstr ) != 0 ) return 0; if( PyDict_SetItem( c.get(), PySStr::itemsstr, m_validated.get() ) != 0 ) return 0; if( !post_change( c ) ) return 0; } return res.release(); } PyObject* pop( PyObject* args ) { Py_ssize_t size = PyList_GET_SIZE( m_list.get() ); int nargs = (int)PyTuple_GET_SIZE( args); PyObject **stack = &PyTuple_GET_ITEM(args, 0); cppy::ptr res( ListMethods::pop( m_list.get(), stack, nargs ) ); if( !res ) return 0; if( observer_check() ) { cppy::ptr c( prepare_change() ); if( !c ) return 0; // LCOV_EXCL_LINE if( PyDict_SetItem( c.get(), PySStr::operationstr, PySStr::popstr ) != 0 ) return 0; // if the superclass call succeeds, then this is safe. Py_ssize_t i = -1; if( PyTuple_GET_SIZE( args ) == 1 ) i = PyLong_AsSsize_t( PyTuple_GET_ITEM( args, 0 ) ); if( i < 0 ) i += size; cppy::ptr index( PyLong_FromSsize_t( i ) ); if( PyDict_SetItem( c.get(), PySStr::indexstr, index.get() ) != 0 ) return 0; if( PyDict_SetItem( c.get(), PySStr::itemstr, res.get() ) != 0 ) return 0; if( !post_change( c ) ) return 0; } return res.release(); } PyObject* remove( PyObject* value ) { cppy::ptr res( ListMethods::remove( m_list.get(), value ) ); if( !res ) return 0; if( observer_check() ) { cppy::ptr c( prepare_change() ); if( !c ) return 0; // LCOV_EXCL_LINE if( PyDict_SetItem( c.get(), PySStr::operationstr, PySStr::removestr ) != 0) return 0; if( PyDict_SetItem( c.get(), PySStr::itemstr, value ) != 0 ) return 0; if( !post_change( c ) ) return 0; } return res.release(); } PyObject* reverse() { int res( PyList_Reverse( m_list.get() ) ); if( res != 0 ) return 0; if( observer_check() ) { cppy::ptr c( prepare_change() ); if( !c ) return 0; // LCOV_EXCL_LINE if( PyDict_SetItem( c.get(), PySStr::operationstr, PySStr::reversestr ) != 0) return 0; if( !post_change( c ) ) return 0; } return cppy::incref( Py_None ); } PyObject* sort( PyObject* args, PyObject* kwargs ) { static char *kwlist[] = { "key", "reverse", 0 }; // Get a reference to builtins (borrowed ref hence the incref) cppy::ptr builtins( cppy::xincref( PyImport_AddModule("builtins") ) ); if ( !builtins ) return 0; cppy::ptr super_type( builtins.getattr( "super" ) ); if ( !super_type ) return 0; // Create super args (tuple steals references) cppy::ptr super_args( PyTuple_New(2) ); if ( !super_args ) return 0; PyTuple_SET_ITEM( super_args.get(), 0, cppy::incref( pyobject_cast( Py_TYPE(m_list.get()) ) ) ); PyTuple_SET_ITEM( super_args.get(), 1, cppy::incref( m_list.get() ) ); // Get and call super method cppy::ptr super( super_type.call( super_args ) ); if ( !super ) return 0; cppy::ptr meth( super.getattr( "sort" ) ); if ( !meth ) return 0; cppy::ptr res( meth.call(args, kwargs) ); if( !res ) return 0; if( observer_check() ) { cppy::ptr c( prepare_change() ); if( !c ) return 0; // LCOV_EXCL_LINE if( PyDict_SetItem( c.get(), PySStr::operationstr, PySStr::sortstr ) != 0 ) return 0; PyObject* key = Py_None; int rev = 0; if( !PyArg_ParseTupleAndKeywords( args, kwargs, "|Oi", kwlist, &key, &rev ) ) return 0; if( PyDict_SetItem( c.get(), PySStr::keystr, key ) != 0) return 0; if( PyDict_SetItem( c.get(), PySStr::reversestr, rev ? Py_True : Py_False ) != 0 ) return 0; if( !post_change( c ) ) return 0; } return res.release(); } PyObject* iadd( PyObject* value ) { cppy::ptr res( AtomListHandler::iadd( value ) ); if( !res ) return 0; if( observer_check() ) { cppy::ptr c( prepare_change() ); if( !c ) return 0; // LCOV_EXCL_LINE if( PyDict_SetItem( c.get(), PySStr::operationstr, PySStr::__iadd__str ) != 0 ) return 0; if( PyDict_SetItem( c.get(), PySStr::itemsstr, m_validated.get() ) != 0 ) return 0; if( !post_change( c ) ) return 0; } return res.release(); } PyObject* imul( Py_ssize_t count ) { cppy::ptr res( PyList_Type.tp_as_sequence->sq_inplace_repeat( m_list.get(), count ) ); if( !res ) return 0; if( observer_check() ) { cppy::ptr c( prepare_change() ); if( !c ) return 0; // LCOV_EXCL_LINE if( PyDict_SetItem( c.get(), PySStr::operationstr, PySStr::__imul__str ) != 0 ) return 0; cppy::ptr pycount( PyLong_FromSsize_t( count ) ); if( !pycount ) return 0; if( PyDict_SetItem( c.get(), PySStr::countstr, pycount.get() ) != 0 ) return 0; if( !post_change( c ) ) return 0; } return res.release(); } int setitem( Py_ssize_t index, PyObject* value ) { cppy::ptr olditem; bool obs = observer_check(); if( obs ) { olditem = cppy::xincref(PyList_GetItem( m_list.get(), index )); if( !olditem ) return -1; } int res = AtomListHandler::setitem( index, value ); if( res < 0 ) return res; if( obs ) { cppy::ptr pyindex( PyLong_FromSsize_t( index ) ); if( !pyindex ) return -1; res = post_setitem_change( pyindex, olditem, m_validated ); } return res; } int setitem( PyObject* key, PyObject* value ) { cppy::ptr olditem; bool obs = observer_check(); if( obs ) { olditem = PyObject_GetItem( m_list.get(), key ); if( !olditem ) return -1; } int res = AtomListHandler::setitem( key, value ); if( res < 0 ) return res; if( obs ) { cppy::ptr index( cppy::incref( key ) ); res = post_setitem_change( index, olditem, m_validated ); } return res; } private: AtomCListHandler(); AtomCList* clist() { return atomclist_cast( m_list.get() ); } Member* member() { return clist()->member; } bool observer_check() { m_obsm = false; m_obsa = false; if( !member() || !atom() ) return false; m_obsm = member()->has_observers( ChangeType::Container ); m_obsa = atom()->has_observers( member()->name ); return m_obsm || m_obsa; } PyObject* prepare_change() { cppy::ptr c( PyDict_New() ); if( !c ) return 0; if( PyDict_SetItem( c.get(), PySStr::typestr, PySStr::containerstr ) != 0 ) return 0; if( PyDict_SetItem( c.get(), PySStr::namestr, member()->name ) != 0 ) return 0; if( PyDict_SetItem( c.get(), PySStr::objectstr, pyobject_cast( atom() ) ) != 0 ) return 0; if( PyDict_SetItem( c.get(), PySStr::valuestr, m_list.get() ) != 0 ) return 0; return c.release(); } bool post_change( cppy::ptr& change ) { cppy::ptr args( PyTuple_New( 1 ) ); if( !args ) return false; PyTuple_SET_ITEM( args.get(), 0, change.release() ); if( m_obsm ) { if( !member()->notify( atom(), args.get(), 0, ChangeType::Container ) ) return false; } if( m_obsa ) { if( !atom()->notify( member()->name, args.get(), 0, ChangeType::Container ) ) return false; } return true; } int post_setitem_change( cppy::ptr& i, cppy::ptr& o, cppy::ptr& n ) { cppy::ptr c( prepare_change() ); if( !c ) return -1; if( n ) { if( PyDict_SetItem( c.get(), PySStr::operationstr, PySStr::__setitem__str ) != 0 ) return -1; if( PyDict_SetItem( c.get(), PySStr::olditemstr, o.get() ) != 0) return -1; if( PyDict_SetItem( c.get(), PySStr::newitemstr, n.get() ) != 0) return -1; } else { if( PyDict_SetItem( c.get(), PySStr::operationstr, PySStr::__delitem__str ) != 0 ) return -1; if( PyDict_SetItem( c.get(), PySStr::itemstr, o.get() ) != 0 ) return -1; } if( PyDict_SetItem( c.get(), PySStr::indexstr, i.get() ) != 0 ) return -1; if( !post_change( c ) ) return -1; return 0; } bool m_obsm; bool m_obsa; }; PyObject* AtomCList_new( PyTypeObject* type, PyObject* args, PyObject* kwargs ) { return AtomList::TypeObject->tp_new( type, args, kwargs ); } int AtomCList_clear( AtomCList* self ) { Py_CLEAR( self->member ); return AtomList_clear( atomlist_cast( self ) ); } int AtomCList_traverse( AtomCList* self, visitproc visit, void* arg ) { Py_VISIT( self->member ); return AtomList_traverse( atomlist_cast( self ) , visit, arg ); } void AtomCList_dealloc( AtomCList* self ) { PyObject_GC_UnTrack( self ); cppy::clear( &self->member ); cppy::clear( &atomlist_cast( self )->validator ); delete atomlist_cast( self )->pointer; atomlist_cast( self )->pointer = 0; PyList_Type.tp_dealloc( pyobject_cast( self ) ); } PyObject* AtomCList_append( AtomCList* self, PyObject* value ) { return AtomCListHandler( self ).append( value ); } PyObject* AtomCList_insert( AtomCList* self, PyObject* args ) { return AtomCListHandler( self ).insert( args ); } PyObject* AtomCList_extend( AtomCList* self, PyObject* value ) { return AtomCListHandler( self ).extend( value ); } PyObject* AtomCList_pop( AtomCList* self, PyObject* args ) { return AtomCListHandler( self ).pop( args ); } PyObject* AtomCList_remove( AtomCList* self, PyObject* value ) { return AtomCListHandler( self ).remove( value ); } PyObject* AtomCList_reverse( AtomCList* self ) { return AtomCListHandler( self ).reverse(); } PyObject* AtomCList_sort( AtomCList* self, PyObject* args, PyObject* kwargs ) { return AtomCListHandler( self ).sort( args, kwargs ); } int AtomCList_ass_item( AtomCList* self, Py_ssize_t index, PyObject* value ) { return AtomCListHandler( self ).setitem( index, value ); } PyObject* AtomCList_inplace_concat( AtomCList* self, PyObject* value ) { return AtomCListHandler( self ).iadd( value ); } PyObject* AtomCList_inplace_repeat( AtomCList* self, Py_ssize_t count ) { return AtomCListHandler( self ).imul( count ); } int AtomCList_ass_subscript( AtomCList* self, PyObject* key, PyObject* value ) { return AtomCListHandler( self ).setitem( key, value ); } PyDoc_STRVAR(c_append_doc, "L.append(object) -- append object to end"); PyDoc_STRVAR(c_insert_doc, "L.insert(index, object) -- insert object before index"); PyDoc_STRVAR(c_extend_doc, "L.extend(iterable) -- extend list by appending elements from the iterable"); PyDoc_STRVAR(c_pop_doc, "L.pop([index]) -> item -- remove and return item at index (default last).\n" "Raises IndexError if list is empty or index is out of range."); PyDoc_STRVAR(c_remove_doc, "L.remove(value) -- remove first occurrence of value.\n" "Raises ValueError if the value is not present."); PyDoc_STRVAR(c_reverse_doc, "L.reverse() -- reverse *IN PLACE*"); PyDoc_STRVAR(c_sort_doc, "L.sort(cmp=None, key=None, reverse=False) -- stable sort *IN PLACE*;\n\ cmp(x, y) -> -1, 0, 1"); static PyMethodDef AtomCList_methods[] = { { "append", ( PyCFunction )AtomCList_append, METH_O, c_append_doc }, { "insert", ( PyCFunction )AtomCList_insert, METH_VARARGS, c_insert_doc }, { "extend", ( PyCFunction )AtomCList_extend, METH_O, c_extend_doc }, { "pop", ( PyCFunction )AtomCList_pop, METH_VARARGS, c_pop_doc }, { "remove", ( PyCFunction )AtomCList_remove, METH_O, c_remove_doc }, { "reverse", ( PyCFunction )AtomCList_reverse, METH_NOARGS, c_reverse_doc }, { "sort", ( PyCFunction )AtomCList_sort, METH_VARARGS | METH_KEYWORDS, c_sort_doc }, { 0 } /* sentinel */ }; static PyType_Slot AtomCList_Type_slots[] = { { Py_tp_base, NULL }, // Set once the base type is created /* tp_base */ { Py_tp_new, void_cast( AtomCList_new ) }, /* tp_new */ { Py_tp_dealloc, void_cast( AtomCList_dealloc ) }, /* tp_dealloc */ { Py_tp_traverse, void_cast( AtomCList_traverse ) }, /* tp_traverse */ { Py_tp_clear, void_cast( AtomCList_clear ) }, /* tp_clear */ { Py_tp_methods, void_cast( AtomCList_methods ) }, /* tp_methods */ { Py_sq_ass_item, void_cast( AtomCList_ass_item ) }, /* sq_ass_item */ { Py_sq_inplace_concat, void_cast( AtomCList_inplace_concat ) }, /* sq_ass_item */ { Py_sq_inplace_repeat, void_cast( AtomCList_inplace_repeat ) }, /* sq_ass_item */ { Py_mp_ass_subscript, void_cast( AtomCList_ass_subscript ) }, /* mp_ass_subscript */ { 0, 0 }, }; } // namespace PyTypeObject* AtomCList::TypeObject = NULL; PyType_Spec AtomCList::TypeObject_Spec = { PACKAGE_TYPENAME( "atomclist" ), /* tp_name */ sizeof( AtomCList ), /* tp_basicsize */ 0, /* tp_itemsize */ Py_TPFLAGS_DEFAULT |Py_TPFLAGS_BASETYPE |Py_TPFLAGS_HAVE_GC, /* tp_flags */ AtomCList_Type_slots /* slots */ }; PyObject* AtomCList::New( Py_ssize_t size, CAtom* atom, Member* validator, Member* member ) { cppy::ptr ptr( ListSubtype_New( AtomCList::TypeObject, size ) ); if( !ptr ) return 0; cppy::xincref( pyobject_cast( validator ) ); cppy::xincref( pyobject_cast( member ) ); atomlist_cast( ptr.get() )->validator = validator; atomlist_cast( ptr.get() )->pointer = new CAtomPointer( atom ); atomclist_cast( ptr.get() )->member = member; return ptr.release(); } bool AtomCList::Ready() { // Ensure the parent type was created if( !AtomList::TypeObject ) { return false; // LCOV_EXCL_LINE (parent type not created, impossible) } AtomCList_Type_slots[0].pfunc = void_cast( AtomList::TypeObject ); // The reference will be handled by the module to which we will add the type TypeObject = pytype_cast( PyType_FromSpec( &TypeObject_Spec ) ); if( !TypeObject ) { return false; // LCOV_EXCL_LINE (failed type creation) } return true; } } // namespace atom atom-0.12.1/atom/src/atomlist.h000066400000000000000000000030131506756731600163040ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #pragma once #include #include "catom.h" #include "catompointer.h" #include "member.h" #define atomlist_cast( o ) ( reinterpret_cast( o ) ) #define atomclist_cast( o ) ( reinterpret_cast( o ) ) namespace atom { // POD struct - all member fields are considered private struct AtomList { PyListObject list; Member* validator; CAtomPointer* pointer; static PyType_Spec TypeObject_Spec; static PyTypeObject* TypeObject; static bool Ready(); static PyObject* New( Py_ssize_t size, CAtom* atom, Member* validator ); static bool TypeCheck( PyObject* ob ) { return PyObject_TypeCheck( ob, TypeObject ) != 0; } }; bool init_containerlistchange(); // POD struct - all member fields are considered private struct AtomCList { PyListObject list; Member* validator; CAtomPointer* pointer; Member* member; static PyType_Spec TypeObject_Spec; static PyTypeObject* TypeObject; static bool Ready(); static PyObject* New( Py_ssize_t size, CAtom* atom, Member* validator, Member* member ); static bool TypeCheck( PyObject* ob ) { return PyObject_TypeCheck( ob, TypeObject ) != 0; } }; } atom-0.12.1/atom/src/atomref.cpp000066400000000000000000000111671506756731600164510ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #include #include #include #include #include "atomref.h" #include "catompointer.h" #include "globalstatic.h" #include "packagenaming.h" #ifdef __clang__ #pragma clang diagnostic ignored "-Wdeprecated-writable-strings" #endif #ifdef __GNUC__ #pragma GCC diagnostic ignored "-Wwrite-strings" #endif #define atomref_cast( o ) ( reinterpret_cast( o ) ) namespace atom { namespace SharedAtomRef { typedef std::map RefMap; GLOBAL_STATIC( RefMap, ref_map ) PyObject* get( CAtom* atom ) { if( atom->has_atomref() ) { return cppy::incref( ( *ref_map() )[ atom ].get() ); } PyObject* pyref = PyType_GenericAlloc( AtomRef::TypeObject, 0 ); if( !pyref ) { return 0; } // placement new since Python malloc'd and zero'd the struct new( &atomref_cast( pyref )->pointer ) CAtomPointer( atom ); ( *ref_map() )[ atom ] = cppy::incref( pyref ); atom->set_has_atomref( true ); return pyref; } void clear( CAtom* atom ) { ref_map()->erase( atom ); atom->set_has_atomref( false ); } } // namespace SharedAtomRef namespace { PyObject* AtomRef_new( PyTypeObject* type, PyObject* args, PyObject* kwargs ) { static char *kwlist[] = { "atom", 0 }; PyObject* atom; if( !PyArg_ParseTupleAndKeywords( args, kwargs, "O:__new__", kwlist, &atom ) ) { return 0; } if( !CAtom::TypeCheck( atom ) ) { return cppy::type_error( atom, "CAtom" ); } return SharedAtomRef::get( catom_cast( atom ) ); } void AtomRef_dealloc( AtomRef* self ) { // manual destructor since Python malloc'd and zero'd the struct self->pointer.~CAtomPointer(); Py_TYPE(self)->tp_free( pyobject_cast( self ) ); } PyObject* AtomRef_call( AtomRef* self, PyObject* args, PyObject* kwargs ) { static char *kwlist[] = { 0 }; if( !PyArg_ParseTupleAndKeywords( args, kwargs, ":__call__", kwlist ) ) { return 0; } PyObject* obj = pyobject_cast( self->pointer.data() ); return cppy::incref( obj ? obj : Py_None ); } PyObject* AtomRef_repr( AtomRef* self ) { std::ostringstream ostr; ostr << "AtomRef(atom="; if( self->pointer.is_null() ) { ostr << "None"; } else { PyObject* obj = pyobject_cast( self->pointer.data() ); cppy::ptr repr( PyObject_Repr( obj ) ); if( !repr ) return 0; ostr << PyUnicode_AsUTF8( repr.get() ); } ostr << ")"; return PyUnicode_FromString( ostr.str().c_str() ); } PyObject* AtomRef_sizeof( AtomRef* self, PyObject* args ) { Py_ssize_t size = Py_TYPE(self)->tp_basicsize; size += sizeof( CAtomPointer ); return PyLong_FromSsize_t( size ); } int AtomRef__bool__( AtomRef* self ) { return self->pointer.is_null() ? 0 : 1; } static PyMethodDef AtomRef_methods[] = { { "__sizeof__", ( PyCFunction )AtomRef_sizeof, METH_NOARGS, "__sizeof__() -> size of object in memory, in bytes" }, { 0 } // sentinel }; static PyType_Slot AtomRef_Type_slots[] = { { Py_tp_dealloc, void_cast( AtomRef_dealloc ) }, /* tp_dealloc */ { Py_tp_repr, void_cast( AtomRef_repr ) }, /* tp_repr */ { Py_tp_methods, void_cast( AtomRef_methods ) }, /* tp_methods */ { Py_tp_new, void_cast( AtomRef_new ) }, /* tp_new */ { Py_tp_call, void_cast( AtomRef_call ) }, /* tp_call */ { Py_tp_alloc, void_cast( PyType_GenericAlloc ) }, /* tp_alloc */ { Py_nb_bool, void_cast( AtomRef__bool__ ) }, /* nb_bool */ { 0, 0 }, }; } // namespace PyTypeObject* AtomRef::TypeObject = NULL; PyType_Spec AtomRef::TypeObject_Spec = { PACKAGE_TYPENAME( "atomref" ), /* tp_name */ sizeof( AtomRef ), /* tp_basicsize */ 0, /* tp_itemsize */ Py_TPFLAGS_DEFAULT, /* tp_flags */ AtomRef_Type_slots /* slots */ }; bool AtomRef::Ready() { // The reference will be handled by the module to which we will add the type TypeObject = pytype_cast( PyType_FromSpec( &TypeObject_Spec ) ); if( !TypeObject ) { return false; // LCOV_EXCL_LINE (failed type init) } return true; } } atom-0.12.1/atom/src/atomref.h000066400000000000000000000015751506756731600161200ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #pragma once #include "catom.h" #include "catompointer.h" namespace atom { // POD struct - all member fields are considered private struct AtomRef { PyObject_HEAD CAtomPointer pointer; // constructed with placement new static PyType_Spec TypeObject_Spec; static PyTypeObject* TypeObject; static bool Ready(); static bool TypeCheck( PyObject* ob ) { return PyObject_TypeCheck( ob, TypeObject ) != 0; } }; namespace SharedAtomRef { void clear( CAtom* atom ); } // namespace SharedAtomRef } // namespace atom atom-0.12.1/atom/src/atomset.cpp000066400000000000000000000230151506756731600164630ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2019-2024, Nucleic | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #include #include "atomset.h" #include "packagenaming.h" namespace atom { typedef PyCFunction pycfunc; typedef _PyCFunctionFast pycfunc_f; namespace SetMethods { static PyObject* update; bool init_methods() { static bool alloced = false; if( alloced ) { return true; } update = PyObject_GetAttrString( pyobject_cast( &PySet_Type ), "update" ); if( !update ) { return false; // LCOV_EXCL_LINE (failed to load set 'update' method, impossible) } return true; } } // namespace PySStr namespace { inline bool should_validate( AtomSet* set ) { return set->m_value_validator; } PyObject* validate_value( AtomSet* set, PyObject* value ) { CAtom* atom = set->pointer->data(); Member* validator = set->m_value_validator; cppy::ptr item( cppy::incref( value ) ); if( validator && atom ) { item = validator->full_validate( atom, Py_None, item.get() ); if( !item ) { return 0; } } return item.release(); } PyObject* validate_set( AtomSet* set, PyObject* value ) { cppy::ptr val_set( PySet_New( 0 ) ); cppy::ptr value_iter = PyObject_GetIter(value); if( !value_iter ) { return 0; } cppy::ptr temp; cppy::ptr validated; while( ( temp = PyIter_Next( value_iter.get() ) ) ) { validated = validate_value( set, temp.get() ); if( !validated ) { return 0; } if( PySet_Add( val_set.get(), validated.get() ) < 0 ) { return 0; } } return val_set.release(); } PyObject* AtomSet_new( PyTypeObject* type, PyObject* args, PyObject* kwargs ) { cppy::ptr self( PySet_Type.tp_new( type, args, kwargs ) ); if( !self ) { return 0; // LCOV_EXCL_LINE (failed instance creation) } atomset_cast( self.get() )->pointer = new CAtomPointer(); return self.release(); } int AtomSet_clear( AtomSet* self ) { Py_CLEAR( self->m_value_validator ); return PySet_Type.tp_clear( pyobject_cast( self ) ); } int AtomSet_traverse( AtomSet* self, visitproc visit, void* arg ) { Py_VISIT( self->m_value_validator ); #if PY_VERSION_HEX >= 0x03090000 // This was not needed before Python 3.9 (Python issue 35810 and 40217) Py_VISIT(Py_TYPE(self)); #endif // PySet_type is not heap allocated so it does visit the type return PySet_Type.tp_traverse( pyobject_cast( self ), visit, arg ); } void AtomSet_dealloc( AtomSet* self ) { PyObject_GC_UnTrack( self ); cppy::clear( &self->m_value_validator ); delete atomset_cast( self )->pointer; atomset_cast( self )->pointer = 0; PySet_Type.tp_dealloc( pyobject_cast( self ) ); } PyObject* AtomSet_isub( AtomSet* self, PyObject* other ) { cppy::ptr other_ptr( cppy::incref( other ) ); if( should_validate( self ) && PyAnySet_Check( other ) ) { other_ptr = validate_set( self, other ); if( !other_ptr ) { return 0; } } return PySet_Type.tp_as_number->nb_inplace_subtract( pyobject_cast( self ), other_ptr.get() ); } PyObject* AtomSet_iand( AtomSet* self, PyObject* other ) { cppy::ptr other_ptr( cppy::incref( other ) ); if( should_validate( self ) && PyAnySet_Check( other ) ) { other_ptr = validate_set( self, other ); if( !other_ptr ) { return 0; } } return PySet_Type.tp_as_number->nb_inplace_and( pyobject_cast( self ), other_ptr.get() ); } PyObject* AtomSet_ior( AtomSet* self, PyObject* other ) { cppy::ptr other_ptr( cppy::incref( other ) ); if( should_validate( self ) && PyAnySet_Check( other ) ) { other_ptr = validate_set( self, other ); if( !other_ptr ) { return 0; } } return PySet_Type.tp_as_number->nb_inplace_or( pyobject_cast( self ), other_ptr.get() ); } PyObject* AtomSet_ixor( AtomSet* self, PyObject* other ) { cppy::ptr other_ptr( cppy::incref( other ) ); if( should_validate( self ) && PyAnySet_Check( other ) ) { other_ptr = validate_set( self, other ); if( !other_ptr ) { return 0; } } return PySet_Type.tp_as_number->nb_inplace_xor( pyobject_cast( self ), other_ptr.get() ); } PyObject* AtomSet_add( AtomSet* self, PyObject* value ) { cppy::ptr value_ptr( cppy::incref( value ) ); if( should_validate( self ) ) { value_ptr = validate_value( self, value ); if( !value_ptr ) { return 0; } } if( PySet_Add( pyobject_cast( self ), value_ptr.get() ) < 0 ) { return 0; } return cppy::incref( Py_None ); } PyObject* AtomSet_difference_update( AtomSet* self, PyObject* value ) { cppy::ptr temp( cppy::incref( value ) ); if( !PyAnySet_Check( value ) && !( temp = PySet_New( value ) ) ) { return 0; } cppy::ptr ignored( AtomSet_isub( self, temp.get() ) ); if( !ignored ) { return 0; } return cppy::incref( Py_None ); } PyObject* AtomSet_intersection_update( AtomSet* self, PyObject* value ) { cppy::ptr temp( cppy::incref( value ) ); if( !PyAnySet_Check( value ) && !( temp = PySet_New( value ) ) ) { return 0; } cppy::ptr ignored( AtomSet_iand( self, temp.get() ) ); if( !ignored ) { return 0; } return cppy::incref( Py_None ); } PyObject* AtomSet_symmetric_difference_update( AtomSet* self, PyObject* value ) { cppy::ptr temp( cppy::incref( value ) ); if( !PyAnySet_Check( value ) && !( temp = PySet_New( value ) ) ) { return 0; } cppy::ptr ignored( AtomSet_ixor( self, temp.get() ) ); if( !ignored ) { return 0; } return cppy::incref( Py_None ); } PyObject* AtomSet_update( AtomSet* self, PyObject* value ) { if( AtomSet::Update( self, value ) < 0 ) { return 0; } return cppy::incref( Py_None ); } static PyMethodDef AtomSet_methods[] = { { "add", ( PyCFunction )AtomSet_add, METH_O, "Add an element to a set." }, { "difference_update", ( PyCFunction )AtomSet_difference_update, METH_O, "Update a set with the difference of itself and another." }, { "intersection_update", ( PyCFunction )AtomSet_intersection_update, METH_O, "Update a set with the intersection of itself and another." }, { "symmetric_difference_update", ( PyCFunction )AtomSet_symmetric_difference_update, METH_O, "Update a set with the symmetric difference of itself and another." }, { "update", ( PyCFunction )AtomSet_update, METH_O, "Update a set with the union of itself and another." }, { 0 } // sentinel }; static PyType_Slot AtomSet_Type_slots[] = { { Py_tp_dealloc, void_cast( AtomSet_dealloc ) }, /* tp_dealloc */ { Py_tp_traverse, void_cast( AtomSet_traverse ) }, /* tp_traverse */ { Py_tp_clear, void_cast( AtomSet_clear ) }, /* tp_clear */ { Py_tp_methods, void_cast( AtomSet_methods ) }, /* tp_methods */ { Py_tp_base, void_cast( &PySet_Type ) }, /* tp_base */ { Py_tp_new, void_cast( AtomSet_new ) }, /* tp_new */ { Py_nb_inplace_subtract, void_cast( AtomSet_isub ) }, /* nb_inplace_substract */ { Py_nb_inplace_and, void_cast( AtomSet_iand ) }, /* nb_inplace_substract */ { Py_nb_inplace_xor, void_cast( AtomSet_ixor ) }, /* nb_inplace_substract */ { Py_nb_inplace_or, void_cast( AtomSet_ior ) }, /* nb_inplace_substract */ { 0, 0 }, }; } // namespace PyTypeObject* AtomSet::TypeObject = NULL; PyType_Spec AtomSet::TypeObject_Spec = { PACKAGE_TYPENAME( "atomset" ), /* tp_name */ sizeof( AtomSet ), /* tp_basicsize */ 0, /* tp_itemsize */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */ AtomSet_Type_slots /* slots */ }; PyObject* AtomSet::New( CAtom* atom, Member* validator ) { cppy::ptr self( PySet_Type.tp_new( AtomSet::TypeObject, 0, 0 ) ); if( !self ) { return 0; // LCOV_EXCL_LINE (failed instance creation) } cppy::xincref( pyobject_cast( validator ) ); atomset_cast( self.get() )->m_value_validator = validator; atomset_cast( self.get() )->pointer = new CAtomPointer( atom ); return self.release(); } int AtomSet::Update( AtomSet* set, PyObject* value ) { cppy::ptr r_temp; if( !should_validate( set ) ) { // Method call return Py_None or 0. We make sure to decref Py_None and // return -1 in case of error. r_temp = PyObject_CallFunctionObjArgs( SetMethods::update, pyobject_cast( set ), value, NULL ); return !r_temp ? -1 : 0; } cppy::ptr temp( cppy::incref( value ) ); if( !PyAnySet_Check( value ) && !( temp = PySet_New( value ) ) ) { return -1; } temp = validate_set( set, temp.get() ); if( !temp ) { return -1; } // Method call return Py_None or 0. We make sure to decref Py_None and // return -1 in case of error. r_temp = PyObject_CallFunctionObjArgs( SetMethods::update, pyobject_cast( set ), temp.get(), NULL ); return !r_temp ? -1 : 0; } bool AtomSet::Ready() { if( !SetMethods::init_methods() ) { return false; // LCOV_EXCL_LINE (failed method lookup, impossible) } // The reference will be handled by the module to which we will add the type TypeObject = pytype_cast( PyType_FromSpec( &TypeObject_Spec ) ); if( !TypeObject ) { return false; // LCOV_EXCL_LINE (failed type creation) } return true; } } // namespace atom atom-0.12.1/atom/src/atomset.h000066400000000000000000000017721506756731600161360ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2014-2024,, Nucleic | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #pragma once #include #include "catom.h" #include "catompointer.h" #include "member.h" #define atomset_cast( o ) ( reinterpret_cast( o ) ) namespace atom { // POD struct - all member fields are considered private struct AtomSet { PySetObject set; Member* m_value_validator; CAtomPointer* pointer; static PyType_Spec TypeObject_Spec; static PyTypeObject* TypeObject; static bool Ready(); static PyObject* New( CAtom* atom, Member* validator ); static int Update( AtomSet* set, PyObject* value ); static bool TypeCheck( PyObject* ob ) { return PyObject_TypeCheck( ob, TypeObject ) != 0; } }; } // namespace atom atom-0.12.1/atom/src/behaviors.h000066400000000000000000000056671506756731600164530ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #pragma once namespace atom { namespace GetAttr { enum Mode: uint8_t { NoOp, Slot, Event, Signal, Delegate, Property, CachedProperty, CallObject_Object, CallObject_ObjectName, ObjectMethod, ObjectMethod_Name, MemberMethod_Object, Last // sentinel }; } // namespace GetAttr namespace PostGetAttr { enum Mode: uint8_t { NoOp, Delegate, ObjectMethod_Value, ObjectMethod_NameValue, MemberMethod_ObjectValue, Last // sentinel }; } // namespace PostGetAttr namespace SetAttr { enum Mode: uint8_t { NoOp, Slot, Constant, ReadOnly, Event, Signal, Delegate, Property, CallObject_ObjectValue, CallObject_ObjectNameValue, ObjectMethod_Value, ObjectMethod_NameValue, MemberMethod_ObjectValue, Last // sentinel }; } // namespace SetAttr namespace PostSetAttr { enum Mode: uint8_t { NoOp, Delegate, ObjectMethod_OldNew, ObjectMethod_NameOldNew, MemberMethod_ObjectOldNew, Last // sentinel }; } // namespace PostSetAttr namespace DefaultValue { enum Mode: uint8_t { NoOp, Static, List, Set, Dict, DefaultDict, NonOptional, Delegate, CallObject, CallObject_Object, CallObject_ObjectName, ObjectMethod, ObjectMethod_Name, MemberMethod_Object, Last // sentinel }; } // namespace DefaultValue namespace Validate { enum Mode: uint8_t { NoOp, Bool, Int, IntPromote, Float, FloatPromote, Bytes, BytesPromote, Str, StrPromote, Tuple, FixedTuple, List, ContainerList, Set, Dict, DefaultDict, OptionalInstance, Instance, OptionalTyped, Typed, Subclass, Enum, Callable, FloatRange, FloatRangePromote, Range, Coerced, Delegate, ObjectMethod_OldNew, ObjectMethod_NameOldNew, MemberMethod_ObjectOldNew, Last // sentinel }; } // namespace Validate namespace PostValidate { enum Mode: uint8_t { NoOp, Delegate, ObjectMethod_OldNew, ObjectMethod_NameOldNew, MemberMethod_ObjectOldNew, Last // sentinel }; } // namespace PostValidate namespace DelAttr { enum Mode: uint8_t { NoOp, Slot, Constant, ReadOnly, Event, Signal, Delegate, Property, Last // sentinel }; } // namespace DelAttr namespace GetState { enum Mode: uint8_t { Include, // We want include to be the default behavior Exclude, IncludeNonDefault, Property, ObjectMethod_Name, MemberMethod_Object, }; } // namespace GetState } // namespace atom atom-0.12.1/atom/src/catom.cpp000066400000000000000000000475201506756731600161210ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #ifdef __clang__ #pragma clang diagnostic ignored "-Wdeprecated-writable-strings" #endif #ifdef __GNUC__ #pragma GCC diagnostic ignored "-Wwrite-strings" #endif #include #include #include "atomref.h" #include "catom.h" #include "globalstatic.h" #include "methodwrapper.h" #include "packagenaming.h" #include "utils.h" #include "member.h" #define signal_cast( o ) reinterpret_cast( o ) #define pymethod_cast( o ) reinterpret_cast( o ) namespace atom { namespace { static PyObject* atom_members; static PyObject* atom_flags; PyObject* CAtom_new( PyTypeObject* type, PyObject* args, PyObject* kwargs ) { cppy::ptr membersptr( PyObject_GetAttr( pyobject_cast( type ), atom_members ) ); if( !membersptr ) return 0; if( !PyDict_CheckExact( membersptr.get() ) ) return cppy::system_error( "atom members" ); cppy::ptr selfptr( PyType_GenericNew( type, args, kwargs ) ); if( !selfptr ) return 0; CAtom* atom = catom_cast( selfptr.get() ); uint32_t count = static_cast( PyDict_Size( membersptr.get() ) ); if( count > 0 ) { if( count > MAX_MEMBER_COUNT ) return cppy::type_error( "too many members" ); size_t size = sizeof( PyObject* ) * count; void* slots = PyObject_MALLOC( size ); if( !slots ) return PyErr_NoMemory(); // LCOV_EXCL_LINE memset( slots, 0, size ); atom->slots = reinterpret_cast( slots ); atom->set_slot_count( count ); } atom->set_notifications_enabled( true ); return selfptr.release(); } int CAtom_init( CAtom* self, PyObject* args, PyObject* kwargs ) { if( PyTuple_GET_SIZE( args ) > 0 ) { cppy::type_error( "__init__() takes no positional arguments" ); return -1; } if( kwargs ) { cppy::ptr selfptr( cppy::incref( pyobject_cast( self ) ) ); PyObject* key; PyObject* value; Py_ssize_t pos = 0; while( PyDict_Next( kwargs, &pos, &key, &value ) ) { if( !selfptr.setattr( key, value ) ) { return -1; // LCOV_EXCL_LINE } } } return 0; } void CAtom_clear( CAtom* self ) { uint32_t count = self->get_slot_count(); for( uint32_t i = 0; i < count; ++i ) { Py_CLEAR( self->slots[ i ] ); } if( self->observers ) { self->observers->py_clear(); } } int CAtom_traverse( CAtom* self, visitproc visit, void* arg ) { uint32_t count = self->get_slot_count(); for( uint32_t i = 0; i < count; ++i ) { Py_VISIT( self->slots[ i ] ); } #if PY_VERSION_HEX >= 0x03090000 // This was not needed before Python 3.9 (Python issue 35810 and 40217) Py_VISIT(Py_TYPE(self)); #endif if( self->observers ) { return self->observers->py_traverse( visit, arg ); } return 0; } void CAtom_dealloc( CAtom* self ) { if( self->has_guards() ) { CAtom::clear_guards( self ); } if( self->has_atomref() ) { SharedAtomRef::clear( self ); } PyObject_GC_UnTrack( self ); CAtom_clear( self ); if( self->slots ) { PyObject_FREE( self->slots ); } delete self->observers; self->observers = 0; Py_TYPE(self)->tp_free( pyobject_cast( self ) ); } PyObject* CAtom_notifications_enabled( CAtom* self ) { return utils::py_bool( self->get_notifications_enabled() ); } PyObject* CAtom_set_notifications_enabled( CAtom* self, PyObject* arg ) { if( !PyBool_Check( arg ) ) return cppy::type_error( arg, "bool" ); bool old = self->get_notifications_enabled(); self->set_notifications_enabled( arg == Py_True ? true : false ); return utils::py_bool( old ); } PyObject* CAtom_get_member( PyObject* self, PyObject* name ) { if( !PyUnicode_Check( name ) ) return cppy::type_error( name, "str" ); cppy::ptr membersptr( PyObject_GetAttr( pyobject_cast( Py_TYPE(self) ), atom_members ) ); if( !membersptr ) return 0; if( !PyDict_CheckExact( membersptr.get() ) ) return cppy::system_error( "atom members" ); cppy::ptr member( cppy::xincref( PyDict_GetItem( membersptr.get(), name ) ) ); if( !member ) Py_RETURN_NONE; return member.release(); } PyObject* CAtom_observe( CAtom* self, PyObject*const *args, Py_ssize_t n ) { if( n < 2 || n > 3) return cppy::type_error( "observe() takes exactly 2 or 3 arguments" ); PyObject* topic = args[0]; PyObject* callback = args[1]; if( !PyCallable_Check( callback ) ) return cppy::type_error( callback, "callable" ); uint8_t change_types = ChangeType::Any; if ( n == 3 ) { PyObject* types = args[2]; if( !PyLong_Check( types ) ) return cppy::type_error( types, "int" ); change_types = PyLong_AsLong( types ) & 0xFF; } if( utils::str_check( topic ) ) { if( !self->observe( topic, callback, change_types ) ) return 0; } else { cppy::ptr iterator( PyObject_GetIter( topic ) ); if( !iterator ) return 0; cppy::ptr topicptr; while( ( topicptr = PyIter_Next( iterator.get() ) ) ) { if( !utils::str_check( topicptr.get() ) ) return cppy::type_error( topicptr.get(), "str" ); if( !self->observe( topicptr.get(), callback, change_types ) ) return 0; } if( PyErr_Occurred() ) return 0; } Py_RETURN_NONE; } PyObject* _CAtom_unobserve_0( CAtom* self ) { if( !self->unobserve() ) return 0; Py_RETURN_NONE; } PyObject* _CAtom_unobserve_1( CAtom* self, PyObject* topic ) { if( utils::str_check( topic ) ) { if( !self->unobserve( topic ) ) return 0; } else { cppy::ptr iterator( PyObject_GetIter( topic ) ); if( !iterator ) return 0; cppy::ptr topicptr; while( ( topicptr = PyIter_Next( iterator.get() ) ) ) { if( !utils::str_check( topicptr.get() ) ) return cppy::type_error( topicptr.get(), "str" ); if( !self->unobserve( topicptr.get() ) ) return 0; } if( PyErr_Occurred() ) return 0; } Py_RETURN_NONE; } PyObject* _CAtom_unobserve_2( CAtom* self, PyObject* topic, PyObject* callback ) { if( !PyCallable_Check( callback ) ) return cppy::type_error( callback, "callable" ); if( utils::str_check( topic ) ) { if( !self->unobserve( topic, callback ) ) return 0; } else { cppy::ptr iterator( PyObject_GetIter( topic ) ); if( !iterator ) return 0; cppy::ptr topicptr; while( ( topicptr = PyIter_Next( iterator.get() ) ) ) { if( !utils::str_check( topicptr.get() ) ) return cppy::type_error( topicptr.get(), "str" ); if( !self->unobserve( topicptr.get(), callback ) ) return 0; } if( PyErr_Occurred() ) return 0; } Py_RETURN_NONE; } PyObject* CAtom_unobserve( CAtom* self, PyObject*const *args, Py_ssize_t n_args ) { if( n_args == 0 ) return _CAtom_unobserve_0( self ); if( n_args == 1 ) return _CAtom_unobserve_1( self, args[0] ); if ( n_args == 2) return _CAtom_unobserve_2( self, args[0], args[1] ); return cppy::type_error( "unobserve() takes at most 2 arguments" ); } PyObject* CAtom_has_observers( CAtom* self, PyObject* topic ) { return utils::py_bool( self->has_observers( topic ) ); } PyObject* CAtom_has_observer( CAtom* self, PyObject*const *args, Py_ssize_t n ) { if( n != 2 ) return cppy::type_error( "has_observer() takes exactly 2 arguments" ); PyObject* topic = args[0]; PyObject* callback = args[1]; if( !utils::str_check( topic ) ) return cppy::type_error( topic, "str" ); if( !PyCallable_Check( callback ) ) return cppy::type_error( callback, "callable" ); return utils::py_bool( self->has_observer( topic, callback ) ); } PyObject* CAtom_notify( CAtom* self, PyObject* args, PyObject* kwargs ) { if( PyTuple_GET_SIZE( args ) < 1 ) return cppy::type_error( "notify() requires at least 1 argument" ); PyObject* topic = PyTuple_GET_ITEM( args, 0 ); if( !utils::str_check( topic ) ) return cppy::type_error( topic, "str" ); cppy::ptr argsptr( PyTuple_GetSlice( args, 1, PyTuple_GET_SIZE( args ) ) ); if( !argsptr ) return 0; if( !self->notify( topic, argsptr.get(), kwargs ) ) return 0; Py_RETURN_NONE; } PyObject* CAtom_freeze( CAtom* self ) { self->set_frozen( true ); Py_RETURN_NONE; } PyObject* CAtom_sizeof( CAtom* self, PyObject* args ) { Py_ssize_t size = Py_TYPE(self)->tp_basicsize; size += sizeof( PyObject* ) * self->get_slot_count(); if( self->observers ) size += self->observers->py_sizeof(); return PyLong_FromSsize_t( size ); } PyObject* CAtom_getstate( CAtom* self ) { cppy::ptr stateptr = PyDict_New(); if ( !stateptr ) { return PyErr_NoMemory(); // LCOV_EXCL_LINE } cppy::ptr selfptr(pyobject_cast(self), true); // Copy __dict__ if present. Using hasattr / getattr makes it slower // than the py version hence the _PyObject_GetDictPtr. if ( PyObject** dict = _PyObject_GetDictPtr( selfptr.get() ) ) { if ( PyDict_Update( stateptr.get(), dict[0] ) ) return 0; } // Copy __slots__ if present. This assumes copyreg._slotnames was called // during AtomMeta's initialization { PyObject* typedict = Py_TYPE(selfptr.get())->tp_dict; cppy::ptr slotnamesptr(PyDict_GetItemString(typedict, "__slotnames__"), true); if ( !slotnamesptr ) { return 0; } if ( !PyList_CheckExact(slotnamesptr.get()) ) { return cppy::system_error( "slot names" ); } for ( Py_ssize_t i=0; i < PyList_GET_SIZE(slotnamesptr.get()); i++ ) { PyObject *name = PyList_GET_ITEM(slotnamesptr.get(), i); cppy::ptr value = selfptr.getattr(name); if (!value ) { // Following CPython impl it is not an error if the attribute is // not present. continue; } else if ( PyDict_SetItem(stateptr.get(), name, value.get()) ) { return 0; } } } cppy::ptr membersptr = selfptr.getattr(atom_members); if ( !membersptr || !PyDict_CheckExact( membersptr.get() ) ) { return cppy::system_error( "atom members" ); } PyObject *name, *member; Py_ssize_t pos = 0; while ( PyDict_Next(membersptr.get(), &pos, &name, &member) ) { cppy::ptr should_gs = member_cast( member )->should_getstate( self ); if ( !should_gs ) { return 0; } int test = PyObject_IsTrue( should_gs.get() ); if ( test == 1) { cppy::ptr value = member_cast( member )->getattr( self ); if (!value || PyDict_SetItem( stateptr.get(), name, value.get() ) ) { return 0; } } else if ( test == -1 ) { return 0; } } // Frozen state if ( self->is_frozen() && PyDict_SetItem(stateptr.get(), atom_flags, Py_None) ) { return 0; } return stateptr.release(); } PyObject* CAtom_setstate( CAtom* self, PyObject* state ) { if ( !PyMapping_Check(state) ) return cppy::type_error("__setstate__ requires a mapping"); // If the -f key is present freeze the object #if PY_VERSION_HEX >= 0x030D0000 int frozen = PyMapping_HasKeyWithError( state, atom_flags ); if ( frozen == -1 ) return 0; #else int frozen = PyMapping_HasKey( state, atom_flags ); #endif if ( frozen ) { if ( PyMapping_DelItem(state, atom_flags) == -1 ) return 0; } cppy::ptr iter( PyObject_GetIter(state) ); if ( !iter ) return 0; cppy::ptr key; while ( ( key = PyIter_Next( iter.get() ) ) ) { cppy::ptr value( PyObject_GetItem( state, key.get() ) ); if ( !value ) return 0; if ( PyObject_SetAttr( pyobject_cast(self), key.get(), value.get() ) ) return 0; } if ( frozen ) self->set_frozen(true); Py_RETURN_NONE; } static PyMethodDef CAtom_methods[] = { { "notifications_enabled", ( PyCFunction )CAtom_notifications_enabled, METH_NOARGS, "Get whether notification is enabled for the atom." }, { "set_notifications_enabled", ( PyCFunction )CAtom_set_notifications_enabled, METH_O, "Enable or disable notifications for the atom." }, { "get_member", ( PyCFunction )CAtom_get_member, METH_O, "Get the named member for the atom." }, { "observe", ( PyCFunction )CAtom_observe, METH_FASTCALL, "Register an observer callback to observe changes on the given topic(s)." }, { "unobserve", ( PyCFunction )CAtom_unobserve, METH_FASTCALL, "Unregister an observer callback for the given topic(s)." }, { "has_observers", ( PyCFunction )CAtom_has_observers, METH_O, "Get whether the atom has observers for a given topic." }, { "has_observer", ( PyCFunction )CAtom_has_observer, METH_FASTCALL, "Get whether the atom has the given observer for a given topic." }, { "notify", ( PyCFunction )CAtom_notify, METH_VARARGS | METH_KEYWORDS, "Call the registered observers for a given topic with positional and keyword arguments." }, { "freeze", ( PyCFunction )CAtom_freeze, METH_NOARGS, "Freeze the atom to prevent further modifications to its attributes." }, { "__sizeof__", ( PyCFunction )CAtom_sizeof, METH_NOARGS, "__sizeof__() -> size of object in memory, in bytes" }, { "__getstate__", ( PyCFunction )CAtom_getstate, METH_NOARGS, "The base implementation of the pickle getstate protocol." }, { "__setstate__", ( PyCFunction )CAtom_setstate, METH_O, "The base implementation of the pickle setstate protocol." }, { 0 } // sentinel }; static PyType_Slot Atom_Type_slots[] = { { Py_tp_dealloc, void_cast( CAtom_dealloc ) }, /* tp_dealloc */ { Py_tp_traverse, void_cast( CAtom_traverse ) }, /* tp_traverse */ { Py_tp_clear, void_cast( CAtom_clear ) }, /* tp_clear */ { Py_tp_methods, void_cast( CAtom_methods ) }, /* tp_methods */ { Py_tp_new, void_cast( CAtom_new ) }, /* tp_new */ { Py_tp_init, void_cast( CAtom_init) }, /* tp_new */ { Py_tp_alloc, void_cast( PyType_GenericAlloc ) }, /* tp_alloc */ { Py_tp_free, void_cast( PyObject_GC_Del ) }, /* tp_free */ { 0, 0 }, }; } // namespace // Initialize static variables (otherwise the compiler eliminates them) PyTypeObject* CAtom::TypeObject = NULL; PyType_Spec CAtom::TypeObject_Spec = { PACKAGE_TYPENAME( "CAtom" ), /* tp_name */ sizeof( CAtom ), /* tp_basicsize */ 0, /* tp_itemsize */ Py_TPFLAGS_DEFAULT |Py_TPFLAGS_BASETYPE |Py_TPFLAGS_HAVE_GC, /* tp_flags */ Atom_Type_slots /* slots */ }; bool CAtom::Ready() { if( !MethodWrapper::Ready() ) { return false; } if( !AtomMethodWrapper::Ready() ) { return false; } // The reference will be handled by the module to which we will add the type TypeObject = pytype_cast( PyType_FromSpec( &TypeObject_Spec ) ); if( !TypeObject ) { return false; // LCOV_EXCL_LINE (failed type init) } atom_members = PyUnicode_InternFromString( "__atom_members__" ); if( !atom_members ) { return false; // LCOV_EXCL_LINE (failed to intern string, impossible) } atom_flags = PyUnicode_InternFromString( "--frozen" ); if( !atom_flags ) return false; // LCOV_EXCL_LINE (failed to intern string, impossible) return true; } static PyObject* wrap_callback( PyObject* callback ) { if( PyMethod_Check( callback ) && PyMethod_GET_SELF( callback ) ) return MethodWrapper::New( callback ); return cppy::incref( callback ); } bool CAtom::observe( PyObject* topic, PyObject* callback, uint8_t change_types ) { cppy::ptr topicptr( cppy::incref( topic ) ); cppy::ptr callbackptr( wrap_callback( callback ) ); if( !callbackptr ) return false; if( !observers ) observers = new ObserverPool(); observers->add( topicptr, callbackptr, change_types ); return true; } bool CAtom::unobserve( PyObject* topic, PyObject* callback ) { if( !observers ) return true; cppy::ptr topicptr( cppy::incref( topic ) ); cppy::ptr callbackptr( cppy::incref( callback ) ); observers->remove( topicptr, callbackptr ); return true; } bool CAtom::unobserve( PyObject* topic ) { if( !observers ) return true; cppy::ptr topicptr( cppy::incref( topic ) ); observers->remove( topicptr ); return true; } bool CAtom::unobserve() { if( !observers ) return true; observers->py_clear(); return true; } bool CAtom::notify( PyObject* topic, PyObject* args, PyObject* kwargs, uint8_t change_types ) { if( observers && get_notifications_enabled() ) { cppy::ptr topicptr( cppy::incref( topic ) ); cppy::ptr argsptr( cppy::incref( args ) ); cppy::ptr kwargsptr( cppy::xincref( kwargs ) ); if( !observers->notify( topicptr, argsptr, kwargsptr, change_types ) ) return false; } return true; } // shamelessly derived from qobject.h typedef std::multimap GuardMap; GLOBAL_STATIC( GuardMap, guard_map ) void CAtom::add_guard( CAtom** ptr ) { if( !*ptr ) return; GuardMap* map = guard_map(); if( !map ) { *ptr = 0; // LCOV_EXCL_LINE return; // LCOV_EXCL_LINE } map->insert( GuardMap::value_type( *ptr, ptr ) ); ( *ptr )->set_has_guards( true ); } void CAtom::remove_guard( CAtom** ptr ) { if( !*ptr ) return; GuardMap* map = guard_map(); if( !map || map->empty() ) return; bool more = false; // if the CAtom has more pointers attached to it. GuardMap::iterator it = map->find( *ptr ); const GuardMap::iterator end = map->end(); for( ; it != end && it->first == *ptr; ++it ) { if( it->second == ptr ) { if( !more ) { ++it; more = ( it != end ) && ( it->first == *ptr ); --it; } map->erase( it ); break; } more = true; } if( !more ) ( *ptr )->set_has_guards( false ); } void CAtom::change_guard( CAtom** ptr, CAtom* o ) { GuardMap* map = guard_map(); if( !map ) { *ptr = 0; // LCOV_EXCL_LINE return; // LCOV_EXCL_LINE } if( o ) { map->insert( GuardMap::value_type( o, ptr ) ); o->set_has_guards( true ); } CAtom::remove_guard( ptr ); *ptr = o; } void CAtom::clear_guards( CAtom* o ) { GuardMap* map = 0; try { map = guard_map(); } catch( std::bad_alloc& ) { // do nothing in case of OOM - code below is safe } if( !map || map->empty() ) return; GuardMap::iterator it = map->find( o ); GuardMap::iterator first = it; const GuardMap::iterator end = map->end(); for( ; it != end && it->first == o; ++it ) *it->second = 0; map->erase( first, it ); o->set_has_guards( false ); } } // namespace atom atom-0.12.1/atom/src/catom.h000066400000000000000000000101271506756731600155570ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #pragma once #include #include "platstdint.h" #include "observerpool.h" #define MAX_MEMBER_COUNT ( static_cast( 0xffff ) ) #define SLOT_COUNT_MASK ( static_cast( 0xffff ) ) #define FLAGS_MASK ( static_cast( 0xffff0000 ) ) #define NOTIFICATION_BIT ( static_cast( 1 << 16 ) ) #define GUARD_BIT ( static_cast( 1 << 17 ) ) #define ATOMREF_BIT ( static_cast( 1 << 18 ) ) #define FROZEN_BIT ( static_cast( 1 << 19 ) ) #define catom_cast( o ) ( reinterpret_cast( o ) ) namespace atom { struct CAtom { PyObject_HEAD uint32_t bitfield; // lower 16 == slot count, upper 16 == flags PyObject** slots; ObserverPool* observers; static PyType_Spec TypeObject_Spec; static PyTypeObject* TypeObject; static bool Ready(); uint32_t get_slot_count() { return bitfield & SLOT_COUNT_MASK; } void set_slot_count( uint32_t count ) { bitfield = ( bitfield & FLAGS_MASK ) | ( count & SLOT_COUNT_MASK ); } PyObject* get_slot( uint32_t index ) { return cppy::xincref( slots[ index ] ); } void set_slot( uint32_t index, PyObject* object ) { PyObject* old = slots[ index ]; slots[ index ] = object; Py_XINCREF( object ); Py_XDECREF( old ); } bool get_notifications_enabled() { return ( bitfield & NOTIFICATION_BIT ) != 0; } void set_notifications_enabled( bool enabled ) { if( enabled ) bitfield |= NOTIFICATION_BIT; else bitfield &= ~NOTIFICATION_BIT; } bool has_guards() { return ( bitfield & GUARD_BIT ) != 0; } void set_has_guards( bool has_guards ) { if( has_guards ) bitfield |= GUARD_BIT; else bitfield &= ~GUARD_BIT; } bool has_atomref() { return ( bitfield & ATOMREF_BIT ) != 0; } void set_has_atomref( bool has_ref ) { if( has_ref ) bitfield |= ATOMREF_BIT; else bitfield &= ~ATOMREF_BIT; } bool has_observers( PyObject* topic ) { if( observers ) { cppy::ptr topicptr( cppy::incref( topic ) ); return observers->has_topic( topicptr ); } return false; } bool has_observer( PyObject* topic, PyObject* callback ) { if( observers ) { cppy::ptr topicptr( cppy::incref( topic ) ); cppy::ptr callbackptr( cppy::incref( callback ) ); return observers->has_observer( topicptr, callbackptr ); } return false; } bool is_frozen() { return ( bitfield & FROZEN_BIT ) != 0; } void set_frozen( bool frozen ) { if( frozen ) bitfield |= FROZEN_BIT; else bitfield &= ~FROZEN_BIT; } bool observe( PyObject* topic, PyObject* callback ) { return observe( topic, callback, ChangeType::Any ); } bool observe( PyObject* topic, PyObject* callback, uint8_t change_types ); bool unobserve( PyObject* topic, PyObject* callback ); bool unobserve( PyObject* topic ); bool unobserve(); bool notify( PyObject* topic, PyObject* args, PyObject* kwargs ) { return notify( topic, args, kwargs, ChangeType::Any ); } bool notify( PyObject* topic, PyObject* args, PyObject* kwargs, uint8_t change_types ); static int TypeCheck( PyObject* object ) { return PyObject_TypeCheck( object, TypeObject ); } static void add_guard( CAtom** ptr ); static void remove_guard( CAtom** ptr ); static void change_guard( CAtom** ptr, CAtom* o ); static void clear_guards( CAtom* o ); }; } // namespace atom atom-0.12.1/atom/src/catommodule.cpp000066400000000000000000000144501506756731600173230ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #include #include "behaviors.h" #include "catom.h" #include "member.h" #include "memberchange.h" #include "eventbinder.h" #include "signalconnector.h" #include "atomref.h" #include "atomlist.h" #include "atomset.h" #include "atomdict.h" #include "enumtypes.h" #include "propertyhelper.h" namespace { bool ready_types() { using namespace atom; if( !AtomList::Ready() ) // LCOV_EXCL_BR_LINE { return false; // LCOV_EXCL_LINE (failed type init) } if( !AtomCList::Ready() ) // LCOV_EXCL_BR_LINE { return false; // LCOV_EXCL_LINE (failed type init) } if( !AtomDict::Ready() ) // LCOV_EXCL_BR_LINE { return false; // LCOV_EXCL_LINE (failed type init) } if( !DefaultAtomDict::Ready() ) // LCOV_EXCL_BR_LINE { return false; // LCOV_EXCL_LINE (failed type init) } if( !AtomSet::Ready() ) // LCOV_EXCL_BR_LINE { return false; // LCOV_EXCL_LINE (failed type init) } if( !AtomRef::Ready() ) // LCOV_EXCL_BR_LINE { return false; // LCOV_EXCL_LINE (failed type init) } if( !Member::Ready() ) // LCOV_EXCL_BR_LINE { return false; // LCOV_EXCL_LINE (failed type init) } if( !CAtom::Ready() ) // LCOV_EXCL_BR_LINE { return false; // LCOV_EXCL_LINE (failed type init) } if( !EventBinder::Ready() ) // LCOV_EXCL_BR_LINE { return false; // LCOV_EXCL_LINE (failed type init) } if( !SignalConnector::Ready() ) // LCOV_EXCL_BR_LINE { return false; // LCOV_EXCL_LINE (failed type init) } return true; } bool add_objects( PyObject* mod ) { using namespace atom; // atomlist cppy::ptr atom_list( pyobject_cast( AtomList::TypeObject ) ); if( PyModule_AddObject( mod, "atomlist", atom_list.get() ) < 0 ) // LCOV_EXCL_BR_LINE { return false; // LCOV_EXCL_LINE (failed type addition to module) } atom_list.release(); // atomclist cppy::ptr atom_clist( pyobject_cast( AtomCList::TypeObject ) ); if( PyModule_AddObject( mod, "atomclist", atom_clist.get() ) < 0 ) // LCOV_EXCL_BR_LINE { return false; // LCOV_EXCL_LINE (failed type addition to module) } atom_clist.release(); // atomdict cppy::ptr atom_dict( pyobject_cast( AtomDict::TypeObject ) ); if( PyModule_AddObject( mod, "atomdict", atom_dict.get() ) < 0 ) // LCOV_EXCL_BR_LINE { return false; // LCOV_EXCL_LINE (failed type addition to module) } atom_dict.release(); // defaultatomdict cppy::ptr defaultatom_dict( pyobject_cast( DefaultAtomDict::TypeObject ) ); if( PyModule_AddObject( mod, "defaultatomdict", defaultatom_dict.get() ) < 0 ) // LCOV_EXCL_BR_LINE { return false; // LCOV_EXCL_LINE (failed type addition to module) } defaultatom_dict.release(); // atomset cppy::ptr atom_set( pyobject_cast( AtomSet::TypeObject ) ); if( PyModule_AddObject( mod, "atomset", atom_set.get() ) < 0 ) // LCOV_EXCL_BR_LINE { return false; // LCOV_EXCL_LINE (failed type addition to module) } atom_set.release(); // atomref cppy::ptr atom_ref( pyobject_cast( AtomRef::TypeObject ) ); if( PyModule_AddObject( mod, "atomref", atom_ref.get() ) < 0 ) // LCOV_EXCL_BR_LINE { return false; // LCOV_EXCL_LINE (failed type addition to module) } atom_ref.release(); // Member cppy::ptr member( pyobject_cast( Member::TypeObject ) ); if( PyModule_AddObject( mod, "Member", member.get() ) < 0 ) // LCOV_EXCL_BR_LINE { return false; // LCOV_EXCL_LINE (failed type addition to module) } member.release(); // CAtom cppy::ptr catom( pyobject_cast( CAtom::TypeObject ) ); if( PyModule_AddObject( mod, "CAtom", catom.get() ) < 0 ) { return false; // LCOV_EXCL_LINE (failed type addition to module) } catom.release(); cppy::incref( PyGetAttr ); cppy::incref( PySetAttr ); cppy::incref( PyDelAttr ); cppy::incref( PyPostGetAttr ); cppy::incref( PyPostSetAttr ); cppy::incref( PyDefaultValue ); cppy::incref( PyValidate ); cppy::incref( PyPostValidate ); cppy::incref( PyGetState ); cppy::incref( PyChangeType ); PyModule_AddObject( mod, "GetAttr", PyGetAttr ); PyModule_AddObject( mod, "SetAttr", PySetAttr ); PyModule_AddObject( mod, "DelAttr", PyDelAttr ); PyModule_AddObject( mod, "PostGetAttr", PyPostGetAttr ); PyModule_AddObject( mod, "PostSetAttr", PyPostSetAttr ); PyModule_AddObject( mod, "DefaultValue", PyDefaultValue ); PyModule_AddObject( mod, "Validate", PyValidate ); PyModule_AddObject( mod, "PostValidate", PyPostValidate ); PyModule_AddObject( mod, "GetState", PyGetState ); PyModule_AddObject( mod, "ChangeType", PyChangeType ); return true; } int catom_modexec( PyObject *mod ) { if( !ready_types() ) // LCOV_EXCL_BR_LINE { return -1; // LCOV_EXCL_LINE (failed type creation) } if( !atom::init_enumtypes() ) // LCOV_EXCL_BR_LINE { return -1; // LCOV_EXCL_LINE (failed enum creation) } if( !atom::init_memberchange() ) // LCOV_EXCL_BR_LINE { return -1; // LCOV_EXCL_LINE (failed type creation) } if( !atom::init_containerlistchange() ) // LCOV_EXCL_BR_LINE { return -1; // LCOV_EXCL_LINE (failed type creation) } if( !add_objects( mod ) ) // LCOV_EXCL_BR_LINE { return -1; // LCOV_EXCL_LINE (failed type addition to module) } return 0; } PyMethodDef catom_methods[] = { { "reset_property", ( PyCFunction )atom::reset_property, METH_VARARGS, "Reset a Property member. For internal use only!" }, { 0 } // Sentinel }; PyModuleDef_Slot catom_slots[] = { {Py_mod_exec, reinterpret_cast( catom_modexec ) }, {0, NULL} }; struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "catom", "catom extension module", 0, catom_methods, catom_slots, NULL, NULL, NULL }; } // namespace PyMODINIT_FUNC PyInit_catom( void ) { return PyModuleDef_Init( &moduledef ); } atom-0.12.1/atom/src/catompointer.h000066400000000000000000000030241506756731600171560ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #pragma once #include "catom.h" namespace atom { // Shamelessly derived from qpointer.h class CAtomPointer { public: inline CAtomPointer() : o( 0 ) {} inline CAtomPointer( CAtom* p ) : o( p ) { CAtom::add_guard( &o ); } inline CAtomPointer( const CAtomPointer& p ) : o( p.o ) { CAtom::add_guard( &o ); } inline ~CAtomPointer() { CAtom::remove_guard( &o ); } inline CAtomPointer& operator=( const CAtomPointer &p ) { if( this != &p ) CAtom::change_guard( &o, p.o ); return *this; } inline CAtomPointer& operator=( CAtom* p ) { if( o != p ) CAtom::change_guard( &o, p ); return *this; } inline bool is_null() const { return !o; } inline CAtom* operator->() const { return const_cast( o ); } inline CAtom& operator*() const { return *const_cast( o ); } inline operator CAtom*() const { return const_cast( o ); } inline CAtom* data() const { return const_cast( o ); } private: CAtom* o; }; } // namespace atom atom-0.12.1/atom/src/defaultvaluebehavior.cpp000066400000000000000000000124421506756731600212120ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #include #include "member.h" namespace atom { bool Member::check_context( DefaultValue::Mode mode, PyObject* context ) { switch( mode ) { case DefaultValue::List: if( context != Py_None && !PyList_Check( context ) ) { cppy::type_error( context, "list or None" ); return false; } break; case DefaultValue::Set: if( context != Py_None && !PyAnySet_Check( context ) ) { cppy::type_error( context, "set or None" ); return false; } break; case DefaultValue::Dict: case DefaultValue::DefaultDict: if( context != Py_None && !PyDict_Check( context ) ) { cppy::type_error( context, "dict or None" ); return false; } break; case DefaultValue::Delegate: if( !Member::TypeCheck( context ) ) { cppy::type_error( context, "Member" ); return false; } break; case DefaultValue::CallObject: case DefaultValue::CallObject_Object: case DefaultValue::CallObject_ObjectName: if( !PyCallable_Check( context ) ) { cppy::type_error( context, "callable" ); return false; } break; case DefaultValue::ObjectMethod: case DefaultValue::ObjectMethod_Name: case DefaultValue::MemberMethod_Object: if( !PyUnicode_Check( context ) ) { cppy::type_error( context, "str" ); return false; } break; default: break; } return true; } namespace { PyObject* no_op_handler( Member* member, CAtom* atom ) { return cppy::incref( Py_None ); } PyObject* static_handler( Member* member, CAtom* atom ) { return cppy::incref( member->default_value_context ); } PyObject* list_handler( Member* member, CAtom* atom ) { if( member->default_value_context == Py_None ) return PyList_New( 0 ); Py_ssize_t size = PyList_GET_SIZE( member->default_value_context ); return PyList_GetSlice( member->default_value_context, 0, size ); } PyObject* set_handler( Member* member, CAtom* atom ) { if( member->default_value_context == Py_None ) return PySet_New( 0 ); return PySet_New( member->default_value_context ); } PyObject* dict_handler( Member* member, CAtom* atom ) { if( member->default_value_context == Py_None ) return PyDict_New(); return PyDict_Copy( member->default_value_context ); } PyObject* delegate_handler( Member* member, CAtom* atom ) { Member* delegate = member_cast( member->default_value_context ); return delegate->default_value( atom ); } PyObject* non_optional_handler( Member* member, CAtom* atom ) { PyErr_Format( PyExc_ValueError, "The '%s' member on the '%s' object is not optional but no default value " "was provided and the member was not set before being accessed.", PyUnicode_AsUTF8( member->name ), Py_TYPE( pyobject_cast( atom ) )->tp_name ); return 0; } PyObject* call_object_handler( Member* member, CAtom* atom ) { return PyObject_CallNoArgs( member->default_value_context ); } PyObject* call_object_object_handler( Member* member, CAtom* atom ) { return PyObject_CallOneArg( member->default_value_context, pyobject_cast( atom ) ); } PyObject* call_object_object_name_handler( Member* member, CAtom* atom ) { PyObject* args[] = { pyobject_cast( atom ), member->name }; return PyObject_Vectorcall( member->default_value_context, args, 2, 0 ); } PyObject* object_method_handler( Member* member, CAtom* atom ) { return PyObject_CallMethodNoArgs( pyobject_cast( atom ), member->default_value_context ); } PyObject* object_method_name_handler( Member* member, CAtom* atom ) { return PyObject_CallMethodOneArg( pyobject_cast( atom ), member->default_value_context, member->name ); } PyObject* member_method_object_handler( Member* member, CAtom* atom ) { return PyObject_CallMethodOneArg( pyobject_cast( member ), member->default_value_context, pyobject_cast( atom ) ); } typedef PyObject* ( *handler )( Member* member, CAtom* atom ); static handler handlers[] = { no_op_handler, static_handler, list_handler, set_handler, dict_handler, dict_handler, non_optional_handler, delegate_handler, call_object_handler, call_object_object_handler, call_object_object_name_handler, object_method_handler, object_method_name_handler, member_method_object_handler }; } // namespace PyObject* Member::default_value( CAtom* atom ) { if( get_default_value_mode() >= sizeof( handlers ) ) return no_op_handler( this, atom ); // LCOV_EXCL_LINE return handlers[ get_default_value_mode() ]( this, atom ); } } // namespace atom atom-0.12.1/atom/src/delattrbehavior.cpp000066400000000000000000000111331506756731600201640ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #include #include "member.h" #include "memberchange.h" namespace atom { bool Member::check_context( DelAttr::Mode mode, PyObject* context ) { switch( mode ) { case DelAttr::Delegate: if( !Member::TypeCheck( context ) ) { cppy::type_error( context, "Member" ); return false; } break; case DelAttr::Property: if( context != Py_None && !PyCallable_Check( context ) ) { cppy::type_error( context, "callable or None" ); return false; } break; default: break; } return true; } namespace { int no_op_handler( Member* member, CAtom* atom ) { return 0; } PyObject* deleted_args( CAtom* atom, Member* member, PyObject* value ) { cppy::ptr argsptr( PyTuple_New( 1 ) ); if( !argsptr ) return 0; cppy::ptr change( MemberChange::deleted( atom, member, value ) ); if( !change ) return 0; PyTuple_SET_ITEM( argsptr.get(), 0, change.release() ); return argsptr.release(); } int slot_handler( Member* member, CAtom* atom ) { if( member->index >= atom->get_slot_count() ) { cppy::attribute_error( pyobject_cast( atom ), (char const *)PyUnicode_AsUTF8( member->name ) ); return -1; } if( atom->is_frozen() ) { PyErr_SetString( PyExc_AttributeError, "can't delete attribute of frozen Atom" ); return -1; } cppy::ptr valueptr( atom->get_slot( member->index ) ); if( !valueptr ) return 0; atom->set_slot( member->index, 0 ); if( atom->get_notifications_enabled() ) { cppy::ptr argsptr; if( member->has_observers( ChangeType::Delete ) ) { argsptr = deleted_args( atom, member, valueptr.get() ); if( !argsptr ) return -1; if( !member->notify( atom, argsptr.get(), 0, ChangeType::Delete ) ) return -1; } if( atom->has_observers( member->name ) ) { if( !argsptr ) { argsptr = deleted_args( atom, member, valueptr.get() ); if( !argsptr ) return -1; } if( !atom->notify( member->name, argsptr.get(), 0, ChangeType::Delete ) ) return -1; } } return 0; } int constant_handler( Member* member, CAtom* atom ) { cppy::type_error( "cannot delete the value of a constant member" ); return -1; } int read_only_handler( Member* member, CAtom* atom ) { cppy::type_error( "cannot delete the value of a read only member" ); return -1; } int event_handler( Member* member, CAtom* atom ) { cppy::type_error( "cannot delete the value of an event" ); return -1; } int signal_handler( Member* member, CAtom* atom ) { cppy::type_error( "cannot delete the value of a signal" ); return -1; } int delegate_handler( Member* member, CAtom* atom ) { Member* delegate = member_cast( member->delattr_context ); return delegate->delattr( atom ); } int _mangled_property_handler( Member* member, CAtom* atom ) { char* suffix = (char *)PyUnicode_AsUTF8( member->name ); cppy::ptr name( PyUnicode_FromFormat( "_del_%s", suffix ) ); if( !name ) return -1; cppy::ptr ok( PyObject_CallMethodNoArgs( pyobject_cast( atom ), name.get() ) ); if( !ok ) return -1; return 0; } int property_handler( Member* member, CAtom* atom ) { if( member->delattr_context != Py_None ) { cppy::ptr ok( PyObject_CallOneArg( member->delattr_context, pyobject_cast( atom ) ) ); if( !ok ) return -1; return 0; } return _mangled_property_handler( member, atom ); } typedef int ( *handler )( Member* member, CAtom* atom ); static handler handlers[] = { no_op_handler, slot_handler, constant_handler, read_only_handler, event_handler, signal_handler, delegate_handler, property_handler }; } // namespace int Member::delattr( CAtom* atom ) { if( get_delattr_mode() >= sizeof( handlers ) ) return no_op_handler( this, atom ); // LCOV_EXCL_LINE return handlers[ get_delattr_mode() ]( this, atom ); } } // namespace atom atom-0.12.1/atom/src/enumtypes.cpp000066400000000000000000000314101506756731600170360ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #include #include "enumtypes.h" #include "observer.h" #include "packagenaming.h" #define expand_enum( e ) #e, e namespace atom { PyObject* PyGetAttr = 0; PyObject* PySetAttr = 0; PyObject* PyDelAttr = 0; PyObject* PyPostGetAttr = 0; PyObject* PyPostSetAttr = 0; PyObject* PyDefaultValue = 0; PyObject* PyValidate = 0; PyObject* PyPostValidate = 0; PyObject* PyGetState = 0; PyObject* PyChangeType = 0; namespace { template inline bool add_long( cppy::ptr& dict_ptr, const char* name, T value ) { cppy::ptr pyint( PyLong_FromLong( static_cast( value ) ) ); if( !pyint ) { return false; // LCOV_EXCL_LINE (failed long creation, impossible) } if( PyDict_SetItemString( dict_ptr.get(), name, pyint.get() ) != 0 ) { return false; // LCOV_EXCL_LINE (failed dict insertion, impossible) } pyint.release(); // Release the reference since the operation succeeded return true; } inline PyObject* make_enum( cppy::ptr& enum_cls, const char* name, cppy::ptr& dict_ptr ) { cppy::ptr pyname( PyUnicode_FromString( name ) ); if( !pyname ) { return 0; // LCOV_EXCL_LINE (failed name creation) } cppy::ptr pydict( PyDict_Copy( dict_ptr.get() ) ); if( !pydict ) { return 0; // LCOV_EXCL_LINE (failed dict copy) } cppy::ptr modname( PyUnicode_FromString( PACKAGE_PREFIX ) ); if( !modname ) { return 0; // LCOV_EXCL_LINE (failed module name creation) } cppy::ptr kwargs( PyDict_New() ); if( !kwargs ) { return 0; // LCOV_EXCL_LINE (failed kwargs creation, impossible) } if( PyDict_SetItemString( kwargs.get(), "module", modname.get() ) != 0 ) { return 0; // LCOV_EXCL_LINE (failed kwargs insertion, impossible) } cppy::ptr callargs( PyTuple_Pack( 2, pyname.get(), pydict.get() ) ); if( !callargs ) { return 0; // LCOV_EXCL_LINE (failed call args creation) } cppy::ptr enumclass( enum_cls.call( callargs, kwargs ) ); if( !enumclass ) { return 0; // LCOV_EXCL_LINE (failed enum creation) } return enumclass.release(); } } // namespace bool init_enumtypes() { cppy::ptr intenum_mod( PyImport_ImportModule( "enum" ) ); if( !intenum_mod ) { return false; // LCOV_EXCL_LINE (failed to import enum module, impossible) } cppy::ptr enum_cls( intenum_mod.getattr( "IntEnum" ) ); if( !enum_cls ) { return false; // LCOV_EXCL_LINE (failed to load IntEnum class, impossible } cppy::ptr flag_cls( intenum_mod.getattr( "IntFlag" ) ); if( !flag_cls ) { return false; // LCOV_EXCL_LINE (failed to load IntFlag class, impossible } { using namespace GetAttr; cppy::ptr dict_ptr( PyDict_New() ); if( !dict_ptr ) { return false; // LCOV_EXCL_LINE } add_long( dict_ptr, expand_enum( NoOp ) ); add_long( dict_ptr, expand_enum( Slot ) ); add_long( dict_ptr, expand_enum( Event ) ); add_long( dict_ptr, expand_enum( Signal ) ); add_long( dict_ptr, expand_enum( Delegate ) ); add_long( dict_ptr, expand_enum( Property ) ); add_long( dict_ptr, expand_enum( CachedProperty ) ); add_long( dict_ptr, expand_enum( CallObject_Object ) ); add_long( dict_ptr, expand_enum( CallObject_ObjectName ) ); add_long( dict_ptr, expand_enum( ObjectMethod ) ); add_long( dict_ptr, expand_enum( ObjectMethod_Name ) ); add_long( dict_ptr, expand_enum( MemberMethod_Object ) ); PyGetAttr = make_enum( enum_cls, "GetAttr", dict_ptr ); if( !PyGetAttr ) { return false; // LCOV_EXCL_LINE (enum creation failed, impossible) } } { using namespace SetAttr; cppy::ptr dict_ptr( PyDict_New() ); if( !dict_ptr ) { return false; // LCOV_EXCL_LINE } add_long( dict_ptr, expand_enum( NoOp ) ); add_long( dict_ptr, expand_enum( Slot ) ); add_long( dict_ptr, expand_enum( Constant ) ); add_long( dict_ptr, expand_enum( ReadOnly ) ); add_long( dict_ptr, expand_enum( Event ) ); add_long( dict_ptr, expand_enum( Signal ) ); add_long( dict_ptr, expand_enum( Delegate ) ); add_long( dict_ptr, expand_enum( Property ) ); add_long( dict_ptr, expand_enum( CallObject_ObjectValue ) ); add_long( dict_ptr, expand_enum( CallObject_ObjectNameValue ) ); add_long( dict_ptr, expand_enum( ObjectMethod_Value ) ); add_long( dict_ptr, expand_enum( ObjectMethod_NameValue ) ); add_long( dict_ptr, expand_enum( MemberMethod_ObjectValue ) ); PySetAttr = make_enum( enum_cls, "SetAttr", dict_ptr ); if( !PySetAttr ) { return false; // LCOV_EXCL_LINE (enum creation failed, impossible) } } { using namespace DelAttr; cppy::ptr dict_ptr( PyDict_New() ); if( !dict_ptr ) { return false; // LCOV_EXCL_LINE } add_long( dict_ptr, expand_enum( NoOp ) ); add_long( dict_ptr, expand_enum( Slot ) ); add_long( dict_ptr, expand_enum( Constant ) ); add_long( dict_ptr, expand_enum( ReadOnly ) ); add_long( dict_ptr, expand_enum( Event ) ); add_long( dict_ptr, expand_enum( Signal ) ); add_long( dict_ptr, expand_enum( Delegate ) ); add_long( dict_ptr, expand_enum( Property ) ); PyDelAttr = make_enum( enum_cls, "DelAttr", dict_ptr ); if( !PyDelAttr ) { return false; // LCOV_EXCL_LINE (enum creation failed, impossible) } } { using namespace PostGetAttr; cppy::ptr dict_ptr( PyDict_New() ); if( !dict_ptr ) { return false; // LCOV_EXCL_LINE } add_long( dict_ptr, expand_enum( NoOp ) ); add_long( dict_ptr, expand_enum( Delegate ) ); add_long( dict_ptr, expand_enum( ObjectMethod_Value ) ); add_long( dict_ptr, expand_enum( ObjectMethod_NameValue ) ); add_long( dict_ptr, expand_enum( MemberMethod_ObjectValue ) ); PyPostGetAttr = make_enum( enum_cls, "PostGetAttr", dict_ptr ); if( !PyPostGetAttr ) { return false; // LCOV_EXCL_LINE (enum creation failed, impossible) } } { using namespace PostSetAttr; cppy::ptr dict_ptr( PyDict_New() ); if( !dict_ptr ) { return false; // LCOV_EXCL_LINE } add_long( dict_ptr, expand_enum( NoOp ) ); add_long( dict_ptr, expand_enum( Delegate ) ); add_long( dict_ptr, expand_enum( ObjectMethod_OldNew ) ); add_long( dict_ptr, expand_enum( ObjectMethod_NameOldNew ) ); add_long( dict_ptr, expand_enum( MemberMethod_ObjectOldNew ) ); PyPostSetAttr = make_enum( enum_cls, "PostSetAttr", dict_ptr ); if( !PyPostSetAttr ) { return false; // LCOV_EXCL_LINE (enum creation failed, impossible) } } { using namespace DefaultValue; cppy::ptr dict_ptr( PyDict_New() ); if( !dict_ptr ) { return false; // LCOV_EXCL_LINE } add_long( dict_ptr, expand_enum( NoOp ) ); add_long( dict_ptr, expand_enum( Static ) ); add_long( dict_ptr, expand_enum( List ) ); add_long( dict_ptr, expand_enum( Set ) ); add_long( dict_ptr, expand_enum( Dict ) ); add_long( dict_ptr, expand_enum( DefaultDict ) ); add_long( dict_ptr, expand_enum( NonOptional ) ); add_long( dict_ptr, expand_enum( Delegate ) ); add_long( dict_ptr, expand_enum( CallObject ) ); add_long( dict_ptr, expand_enum( CallObject_Object ) ); add_long( dict_ptr, expand_enum( CallObject_ObjectName ) ); add_long( dict_ptr, expand_enum( ObjectMethod ) ); add_long( dict_ptr, expand_enum( ObjectMethod_Name ) ); add_long( dict_ptr, expand_enum( MemberMethod_Object ) ); PyDefaultValue = make_enum( enum_cls, "DefaultValue", dict_ptr ); if( !PyDefaultValue ) { return false; // LCOV_EXCL_LINE (enum creation failed, impossible) } } { using namespace Validate; cppy::ptr dict_ptr( PyDict_New() ); if( !dict_ptr ) { return false; // LCOV_EXCL_LINE } add_long( dict_ptr, expand_enum( NoOp ) ); add_long( dict_ptr, expand_enum( Bool ) ); add_long( dict_ptr, expand_enum( Int ) ); add_long( dict_ptr, expand_enum( IntPromote ) ); add_long( dict_ptr, expand_enum( Float ) ); add_long( dict_ptr, expand_enum( FloatPromote ) ); add_long( dict_ptr, expand_enum( Bytes ) ); add_long( dict_ptr, expand_enum( BytesPromote ) ); add_long( dict_ptr, expand_enum( Str ) ); add_long( dict_ptr, expand_enum( StrPromote ) ); add_long( dict_ptr, expand_enum( Tuple ) ); add_long( dict_ptr, expand_enum( FixedTuple ) ); add_long( dict_ptr, expand_enum( List ) ); add_long( dict_ptr, expand_enum( ContainerList ) ); add_long( dict_ptr, expand_enum( Set ) ); add_long( dict_ptr, expand_enum( Dict ) ); add_long( dict_ptr, expand_enum( DefaultDict ) ); add_long( dict_ptr, expand_enum( OptionalInstance ) ); add_long( dict_ptr, expand_enum( Instance ) ); add_long( dict_ptr, expand_enum( OptionalTyped ) ); add_long( dict_ptr, expand_enum( Typed ) ); add_long( dict_ptr, expand_enum( Subclass ) ); add_long( dict_ptr, expand_enum( Enum ) ); add_long( dict_ptr, expand_enum( Callable ) ); add_long( dict_ptr, expand_enum( FloatRange ) ); add_long( dict_ptr, expand_enum( FloatRangePromote ) ); add_long( dict_ptr, expand_enum( Range ) ); add_long( dict_ptr, expand_enum( Coerced ) ); add_long( dict_ptr, expand_enum( Delegate ) ); add_long( dict_ptr, expand_enum( ObjectMethod_OldNew ) ); add_long( dict_ptr, expand_enum( ObjectMethod_NameOldNew ) ); add_long( dict_ptr, expand_enum( MemberMethod_ObjectOldNew ) ); PyValidate = make_enum( enum_cls, "Validate", dict_ptr ); if( !PyValidate ) { return false; // LCOV_EXCL_LINE (enum creation failed, impossible) } } { using namespace PostValidate; cppy::ptr dict_ptr( PyDict_New() ); if( !dict_ptr ) { return false; // LCOV_EXCL_LINE } add_long( dict_ptr, expand_enum( NoOp ) ); add_long( dict_ptr, expand_enum( Delegate ) ); add_long( dict_ptr, expand_enum( ObjectMethod_OldNew ) ); add_long( dict_ptr, expand_enum( ObjectMethod_NameOldNew ) ); add_long( dict_ptr, expand_enum( MemberMethod_ObjectOldNew ) ); PyPostValidate = make_enum( enum_cls, "PostValidate", dict_ptr ); if( !PyPostValidate ) { return false; // LCOV_EXCL_LINE (enum creation failed, impossible) } } { using namespace ChangeType; cppy::ptr dict_ptr( PyDict_New() ); if( !dict_ptr ) { return false; // LCOV_EXCL_LINE } add_long( dict_ptr, "CREATE", Create ); add_long( dict_ptr, "UPDATE", Update ); add_long( dict_ptr, "DELETE", Delete ); add_long( dict_ptr, "EVENT", Event ); add_long( dict_ptr, "PROPERTY", Property ); add_long( dict_ptr, "CONTAINER", Container ); add_long( dict_ptr, "ANY", Any ); PyChangeType = make_enum( flag_cls, "ChangeType", dict_ptr ); if( !PyChangeType ) { return false; // LCOV_EXCL_LINE (enum creation failed, impossible) } } { using namespace GetState; cppy::ptr dict_ptr( PyDict_New() ); if( !dict_ptr ) { return false; // LCOV_EXCL_LINE } add_long( dict_ptr, expand_enum( Exclude ) ); add_long( dict_ptr, expand_enum( Include ) ); add_long( dict_ptr, expand_enum( IncludeNonDefault ) ); add_long( dict_ptr, expand_enum( Property ) ); add_long( dict_ptr, expand_enum( ObjectMethod_Name ) ); add_long( dict_ptr, expand_enum( MemberMethod_Object ) ); PyGetState = make_enum( enum_cls, "GetState", dict_ptr ); if( !PyGetState ) { return false; // LCOV_EXCL_LINE (enum creation failed, impossible) } } return true; } } // namespace atom atom-0.12.1/atom/src/enumtypes.h000066400000000000000000000077251506756731600165170ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #pragma once #include #include "behaviors.h" namespace atom { extern PyObject* PyGetAttr; extern PyObject* PySetAttr; extern PyObject* PyDelAttr; extern PyObject* PyPostGetAttr; extern PyObject* PyPostSetAttr; extern PyObject* PyDefaultValue; extern PyObject* PyValidate; extern PyObject* PyPostValidate; extern PyObject* PyGetState; bool init_enumtypes(); namespace EnumTypes { template inline bool _from_py_enum( PyObject* value, PyObject* py_type, T& out ) { PyTypeObject* ob_type = pytype_cast( py_type ); if( !PyObject_TypeCheck( value, ob_type ) ) { cppy::type_error( value, ob_type->tp_name ); return false; } long lval = PyLong_AsLong( value ); if( lval == -1 && PyErr_Occurred() ) return false; out = static_cast( lval ); return true; } template inline bool from_py_enum( PyObject* value, T& out ); template<> inline bool from_py_enum( PyObject* value, GetAttr::Mode& out ) { return _from_py_enum( value, PyGetAttr, out ); } template<> inline bool from_py_enum( PyObject* value, SetAttr::Mode& out ) { return _from_py_enum( value, PySetAttr, out ); } template<> inline bool from_py_enum( PyObject* value, DelAttr::Mode& out ) { return _from_py_enum( value, PyDelAttr, out ); } template<> inline bool from_py_enum( PyObject* value, PostGetAttr::Mode& out ) { return _from_py_enum( value, PyPostGetAttr, out ); } template<> inline bool from_py_enum( PyObject* value, PostSetAttr::Mode& out ) { return _from_py_enum( value, PyPostSetAttr, out ); } template<> inline bool from_py_enum( PyObject* value, DefaultValue::Mode& out ) { return _from_py_enum( value, PyDefaultValue, out ); } template<> inline bool from_py_enum( PyObject* value, Validate::Mode& out ) { return _from_py_enum( value, PyValidate, out ); } template<> inline bool from_py_enum( PyObject* value, PostValidate::Mode& out ) { return _from_py_enum( value, PyPostValidate, out ); } template<> inline bool from_py_enum( PyObject* value, GetState::Mode& out ) { return _from_py_enum( value, PyGetState, out ); } template inline PyObject* _to_py_enum( T value, PyObject* py_enum_class ) { cppy::ptr py_int( PyLong_FromLong( static_cast( value ) ) ); if( !py_int ) return 0; cppy::ptr py_args( PyTuple_New( 1 ) ); if( !py_args ) return 0; PyTuple_SET_ITEM( py_args.get(), 0, py_int.release() ); return PyObject_Call( py_enum_class, py_args.get(), 0 ); } template inline PyObject* to_py_enum( T value ); template<> inline PyObject* to_py_enum( GetAttr::Mode value ) { return _to_py_enum( value, PyGetAttr ); } template<> inline PyObject* to_py_enum( SetAttr::Mode value ) { return _to_py_enum( value, PySetAttr ); } template<> inline PyObject* to_py_enum( DelAttr::Mode value ) { return _to_py_enum( value, PyDelAttr ); } template<> inline PyObject* to_py_enum( PostGetAttr::Mode value ) { return _to_py_enum( value, PyPostGetAttr ); } template<> inline PyObject* to_py_enum( PostSetAttr::Mode value ) { return _to_py_enum( value, PyPostSetAttr ); } template<> inline PyObject* to_py_enum( DefaultValue::Mode value ) { return _to_py_enum( value, PyDefaultValue ); } template<> inline PyObject* to_py_enum( Validate::Mode value ) { return _to_py_enum( value, PyValidate ); } template<> inline PyObject* to_py_enum( PostValidate::Mode value ) { return _to_py_enum( value, PyPostValidate ); } template<> inline PyObject* to_py_enum( GetState::Mode value ) { return _to_py_enum( value, PyGetState ); } } // namespace EnumTypes } // namespace atom atom-0.12.1/atom/src/eventbinder.cpp000066400000000000000000000117761506756731600173270ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #include #include "eventbinder.h" #include "packagenaming.h" namespace atom { namespace { #define FREELIST_MAX 128 static int numfree = 0; static EventBinder* freelist[ FREELIST_MAX ]; void EventBinder_clear( EventBinder* self ) { Py_CLEAR( self->member ); Py_CLEAR( self->atom ); } int EventBinder_traverse( EventBinder* self, visitproc visit, void* arg ) { Py_VISIT( self->member ); Py_VISIT( self->atom ); #if PY_VERSION_HEX >= 0x03090000 // This was not needed before Python 3.9 (Python issue 35810 and 40217) Py_VISIT(Py_TYPE(self)); #endif return 0; } void EventBinder_dealloc( EventBinder* self ) { PyObject_GC_UnTrack( self ); EventBinder_clear( self ); if( numfree < FREELIST_MAX ) freelist[ numfree++ ] = self; else Py_TYPE(self)->tp_free( pyobject_cast( self ) ); } PyObject* EventBinder_richcompare( EventBinder* self, PyObject* other, int op ) { if( op == Py_EQ ) { if( EventBinder::TypeCheck( other ) ) { EventBinder* binder = reinterpret_cast( other ); if( self->member == binder->member && self->atom == binder->atom ) Py_RETURN_TRUE; Py_RETURN_FALSE; } else Py_RETURN_FALSE; } Py_RETURN_NOTIMPLEMENTED; } PyObject* EventBinder_bind( EventBinder* self, PyObject* callback ) { if( !self->atom->observe( self->member->name, callback ) ) return 0; Py_RETURN_NONE; } PyObject* EventBinder_unbind( EventBinder* self, PyObject* callback ) { if( !self->atom->unobserve( self->member->name, callback ) ) return 0; Py_RETURN_NONE; } PyObject* EventBinder__call__( EventBinder* self, PyObject* args, PyObject* kwargs ) { if( kwargs && ( PyDict_Size( kwargs ) > 0 ) ) return cppy::type_error( "An event cannot be triggered with keyword arguments" ); Py_ssize_t size = PyTuple_GET_SIZE( args ); if( size > 1 ) return cppy::type_error( "An event can be triggered with at most 1 argument" ); PyObject* value = size == 0 ? Py_None : PyTuple_GET_ITEM( args, 0 ); if( self->member->setattr( self->atom, value ) < 0 ) return 0; Py_RETURN_NONE; } static PyMethodDef EventBinder_methods[] = { { "bind", ( PyCFunction )EventBinder_bind, METH_O, "Bind a handler to the event. This is equivalent to observing the event." }, { "unbind", ( PyCFunction )EventBinder_unbind, METH_O, "Unbind a handler from the event. This is equivalent to unobserving the event." }, { 0 } // sentinel }; static PyType_Slot EventBinder_Type_slots[] = { { Py_tp_dealloc, void_cast( EventBinder_dealloc ) }, /* tp_dealloc */ { Py_tp_traverse, void_cast( EventBinder_traverse ) }, /* tp_traverse */ { Py_tp_clear, void_cast( EventBinder_clear ) }, /* tp_clear */ { Py_tp_methods, void_cast( EventBinder_methods ) }, /* tp_methods */ { Py_tp_call, void_cast( EventBinder__call__ ) }, /* tp_call */ { Py_tp_richcompare, void_cast( EventBinder_richcompare ) }, /* tp_richcompare */ { Py_tp_alloc, void_cast( PyType_GenericAlloc ) }, /* tp_alloc */ { Py_tp_free, void_cast( PyObject_GC_Del ) }, /* tp_free */ { 0, 0 }, }; } // namespace // Initialize static variables (otherwise the compiler eliminates them) PyTypeObject* EventBinder::TypeObject = NULL; PyType_Spec EventBinder::TypeObject_Spec = { PACKAGE_TYPENAME( "EventBinder" ), /* tp_name */ sizeof( EventBinder ), /* tp_basicsize */ 0, /* tp_itemsize */ Py_TPFLAGS_DEFAULT| Py_TPFLAGS_HAVE_GC, /* tp_flags */ EventBinder_Type_slots /* slots */ }; bool EventBinder::Ready() { // The reference will be handled by the module to which we will add the type TypeObject = pytype_cast( PyType_FromSpec( &TypeObject_Spec ) ); if( !TypeObject ) { return false; } return true; } PyObject* EventBinder::New( Member* member, CAtom* atom ) { PyObject* pybinder; if( numfree > 0 ) { pybinder = pyobject_cast( freelist[ --numfree ] ); _Py_NewReference( pybinder ); } else { pybinder = PyType_GenericAlloc( TypeObject, 0 ); if( !pybinder ) { return 0; // LCOV_EXCL_LINE (allocation failed, impossible) } } Py_INCREF( pyobject_cast( atom ) ); Py_INCREF( pyobject_cast( member ) ); EventBinder* binder = reinterpret_cast( pybinder ); binder->member = member; binder->atom = atom; return pybinder; } } // namespace atom atom-0.12.1/atom/src/eventbinder.h000066400000000000000000000015061506756731600167620ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #pragma once #include "catom.h" #include "member.h" namespace atom { // POD struct - all member fields are considered private struct EventBinder { PyObject_HEAD Member* member; CAtom* atom; static PyType_Spec TypeObject_Spec; static PyTypeObject* TypeObject; static bool Ready(); static PyObject* New( Member* member, CAtom* atom ); static bool TypeCheck( PyObject* ob ) { return PyObject_TypeCheck( ob, TypeObject ) != 0; } }; } // namespace atom atom-0.12.1/atom/src/getattrbehavior.cpp000066400000000000000000000157171506756731600202130ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Atom Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #include #include "eventbinder.h" #include "member.h" #include "memberchange.h" #include "signalconnector.h" namespace atom { bool Member::check_context( GetAttr::Mode mode, PyObject* context ) { switch( mode ) { case GetAttr::Delegate: if( !Member::TypeCheck( context ) ) { cppy::type_error( context, "Member" ); return false; } break; case GetAttr::Property: case GetAttr::CachedProperty: if( context != Py_None && !PyCallable_Check( context ) ) { cppy::type_error( context, "callable or None" ); return false; } break; case GetAttr::CallObject_Object: case GetAttr::CallObject_ObjectName: if( !PyCallable_Check( context ) ) { cppy::type_error( context, "callable" ); return false; } break; case GetAttr::ObjectMethod: case GetAttr::ObjectMethod_Name: case GetAttr::MemberMethod_Object: if( !PyUnicode_Check( context ) ) { cppy::type_error( context, "str" ); return false; } break; default: break; } return true; } namespace { PyObject* no_op_handler( Member* member, CAtom* atom ) { return cppy::incref( Py_None ); } PyObject* created_args( CAtom* atom, Member* member, PyObject* value ) { cppy::ptr argsptr( PyTuple_New( 1 ) ); if( !argsptr ) return 0; cppy::ptr change( MemberChange::created( atom, member, value ) ); if( !change ) return 0; PyTuple_SET_ITEM( argsptr.get(), 0, change.release() ); return argsptr.release(); } PyObject* slot_handler( Member* member, CAtom* atom ) { if( member->index >= atom->get_slot_count() ) return cppy::attribute_error( pyobject_cast( atom ), (char const *)PyUnicode_AsUTF8( member->name ) ); cppy::ptr value( atom->get_slot( member->index ) ); if( value ) { if( member->get_post_getattr_mode() ) value = member->post_getattr( atom, value.get() ); return value.release(); } value = member->default_value( atom ); if( !value ) return 0; value = member->full_validate( atom, Py_None, value.get() ); if( !value ) return 0; atom->set_slot( member->index, value.get() ); if( atom->get_notifications_enabled() ) { cppy::ptr argsptr; if( member->has_observers( ChangeType::Create ) ) { argsptr = created_args( atom, member, value.get() ); if( !argsptr ) return 0; if( !member->notify( atom, argsptr.get(), 0, ChangeType::Create ) ) return 0; } if( atom->has_observers( member->name ) ) { if( !argsptr ) { argsptr = created_args( atom, member, value.get() ); if( !argsptr ) return 0; } if( !atom->notify( member->name, argsptr.get(), 0, ChangeType::Create ) ) return 0; } } if( member->get_post_getattr_mode() ) value = member->post_getattr( atom, value.get() ); return value.release(); } PyObject* event_handler( Member* member, CAtom* atom ) { return EventBinder::New( member, atom ); } PyObject* signal_handler( Member* member, CAtom* atom ) { return SignalConnector::New( member, atom ); } PyObject* delegate_handler( Member* member, CAtom* atom ) { Member* delegate = member_cast( member->getattr_context ); return delegate->getattr( atom ); } PyObject* _mangled_property_handler( Member* member, CAtom* atom ) { char* suffix = (char *)PyUnicode_AsUTF8( member->name ); cppy::ptr name( PyUnicode_FromFormat( "_get_%s", suffix ) ); if( !name ) return 0; return PyObject_CallMethodNoArgs( pyobject_cast( atom ), name.get() ); } PyObject* property_handler( Member* member, CAtom* atom ) { if( member->getattr_context != Py_None ) { return PyObject_CallOneArg( member->getattr_context, pyobject_cast( atom ) ); } return _mangled_property_handler( member, atom ); } PyObject* cached_property_handler( Member* member, CAtom* atom ) { cppy::ptr value( atom->get_slot( member->index ) ); if( value ) return value.release(); value = property_handler( member, atom ); atom->set_slot( member->index, value.get() ); // exception-safe return value.release(); } PyObject* call_object_object_handler( Member* member, CAtom* atom ) { cppy::ptr result( PyObject_CallOneArg( member->getattr_context, pyobject_cast( atom ) ) ); if( !result ) return 0; return member->full_validate( atom, Py_None, result.get() ); } PyObject* call_object_object_name_handler( Member* member, CAtom* atom ) { PyObject* args[] = { pyobject_cast( atom ), member->name }; cppy::ptr result( PyObject_Vectorcall( member->getattr_context, args, 2, 0 ) ); if( !result ) return 0; return member->full_validate( atom, Py_None, result.get() ); } PyObject* object_method_handler( Member* member, CAtom* atom ) { cppy::ptr result( PyObject_CallMethodNoArgs( pyobject_cast( atom ), member->getattr_context ) ); if( !result ) return 0; return member->full_validate( atom, Py_None, result.get() ); } PyObject* object_method_name_handler( Member* member, CAtom* atom ) { cppy::ptr result( PyObject_CallMethodOneArg( pyobject_cast( atom ), member->getattr_context, member->name ) ); if( !result ) return 0; return member->full_validate( atom, Py_None, result.get() ); } PyObject* member_method_object_handler( Member* member, CAtom* atom ) { cppy::ptr result( PyObject_CallMethodOneArg( pyobject_cast( member ), member->getattr_context, pyobject_cast( atom ) ) ); if( !result ) return 0; return member->full_validate( atom, Py_None, result.get() ); } typedef PyObject* ( *handler )( Member* member, CAtom* atom ); static handler handlers[] = { no_op_handler, slot_handler, event_handler, signal_handler, delegate_handler, property_handler, cached_property_handler, call_object_object_handler, call_object_object_name_handler, object_method_handler, object_method_name_handler, member_method_object_handler }; } // namespace PyObject* Member::getattr( CAtom* atom ) { if( get_getattr_mode() >= sizeof( handlers ) ) return no_op_handler( this, atom ); // LCOV_EXCL_LINE return handlers[ get_getattr_mode() ]( this, atom ); } } // namespace atom atom-0.12.1/atom/src/getstatebehavior.cpp000066400000000000000000000063521506756731600203540ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2023-2024, Atom Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #include #include "member.h" namespace atom { bool Member::check_context( GetState::Mode mode, PyObject* context ) { switch( mode ) { case GetState::ObjectMethod_Name: case GetState::MemberMethod_Object: if( !PyUnicode_Check( context ) ) { cppy::type_error( context, "str" ); return false; } break; default: break; } return true; } namespace { PyObject* exclude_handler( Member* member, CAtom* atom ) { return cppy::incref( Py_False ); } PyObject* include_handler( Member* member, CAtom* atom ) { return cppy::incref( Py_True ); } PyObject* include_non_default_handler( Member* member, CAtom* atom ) { if( member->index >= atom->get_slot_count() ) { return cppy::attribute_error( pyobject_cast( atom ), (char const *)PyUnicode_AsUTF8( member->name ) ); } cppy::ptr value( atom->get_slot( member->index ) ); if( value ) { return cppy::incref( Py_True ); } else { return cppy::incref( Py_False ); } } PyObject* property_handler( Member* member, CAtom* atom ) { // Pickle a property only if the value can be set if( member->get_setattr_mode() == SetAttr::Property && member->setattr_context != Py_None ) { return cppy::incref( Py_True ); } else { return cppy::incref( Py_False ); } } PyObject* object_method_name_handler( Member* member, CAtom* atom ) { cppy::ptr callable( PyObject_GetAttr( pyobject_cast( atom ), member->getstate_context ) ); if( !callable ) return 0; cppy::ptr args( PyTuple_New( 1 ) ); if( !args ) return 0; PyTuple_SET_ITEM( args.get(), 0, cppy::incref( member->name ) ); cppy::ptr result( callable.call( args ) ); if( !result ) return 0; return result.release(); } PyObject* member_method_object_handler( Member* member, CAtom* atom ) { cppy::ptr callable( PyObject_GetAttr( pyobject_cast( member ), member->getstate_context ) ); if( !callable ) return 0; cppy::ptr args( PyTuple_New( 1 ) ); if( !args ) return 0; PyTuple_SET_ITEM( args.get(), 0, cppy::incref( pyobject_cast( atom ) ) ); cppy::ptr result( callable.call( args ) ); if( !result ) return 0; return result.release(); } typedef PyObject* ( *handler )( Member* member, CAtom* atom ); static handler handlers[] = { include_handler, // We want the include handler to be the default one exclude_handler, include_non_default_handler, property_handler, object_method_name_handler, member_method_object_handler }; } // namespace PyObject* Member::should_getstate( CAtom* atom ) { if( get_getstate_mode() >= sizeof( handlers ) ) return include_handler( this, atom ); // LCOV_EXCL_LINE return handlers[ get_getstate_mode() ]( this, atom ); } } // namespace atom atom-0.12.1/atom/src/globalstatic.h000066400000000000000000000020341506756731600171220ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #pragma once namespace atom { // shamelessly derived from qglobal.h template class GlobalStatic { public: T* pointer; inline GlobalStatic( T* p ) : pointer( p ) {} inline ~GlobalStatic() { pointer = 0; } }; #define GLOBAL_STATIC( TYPE, NAME ) \ static TYPE* NAME() \ { \ static TYPE this_variable; \ static GlobalStatic this_global_static( &this_variable); \ return this_global_static.pointer; \ } } // namespace atom atom-0.12.1/atom/src/member.cpp000066400000000000000000001074511506756731600162650ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #ifdef __clang__ #pragma clang diagnostic ignored "-Wdeprecated-writable-strings" #endif #ifdef __GNUC__ #pragma GCC diagnostic ignored "-Wwrite-strings" #endif #include #include "member.h" #include "enumtypes.h" #include "packagenaming.h" #include "utils.h" namespace atom { namespace { static PyObject* undefined; PyObject* Member_new( PyTypeObject* type, PyObject* args, PyObject* kwargs ) { cppy::ptr selfptr( PyType_GenericNew( type, args, kwargs ) ); if( !selfptr ) { return 0; // LCOV_EXCL_LINE (allocation failed, impossible) } Member* member = member_cast( selfptr.get() ); member->name = cppy::incref( undefined ); member->set_getattr_mode( GetAttr::Slot ); member->set_setattr_mode( SetAttr::Slot ); member->set_delattr_mode( DelAttr::Slot ); return selfptr.release(); } void Member_clear( Member* self ) { Py_CLEAR( self->name ); Py_CLEAR( self->metadata ); Py_CLEAR( self->getattr_context ); Py_CLEAR( self->setattr_context ); Py_CLEAR( self->delattr_context ); Py_CLEAR( self->validate_context ); Py_CLEAR( self->post_getattr_context ); Py_CLEAR( self->post_setattr_context ); Py_CLEAR( self->default_value_context ); Py_CLEAR( self->post_validate_context ); Py_CLEAR( self->getstate_context ); if( self->static_observers ) self->static_observers->clear(); } int Member_traverse( Member* self, visitproc visit, void* arg ) { Py_VISIT( self->name ); Py_VISIT( self->metadata ); Py_VISIT( self->getattr_context ); Py_VISIT( self->setattr_context ); Py_VISIT( self->delattr_context ); Py_VISIT( self->validate_context ); Py_VISIT( self->post_getattr_context ); Py_VISIT( self->post_setattr_context ); Py_VISIT( self->default_value_context ); Py_VISIT( self->post_validate_context ); Py_VISIT( self->getstate_context ); if( self->static_observers ) { std::vector::iterator it; std::vector::iterator end = self->static_observers->end(); for( it = self->static_observers->begin(); it != end; ++it ) Py_VISIT( it->m_observer.get() ); } #if PY_VERSION_HEX >= 0x03090000 // This was not needed before Python 3.9 (Python issue 35810 and 40217) Py_VISIT(Py_TYPE(self)); #endif return 0; } void Member_dealloc( Member* self ) { PyObject_GC_UnTrack( self ); Member_clear( self ); delete self->static_observers; self->static_observers = 0; Py_TYPE(self)->tp_free( pyobject_cast( self ) ); } PyObject* Member_has_observers( Member* self, PyObject*const *args, Py_ssize_t n ) { if( n == 0 ) return utils::py_bool( self->has_observers() ); if( n > 1 ) return cppy::type_error( "has_observers() takes at most 1 argument" ); PyObject* types = args[0]; if( !PyLong_Check( types ) ) return cppy::type_error( types, "int" ); uint8_t change_types = PyLong_AsLong( types ); return utils::py_bool( self->has_observers( change_types ) ); } PyObject* Member_has_observer( Member* self, PyObject*const *args, Py_ssize_t n ) { if( n < 1 || n > 2 ) return cppy::type_error( "has_observer() expects a callable and an optional change type" ); PyObject* observer = args[0]; if( !PyUnicode_CheckExact( observer ) && !PyCallable_Check( observer ) ) return cppy::type_error( observer, "str or callable" ); uint8_t change_types = ChangeType::Any; if ( n == 2 ) { PyObject* types = args[1]; if( !PyLong_Check( types ) ) return cppy::type_error( types, "int" ); change_types = PyLong_AsLong( types ) & 0xFF; } return utils::py_bool( self->has_observer( observer, change_types ) ); } PyObject* Member_copy_static_observers( Member* self, PyObject* other ) { if( !Member::TypeCheck( other ) ) return cppy::type_error( other, "Member" ); Member* member = member_cast( other ); if( self == member ) Py_RETURN_NONE; if( !member->static_observers ) { delete self->static_observers; self->static_observers = 0; } else { if( !self->static_observers ) self->static_observers = new std::vector(); *self->static_observers = *member->static_observers; } Py_RETURN_NONE; } PyObject* Member_static_observers( Member* self ) { if( !self->static_observers ) return PyTuple_New( 0 ); std::vector& observers( *self->static_observers ); size_t size = observers.size(); PyObject* items = PyTuple_New( size ); if( !items ) return 0; for( size_t i = 0; i < size; ++i ) PyTuple_SET_ITEM( items, i, cppy::incref( observers[ i ].m_observer.get() ) ); return items; } PyObject* Member_add_static_observer( Member* self, PyObject*const *args, Py_ssize_t n) { if( n < 1 ) return cppy::type_error( "add_static_observer() requires at least 1 argument" ); if( n > 2 ) return cppy::type_error( "add_static_observer() takes at most 2 arguments" ); PyObject* observer = args[0]; if( !PyUnicode_CheckExact( observer ) && !PyCallable_Check( observer ) ) return cppy::type_error( observer, "str or callable" ); uint8_t change_types = ChangeType::Any; if(n == 2) { PyObject* types = args[1]; if( !PyLong_Check( types ) ) return cppy::type_error( types, "int" ); change_types = PyLong_AsLong( types ) & 0xFF ; } self->add_observer( observer, change_types ); Py_RETURN_NONE; } PyObject* Member_remove_static_observer( Member* self, PyObject* observer ) { if( !PyUnicode_CheckExact( observer ) && !PyCallable_Check( observer ) ) return cppy::type_error( observer, "str or callable" ); self->remove_observer( observer ); Py_RETURN_NONE; } PyObject* Member_get_slot( Member* self, PyObject* object ) { if( !CAtom::TypeCheck( object ) ) return cppy::type_error( object, "CAtom" ); CAtom* atom = catom_cast( object ); if( self->index >= atom->get_slot_count() ) return cppy::attribute_error( object, (char *)PyUnicode_AsUTF8( self->name ) ); cppy::ptr value( atom->get_slot( self->index ) ); if( value ) return value.release(); Py_RETURN_NONE; } PyObject* Member_set_slot( Member* self, PyObject*const *args, Py_ssize_t n ) { if( n != 2 ) return cppy::type_error( "set_slot() takes exactly 2 arguments" ); PyObject* object = args[0]; PyObject* value = args[1]; if( !CAtom::TypeCheck( object ) ) return cppy::type_error( object, "CAtom" ); CAtom* atom = catom_cast( object ); if( self->index >= atom->get_slot_count() ) return cppy::attribute_error( object, (char *)PyUnicode_AsUTF8( self->name ) ); atom->set_slot( self->index, value ); Py_RETURN_NONE; } PyObject* Member_del_slot( Member* self, PyObject* object ) { if( !CAtom::TypeCheck( object ) ) return cppy::type_error( object, "CAtom" ); CAtom* atom = catom_cast( object ); if( self->index >= atom->get_slot_count() ) return cppy::attribute_error( object, (char *)PyUnicode_AsUTF8( self->name ) ); atom->set_slot( self->index, 0 ); Py_RETURN_NONE; } PyObject* Member_do_getattr( Member* self, PyObject* object ) { if( !CAtom::TypeCheck( object ) ) return cppy::type_error( object, "CAtom" ); return self->getattr( catom_cast( object ) ); } PyObject* Member_do_setattr( Member* self, PyObject*const *args, Py_ssize_t n ) { if( n != 2 ) return cppy::type_error( "do_setattr() takes exactly 2 arguments" ); PyObject* object = args[0]; PyObject* value = args[1]; if( !CAtom::TypeCheck( object ) ) return cppy::type_error( object, "CAtom" ); if( self->setattr( catom_cast( object ), value ) < 0 ) return 0; Py_RETURN_NONE; } PyObject* Member_do_delattr( Member* self, PyObject* object ) { if( !CAtom::TypeCheck( object ) ) return cppy::type_error( object, "CAtom" ); if( self->delattr( catom_cast( object ) ) < 0 ) return 0; Py_RETURN_NONE; } PyObject* Member_do_post_getattr( Member* self, PyObject*const *args, Py_ssize_t n ) { if( n != 2 ) return cppy::type_error( "do_post_getattr() takes exactly 2 arguments" ); PyObject* object = args[0]; PyObject* value = args[1]; if( !CAtom::TypeCheck( object ) ) return cppy::type_error( object, "CAtom" ); return self->post_getattr( catom_cast( object ), value ); } PyObject* Member_do_post_setattr( Member* self, PyObject*const *args, Py_ssize_t n ) { if( n != 3 ) return cppy::type_error( "do_post_setattr() takes exactly 3 arguments" ); PyObject* object = args[0]; PyObject* oldvalue = args[1]; PyObject* newvalue = args[2]; if( !CAtom::TypeCheck( object ) ) return cppy::type_error( object, "CAtom" ); if( self->post_setattr( catom_cast( object ), oldvalue, newvalue ) < 0 ) return 0; Py_RETURN_NONE; } PyObject* Member_do_default_value( Member* self, PyObject* object ) { if( !CAtom::TypeCheck( object ) ) return cppy::type_error( object, "CAtom" ); return self->default_value( catom_cast( object ) ); } PyObject* Member_do_validate( Member* self, PyObject*const *args, Py_ssize_t n ) { if( n != 3 ) return cppy::type_error( "do_validate() takes exactly 3 arguments" ); PyObject* object = args[0]; PyObject* oldvalue = args[1]; PyObject* newvalue = args[2]; if( !CAtom::TypeCheck( object ) ) return cppy::type_error( object, "CAtom" ); return self->validate( catom_cast( object ), oldvalue, newvalue ); } PyObject* Member_do_post_validate( Member* self, PyObject*const *args, Py_ssize_t n ) { if( n != 3 ) return cppy::type_error( "do_post_validate() takes exactly 3 arguments" ); PyObject* object = args[0]; PyObject* oldvalue = args[1]; PyObject* newvalue = args[2]; if( !CAtom::TypeCheck( object ) ) return cppy::type_error( object, "CAtom" ); return self->post_validate( catom_cast( object ), oldvalue, newvalue ); } PyObject* Member_do_full_validate( Member* self, PyObject*const *args, Py_ssize_t n ) { if( n != 3 ) return cppy::type_error( "do_full_validate() takes exactly 3 arguments" ); PyObject* object = args[0]; PyObject* oldvalue = args[1]; PyObject* newvalue = args[2]; if( !CAtom::TypeCheck( object ) ) return cppy::type_error( object, "CAtom" ); return self->full_validate( catom_cast( object ), oldvalue, newvalue ); } PyObject* Member_do_should_getstate( Member* self, PyObject* object ) { if( !CAtom::TypeCheck( object ) ) return cppy::type_error( object, "CAtom" ); return self->should_getstate( catom_cast( object ) ); } PyObject* Member_clone( Member* self ) { // reimplement in a subclass to clone additional Python state PyObject* pyclone = PyType_GenericNew( Py_TYPE(self), 0, 0 ); if( !pyclone ) return 0; Member* clone = member_cast( pyclone ); clone->modes = self->modes; clone->index = self->index; clone->name = cppy::incref( self->name ); if( self->metadata ) clone->metadata = PyDict_Copy( self->metadata ); clone->getattr_context = cppy::xincref( self->getattr_context ); clone->setattr_context = cppy::xincref( self->setattr_context ); clone->delattr_context = cppy::xincref( self->delattr_context ); clone->validate_context = cppy::xincref( self->validate_context ); clone->post_getattr_context = cppy::xincref( self->post_getattr_context ); clone->post_setattr_context = cppy::xincref( self->post_setattr_context ); clone->default_value_context = cppy::xincref( self->default_value_context ); clone->post_validate_context = cppy::xincref( self->post_validate_context ); clone->getstate_context = cppy::xincref( self->getstate_context ); if( self->static_observers ) { clone->static_observers = new std::vector(); *clone->static_observers = *self->static_observers; } return pyclone; } PyObject* Member_get_name( Member* self, void* context ) { return cppy::incref( self->name ); } PyObject* Member_set_name( Member* self, PyObject* value ) { if( !PyUnicode_CheckExact( value ) ) return cppy::type_error( value, "str" ); cppy::incref( value ); // incref before interning or segfault! PyUnicode_InternInPlace( &value ); PyObject* old = self->name; self->name = value; cppy::decref( old ); Py_RETURN_NONE; } PyObject* Member_get_index( Member* self, void* context ) { return PyLong_FromSsize_t( static_cast( self->index ) ); } PyObject* Member_set_index( Member* self, PyObject* value ) { if( !PyLong_Check( value ) ) return cppy::type_error( value, "int" ); Py_ssize_t index = PyLong_AsSsize_t( value ); if( index < 0 && PyErr_Occurred() ) return 0; self->index = static_cast( index < 0 ? 0 : index ); Py_RETURN_NONE; } template bool parse_mode_and_context( PyObject*const *args, Py_ssize_t n, PyObject** context, T& mode ) { if( n != 2 ) { cppy::type_error( "set mode requires two arguments mode, context" ); return false; } if( !EnumTypes::from_py_enum( args[0], mode ) ) return false; *context = args[1]; if( !Member::check_context( mode, *context ) ) return false; return true; } PyObject* Member_get_getattr_mode( Member* self, void* ctxt ) { cppy::ptr tuple( PyTuple_New( 2 ) ); if( !tuple ) return 0; cppy::ptr py_enum( EnumTypes::to_py_enum( self->get_getattr_mode() ) ); if( !py_enum ) return 0; PyTuple_SET_ITEM( tuple.get(), 0, py_enum.release() ); PyObject* context = self->getattr_context; PyTuple_SET_ITEM( tuple.get(), 1, cppy::incref( context ? context : Py_None ) ); return tuple.release(); } PyObject* Member_set_getattr_mode( Member* self, PyObject*const *args, Py_ssize_t n ) { GetAttr::Mode mode; PyObject* context; if( !parse_mode_and_context( args, n, &context, mode ) ) return 0; self->set_getattr_mode( mode ); cppy::replace( &self->getattr_context, context ); Py_RETURN_NONE; } PyObject* Member_get_setattr_mode( Member* self, void* ctxt ) { cppy::ptr tuple( PyTuple_New( 2 ) ); if( !tuple ) return 0; cppy::ptr py_enum( EnumTypes::to_py_enum( self->get_setattr_mode() ) ); if( !py_enum ) return 0; PyTuple_SET_ITEM( tuple.get(), 0, py_enum.release() ); PyObject* context = self->setattr_context; PyTuple_SET_ITEM( tuple.get(), 1, cppy::incref( context ? context : Py_None ) ); return tuple.release(); } PyObject* Member_set_setattr_mode( Member* self, PyObject*const *args, Py_ssize_t n ) { SetAttr::Mode mode; PyObject* context; if( !parse_mode_and_context( args, n, &context, mode ) ) return 0; self->set_setattr_mode( mode ); cppy::replace( &self->setattr_context, context ); Py_RETURN_NONE; } PyObject* Member_get_delattr_mode( Member* self, void* ctxt ) { cppy::ptr tuple( PyTuple_New( 2 ) ); if( !tuple ) return 0; cppy::ptr py_enum( EnumTypes::to_py_enum( self->get_delattr_mode() ) ); if( !py_enum ) return 0; PyTuple_SET_ITEM( tuple.get(), 0, py_enum.release() ); PyObject* context = self->delattr_context; PyTuple_SET_ITEM( tuple.get(), 1, cppy::incref( context ? context : Py_None ) ); return tuple.release(); } PyObject* Member_set_delattr_mode( Member* self, PyObject*const *args, Py_ssize_t n ) { DelAttr::Mode mode; PyObject* context; if( !parse_mode_and_context( args, n, &context, mode ) ) return 0; self->set_delattr_mode( mode ); cppy::replace( &self->delattr_context, context ); Py_RETURN_NONE; } PyObject* Member_get_post_getattr_mode( Member* self, void* ctxt ) { cppy::ptr tuple( PyTuple_New( 2 ) ); if( !tuple ) return 0; cppy::ptr py_enum( EnumTypes::to_py_enum( self->get_post_getattr_mode() ) ); if( !py_enum ) return 0; PyTuple_SET_ITEM( tuple.get(), 0, py_enum.release() ); PyObject* context = self->post_getattr_context; PyTuple_SET_ITEM( tuple.get(), 1, cppy::incref( context ? context : Py_None ) ); return tuple.release(); } PyObject* Member_set_post_getattr_mode( Member* self, PyObject*const *args, Py_ssize_t n ) { PostGetAttr::Mode mode; PyObject* context; if( !parse_mode_and_context( args, n, &context, mode ) ) return 0; self->set_post_getattr_mode( mode ); cppy::replace(&self->post_getattr_context, context); Py_RETURN_NONE; } PyObject* Member_get_post_setattr_mode( Member* self, void* ctxt ) { cppy::ptr tuple( PyTuple_New( 2 ) ); if( !tuple ) return 0; cppy::ptr py_enum( EnumTypes::to_py_enum( self->get_post_setattr_mode() ) ); if( !py_enum ) return 0; PyTuple_SET_ITEM( tuple.get(), 0, py_enum.release() ); PyObject* context = self->post_setattr_context; PyTuple_SET_ITEM( tuple.get(), 1, cppy::incref( context ? context : Py_None ) ); return tuple.release(); } PyObject* Member_set_post_setattr_mode( Member* self, PyObject*const *args, Py_ssize_t n ) { PostSetAttr::Mode mode; PyObject* context; if( !parse_mode_and_context( args, n, &context, mode ) ) return 0; self->set_post_setattr_mode( mode ); cppy::replace( &self->post_setattr_context, context ); Py_RETURN_NONE; } PyObject* Member_get_default_value_mode( Member* self, void* ctxt ) { cppy::ptr tuple( PyTuple_New( 2 ) ); if( !tuple ) return 0; cppy::ptr py_enum( EnumTypes::to_py_enum( self->get_default_value_mode() ) ); if( !py_enum ) return 0; PyTuple_SET_ITEM( tuple.get(), 0, py_enum.release() ); PyObject* context = self->default_value_context; PyTuple_SET_ITEM( tuple.get(), 1, cppy::incref( context ? context : Py_None ) ); return tuple.release(); } PyObject* Member_set_default_value_mode( Member* self, PyObject*const *args, Py_ssize_t n ) { DefaultValue::Mode mode; PyObject* context; if( !parse_mode_and_context( args, n, &context, mode ) ) return 0; self->set_default_value_mode( mode ); cppy::replace( &self->default_value_context, context ); Py_RETURN_NONE; } PyObject* Member_get_validate_mode( Member* self, void* ctxt ) { cppy::ptr tuple( PyTuple_New( 2 ) ); if( !tuple ) return 0; cppy::ptr py_enum( EnumTypes::to_py_enum( self->get_validate_mode() ) ); if( !py_enum ) return 0; PyTuple_SET_ITEM( tuple.get(), 0, py_enum.release() ); PyObject* context = self->validate_context; PyTuple_SET_ITEM( tuple.get(), 1, cppy::incref( context ? context : Py_None ) ); return tuple.release(); } PyObject* Member_set_validate_mode( Member* self, PyObject*const *args, Py_ssize_t n ) { Validate::Mode mode; PyObject* context; if( !parse_mode_and_context( args, n, &context, mode ) ) return 0; self->set_validate_mode( mode ); cppy::replace( &self->validate_context, context ); Py_RETURN_NONE; } PyObject* Member_get_post_validate_mode( Member* self, void* ctxt ) { cppy::ptr tuple( PyTuple_New( 2 ) ); if( !tuple ) return 0; cppy::ptr py_enum( EnumTypes::to_py_enum( self->get_post_validate_mode() ) ); if( !py_enum ) return 0; PyTuple_SET_ITEM( tuple.get(), 0, py_enum.release() ); PyObject* context = self->post_validate_context; PyTuple_SET_ITEM( tuple.get(), 1, cppy::incref( context ? context : Py_None ) ); return tuple.release(); } PyObject* Member_set_post_validate_mode( Member* self, PyObject*const *args, Py_ssize_t n ) { PostValidate::Mode mode; PyObject* context; if( !parse_mode_and_context( args, n, &context, mode ) ) return 0; self->set_post_validate_mode( mode ); cppy::replace( &self->post_validate_context, context ); Py_RETURN_NONE; } PyObject* Member_get_getstate_mode( Member* self, void* ctxt ) { cppy::ptr tuple( PyTuple_New( 2 ) ); if( !tuple ) return 0; cppy::ptr py_enum( EnumTypes::to_py_enum( self->get_getstate_mode() ) ); if( !py_enum ) return 0; PyTuple_SET_ITEM( tuple.get(), 0, py_enum.release() ); PyObject* context = self->getstate_context; PyTuple_SET_ITEM( tuple.get(), 1, cppy::incref( context ? context : Py_None ) ); return tuple.release(); } PyObject* Member_set_getstate_mode( Member* self, PyObject*const *args, Py_ssize_t n ) { GetState::Mode mode; PyObject* context; if( !parse_mode_and_context( args, n, &context, mode ) ) return 0; self->set_getstate_mode( mode ); cppy::replace( &self->getstate_context, context ); Py_RETURN_NONE; } PyObject* Member_notify( Member* self, PyObject* args, PyObject* kwargs ) { if( PyTuple_GET_SIZE( args ) < 1 ) return cppy::type_error( "notify() requires at least 1 argument" ); PyObject* owner = PyTuple_GET_ITEM( args, 0 ); if( !CAtom::TypeCheck( owner ) ) return cppy::type_error( owner, "CAtom" ); cppy::ptr argsptr( PyTuple_GetSlice( args, 1, PyTuple_GET_SIZE( args ) ) ); if( !argsptr ) return 0; if( !self->notify( catom_cast( owner ), argsptr.get(), kwargs ) ) return 0; Py_RETURN_NONE; } PyObject* Member_tag( Member* self, PyObject* args, PyObject* kwargs ) { if( PyTuple_GET_SIZE( args ) != 0 ) return cppy::type_error( "tag() takes no positional arguments" ); if( !kwargs ) return cppy::type_error( "tag() requires keyword arguments" ); if( !self->metadata ) { self->metadata = PyDict_New(); if( !self->metadata ) return 0; } if( PyDict_Update( self->metadata, kwargs ) < 0 ) return 0; return cppy::incref( pyobject_cast( self ) ); } PyObject* Member_get_metadata( Member* self, void* ctxt ) { if( !self->metadata ) Py_RETURN_NONE; cppy::incref( self->metadata ); return self->metadata; } int Member_set_metadata( Member* self, PyObject* value, void* ctxt ) { if( value && value != Py_None && !PyDict_Check( value ) ) { cppy::type_error( value, "dict or None" ); return -1; } if( value == Py_None ) value = 0; PyObject* old = self->metadata; self->metadata = value; cppy::xincref( value ); cppy::xdecref( old ); return 0; } PyObject* Member__get__( Member* self, PyObject* object, PyObject* type ) { if( !object ) return cppy::incref( pyobject_cast( self ) ); if( !CAtom::TypeCheck( object ) ) return cppy::type_error( object, "CAtom" ); return self->getattr( catom_cast( object ) ); } int Member__set__( Member* self, PyObject* object, PyObject* value ) { if( !CAtom::TypeCheck( object ) ) { cppy::type_error( object, "CAtom" ); return -1; } if( value ) return self->setattr( catom_cast( object ), value ); return self->delattr( catom_cast( object ) ); } static PyGetSetDef Member_getset[] = { { "name", ( getter )Member_get_name, 0, "Get the name to which the member is bound." }, { "metadata", ( getter )Member_get_metadata, ( setter )Member_set_metadata, "Get and set the metadata for the member." }, { "index", ( getter )Member_get_index, 0, "Get the index to which the member is bound" }, { "getattr_mode", ( getter )Member_get_getattr_mode, 0, "Get the getattr mode for the member." }, { "setattr_mode", ( getter )Member_get_setattr_mode, 0, "Get the setattr mode for the member." }, { "delattr_mode", ( getter )Member_get_delattr_mode, 0, "Get the delattr mode for the member." }, { "default_value_mode", ( getter )Member_get_default_value_mode, 0, "Get the default value mode for the member." }, { "validate_mode", ( getter )Member_get_validate_mode, 0, "Get the validate mode for the member." }, { "post_getattr_mode", ( getter )Member_get_post_getattr_mode, 0, "Get the post getattr mode for the member." }, { "post_setattr_mode", ( getter )Member_get_post_setattr_mode, 0, "Get the post setattr mode for the member." }, { "post_validate_mode", ( getter )Member_get_post_validate_mode, 0, "Get the post validate mode for the member." }, { "getstate_mode", ( getter )Member_get_getstate_mode, 0, "Get the getstate mode for the member"}, { 0 } // sentinel }; static PyMethodDef Member_methods[] = { { "set_name", ( PyCFunction )Member_set_name, METH_O, "Set the name to which the member is bound. Use with extreme caution!" }, { "set_index", ( PyCFunction )Member_set_index, METH_O, "Set the index to which the member is bound. Use with extreme caution!" }, { "get_slot", ( PyCFunction )Member_get_slot, METH_O, "Get the atom's slot value directly." }, { "set_slot", ( PyCFunction )Member_set_slot, METH_FASTCALL, "Set the atom's slot value directly." }, { "del_slot", ( PyCFunction )Member_del_slot, METH_O, "Delete the atom's slot value directly." }, { "has_observers", ( PyCFunction )Member_has_observers, METH_FASTCALL, "Get whether or not this member has observers." }, { "has_observer", ( PyCFunction )Member_has_observer, METH_FASTCALL, "Get whether or not the member already has the given observer." }, { "copy_static_observers", ( PyCFunction )Member_copy_static_observers, METH_O, "Copy the static observers from one member into this member." }, { "static_observers", ( PyCFunction )Member_static_observers, METH_NOARGS, "Get a tuple of the static observers defined for this member" }, { "add_static_observer", ( PyCFunction )Member_add_static_observer, METH_FASTCALL, "Add the name of a method to call on all atoms when the member changes." }, { "remove_static_observer", ( PyCFunction )Member_remove_static_observer, METH_O, "Remove the name of a method to call on all atoms when the member changes." }, { "clone", ( PyCFunction )Member_clone, METH_NOARGS, "Create a clone of this member." }, { "do_getattr", ( PyCFunction )Member_do_getattr, METH_O, "Run the getattr handler for the member." }, { "do_setattr", ( PyCFunction )Member_do_setattr, METH_FASTCALL, "Run the setattr handler for the member." }, { "do_delattr", ( PyCFunction )Member_do_delattr, METH_O, "Run the delattr handler for the member." }, { "do_default_value", ( PyCFunction )Member_do_default_value, METH_O, "Run the default value handler for member." }, { "do_validate", ( PyCFunction )Member_do_validate, METH_FASTCALL, "Run the validation handler for the member." }, { "do_post_getattr", ( PyCFunction )Member_do_post_getattr, METH_FASTCALL, "Run the post getattr handler for the member." }, { "do_post_setattr", ( PyCFunction )Member_do_post_setattr, METH_FASTCALL, "Run the post setattr handler for the member." }, { "do_post_validate", ( PyCFunction )Member_do_post_validate, METH_FASTCALL, "Run the post validation handler for the member." }, { "do_full_validate", ( PyCFunction )Member_do_full_validate, METH_FASTCALL, "Run the validation and post validation handlers for the member." }, { "do_should_getstate", ( PyCFunction )Member_do_should_getstate, METH_O, "Run the validation and post validation handlers for the member." }, { "set_getattr_mode", ( PyCFunction )Member_set_getattr_mode, METH_FASTCALL, "Set the getattr mode for the member." }, { "set_setattr_mode", ( PyCFunction )Member_set_setattr_mode, METH_FASTCALL, "Set the setattr mode for the member." }, { "set_delattr_mode", ( PyCFunction )Member_set_delattr_mode, METH_FASTCALL, "Set the delattr mode for the member." }, { "set_default_value_mode", ( PyCFunction )Member_set_default_value_mode, METH_FASTCALL, "Set the default value mode for the member." }, { "set_validate_mode", ( PyCFunction )Member_set_validate_mode, METH_FASTCALL, "Set the validate mode for the member." }, { "set_post_getattr_mode", ( PyCFunction )Member_set_post_getattr_mode, METH_FASTCALL, "Set the post getattr mode for the member." }, { "set_post_setattr_mode", ( PyCFunction )Member_set_post_setattr_mode, METH_FASTCALL, "Set the post setattr mode for the member." }, { "set_post_validate_mode", ( PyCFunction )Member_set_post_validate_mode, METH_FASTCALL, "Set the post validate mode for the member." }, { "set_getstate_mode", ( PyCFunction )Member_set_getstate_mode, METH_FASTCALL, "Set the getstate mode for the member." }, { "notify", ( PyCFunction )Member_notify, METH_VARARGS | METH_KEYWORDS, "Notify the static observers for the given member and atom." }, { "tag", ( PyCFunction )Member_tag, METH_VARARGS | METH_KEYWORDS, "Tag the member with metatdata. " }, #if PY_VERSION_HEX >= 0x03090000 // Generic aliases have been added in 3.9 and allow to index types // This removes the need to quote explicit member type annotations { "__class_getitem__", (PyCFunction)Py_GenericAlias, METH_O|METH_CLASS, "See PEP 585"}, #endif { 0 } // sentinel }; static PyType_Slot Member_Type_slots[] = { { Py_tp_dealloc, void_cast( Member_dealloc ) }, /* tp_dealloc */ { Py_tp_traverse, void_cast( Member_traverse ) }, /* tp_traverse */ { Py_tp_clear, void_cast( Member_clear ) }, /* tp_clear */ { Py_tp_methods, void_cast( Member_methods ) }, /* tp_methods */ { Py_tp_getset, void_cast( Member_getset ) }, /* tp_getset */ { Py_tp_descr_get, void_cast( Member__get__ ) }, /* tp_descr_get */ { Py_tp_descr_set, void_cast( Member__set__ ) }, /* tp_descr_get */ { Py_tp_new, void_cast( Member_new ) }, /* tp_new */ { Py_tp_alloc, void_cast( PyType_GenericAlloc ) }, /* tp_new */ { Py_tp_free, void_cast( PyObject_GC_Del ) }, /* tp_new */ { 0, 0 }, }; } // namespace // Initialize static variables (otherwise the compiler eliminate them) PyTypeObject* Member::TypeObject = NULL; PyType_Spec Member::TypeObject_Spec = { PACKAGE_TYPENAME( "Member" ), /* tp_name */ sizeof( Member ), /* tp_basicsize */ 0, /* tp_itemsize */ Py_TPFLAGS_DEFAULT |Py_TPFLAGS_BASETYPE |Py_TPFLAGS_HAVE_GC, /* tp_flags */ Member_Type_slots /* slots */ }; PyObject* Member::full_validate( CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { cppy::ptr result( cppy::incref( newvalue ) ); if( get_validate_mode() ) { result = validate( atom, oldvalue, result.get() ); if( !result ) return 0; } if( get_post_validate_mode() ) { result = post_validate( atom, oldvalue, result.get() ); if( !result ) return 0; } return result.release(); } namespace { struct BaseTask : public ModifyTask { BaseTask( Member* member, PyObject* observer ) : m_member( cppy::incref( pyobject_cast( member ) ) ), m_observer( cppy::incref( observer ) ) {} cppy::ptr m_member; cppy::ptr m_observer; }; struct AddTask : public BaseTask { AddTask( Member* member, PyObject* observer, uint8_t change_types ) : BaseTask( member, observer ), m_change_types(change_types) {} void run() { Member* member = member_cast( m_member.get() ); member->add_observer( m_observer.get(), m_change_types ); } uint8_t m_change_types; }; struct RemoveTask : public BaseTask { RemoveTask( Member* member, PyObject* observer ) : BaseTask( member, observer ) {} void run() { Member* member = member_cast( m_member.get() ); member->remove_observer( m_observer.get() ); } }; } // namespace bool Member::has_observers( uint8_t change_types ) { if ( static_observers ) { std::vector::iterator it; std::vector::iterator end = static_observers->end(); for( it = static_observers->begin(); it != end; ++it ) { if( it->enabled( change_types ) ) return true; } } return false; } void Member::add_observer( PyObject* observer, uint8_t change_types ) { if( modify_guard ) { ModifyTask* task = new AddTask( this, observer, change_types ); modify_guard->add_task( task ); return; } if( !static_observers ) static_observers = new std::vector(); cppy::ptr obptr( cppy::incref( observer ) ); std::vector::iterator it; std::vector::iterator end = static_observers->end(); for( it = static_observers->begin(); it != end; ++it ) { if( it->match( obptr ) ) { it->m_change_types = change_types; return; } } static_observers->push_back( Observer(obptr, change_types) ); return; } void Member::remove_observer( PyObject* observer ) { if( modify_guard ) { ModifyTask* task = new RemoveTask( this, observer ); modify_guard->add_task( task ); return; } if( static_observers ) { cppy::ptr obptr( cppy::incref( observer ) ); std::vector::iterator it; std::vector::iterator end = static_observers->end(); for( it = static_observers->begin(); it != end; ++it ) { if( it->match( obptr ) ) { static_observers->erase( it ); if( static_observers->size() == 0 ) { delete static_observers; static_observers = 0; } break; } } } } bool Member::has_observer( PyObject* observer, uint8_t change_types ) { if( !static_observers ) return false; cppy::ptr obptr( cppy::incref( observer ) ); std::vector::iterator it; std::vector::iterator end = static_observers->end(); for( it = static_observers->begin(); it != end; ++it ) { if( it->match( obptr ) && it->enabled( change_types )) return true; } return false; } bool Member::notify( CAtom* atom, PyObject* args, PyObject* kwargs, uint8_t change_types) { if( static_observers && atom->get_notifications_enabled() ) { ModifyGuard guard( *this ); cppy::ptr argsptr( cppy::incref( args ) ); cppy::ptr kwargsptr( cppy::xincref( kwargs ) ); cppy::ptr objectptr( cppy::incref( pyobject_cast( atom ) ) ); cppy::ptr callable; std::vector::iterator it; std::vector::iterator end = static_observers->end(); for( it = static_observers->begin(); it != end; ++it ) { if ( !it->enabled( change_types ) ) continue; // Ignore if( PyUnicode_CheckExact( it->m_observer.get() ) ) { callable = objectptr.getattr( it->m_observer ); if( !callable ) return false; } else { callable = it->m_observer; } cppy::ptr ok( callable.call( argsptr, kwargsptr ) ); if( !ok ) return false; } } return true; } bool Member::Ready() { // The reference will be handled by the module to which we will add the type TypeObject = pytype_cast( PyType_FromSpec( &TypeObject_Spec ) ); if( !TypeObject ) { return false; // LCOV_EXCL_LINE (type creation failed, very unlikely) } undefined = PyUnicode_FromString( "" ); if( !undefined ) { return false; // LCOV_EXCL_LINE (string creation failed, very unlikely) } return true; } } // namespace atom atom-0.12.1/atom/src/member.h000066400000000000000000000130761506756731600157310ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #pragma once #include #include #include "inttypes.h" #include "behaviors.h" #include "catom.h" #include "modifyguard.h" #include "observer.h" #ifndef UINT64_C #define UINT64_C( c ) ( c ## ULL ) #endif #define member_cast( o ) ( reinterpret_cast( o ) ) namespace atom { PACK(struct MemberModes { GetAttr::Mode getattr: 4; PostGetAttr::Mode post_getattr: 3; SetAttr::Mode setattr: 4; PostSetAttr::Mode post_setattr: 3; DefaultValue::Mode default_value: 4; Validate::Mode validate: 5; PostValidate::Mode post_validate: 3; DelAttr::Mode delattr: 3; GetState::Mode getstate: 3; }); struct Member { PyObject_HEAD PyObject* name; PyObject* metadata; PyObject* getattr_context; PyObject* setattr_context; PyObject* delattr_context; PyObject* validate_context; PyObject* post_getattr_context; PyObject* post_setattr_context; PyObject* default_value_context; PyObject* post_validate_context; PyObject* getstate_context; ModifyGuard* modify_guard; std::vector* static_observers; MemberModes modes; uint32_t index; static PyType_Spec TypeObject_Spec; static PyTypeObject* TypeObject; static bool Ready(); // ModifyGuard template interface ModifyGuard* get_modify_guard() { return modify_guard; } void set_modify_guard( ModifyGuard* guard ) { modify_guard = guard; } GetAttr::Mode get_getattr_mode() { return modes.getattr; } void set_getattr_mode( GetAttr::Mode mode ) { modes.getattr = mode; } SetAttr::Mode get_setattr_mode() { return modes.setattr; } void set_setattr_mode( SetAttr::Mode mode ) { modes.setattr = mode; } PostGetAttr::Mode get_post_getattr_mode() { return modes.post_getattr; } void set_post_getattr_mode( PostGetAttr::Mode mode ) { modes.post_getattr = mode; } PostSetAttr::Mode get_post_setattr_mode() { return modes.post_setattr; } void set_post_setattr_mode( PostSetAttr::Mode mode ) { modes.post_setattr = mode; } DefaultValue::Mode get_default_value_mode() { return modes.default_value; } void set_default_value_mode( DefaultValue::Mode mode ) { modes.default_value = mode; } Validate::Mode get_validate_mode() { return modes.validate; } void set_validate_mode( Validate::Mode mode ) { modes.validate = mode; } PostValidate::Mode get_post_validate_mode() { return modes.post_validate; } void set_post_validate_mode( PostValidate::Mode mode ) { modes.post_validate = mode; } DelAttr::Mode get_delattr_mode() { return modes.delattr; } void set_delattr_mode( DelAttr::Mode mode ) { modes.delattr = mode; } GetState::Mode get_getstate_mode() { return modes.getstate; } void set_getstate_mode( GetState::Mode mode ) { modes.getstate = mode; } PyObject* getattr( CAtom* atom ); int setattr( CAtom* atom, PyObject* value ); int delattr( CAtom* atom ); PyObject* post_getattr( CAtom* atom, PyObject* value ); int post_setattr( CAtom* atom, PyObject* oldvalue, PyObject* newvalue ); PyObject* default_value( CAtom* atom ); PyObject* validate( CAtom* atom, PyObject* oldvalue, PyObject* newvalue ); PyObject* post_validate( CAtom* atom, PyObject* oldvalue, PyObject* newvalue ); PyObject* full_validate( CAtom* atom, PyObject* oldvalue, PyObject* newvalue ); PyObject* should_getstate( CAtom* atom ); bool has_observers() { return static_observers && static_observers->size() > 0; } bool has_observers( uint8_t change_types ); bool has_observer( PyObject* observer ) { return has_observer( observer, ChangeType::Any ); } bool has_observer( PyObject* observer, uint8_t change_types ); void add_observer( PyObject* observer ) { return add_observer( observer, ChangeType::Any ); } void add_observer( PyObject* observer, uint8_t change_types ); void remove_observer( PyObject* observer ); bool notify( CAtom* atom, PyObject* args, PyObject* kwargs ) { return notify( atom, args, kwargs, ChangeType::Any ); } bool notify( CAtom* atom, PyObject* args, PyObject* kwargs, uint8_t change_types ); static bool check_context( GetAttr::Mode mode, PyObject* context ); static bool check_context( PostGetAttr::Mode mode, PyObject* context ); static bool check_context( SetAttr::Mode mode, PyObject* context ); static bool check_context( PostSetAttr::Mode mode, PyObject* context ); static bool check_context( DefaultValue::Mode mode, PyObject* context ); static bool check_context( Validate::Mode mode, PyObject* context ); static bool check_context( PostValidate::Mode mode, PyObject* context ); static bool check_context( DelAttr::Mode mode, PyObject* context ); static bool check_context( GetState::Mode mode, PyObject* context ); static int TypeCheck( PyObject* object ) { return PyObject_TypeCheck( object, TypeObject ); } }; } // namespace atom atom-0.12.1/atom/src/memberchange.cpp000066400000000000000000000142321506756731600174250ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #include #include "memberchange.h" namespace atom { namespace MemberChange { static PyObject* createstr; static PyObject* updatestr; static PyObject* deletestr; static PyObject* eventstr; static PyObject* propertystr; static PyObject* typestr; static PyObject* objectstr; static PyObject* namestr; static PyObject* valuestr; static PyObject* oldvaluestr; PyObject* created( CAtom* atom, Member* member, PyObject* value ) { cppy::ptr dict( PyDict_New() ); if( !dict ) { return 0; // LCOV_EXCL_LINE (failed dict creation) } if( PyDict_SetItem( dict.get(), typestr, createstr ) != 0 ) { return 0; // LCOV_EXCL_LINE (failed item setting) } if( PyDict_SetItem( dict.get(), objectstr, pyobject_cast( atom ) ) != 0 ) { return 0; // LCOV_EXCL_LINE (failed item setting) } if( PyDict_SetItem( dict.get(), namestr, member->name ) != 0 ) { return 0; // LCOV_EXCL_LINE (failed item setting) } if( PyDict_SetItem( dict.get(), valuestr, value ) != 0 ) { return 0; // LCOV_EXCL_LINE (failed item setting) } return dict.release(); } PyObject* updated( CAtom* atom, Member* member, PyObject* oldvalue, PyObject* newvalue ) { cppy::ptr dict( PyDict_New() ); if( !dict ) { return 0; // LCOV_EXCL_LINE (dict creation failed) } if( PyDict_SetItem( dict.get(), typestr, updatestr ) != 0 ) { return 0; // LCOV_EXCL_LINE (failed item setting) } if( PyDict_SetItem( dict.get(), objectstr, pyobject_cast( atom ) ) != 0 ) { return 0; // LCOV_EXCL_LINE (failed item setting) } if( PyDict_SetItem( dict.get(), namestr, member->name ) != 0 ) { return 0; // LCOV_EXCL_LINE (failed item setting) } if( PyDict_SetItem( dict.get(), oldvaluestr, oldvalue ) != 0 ) { return 0; // LCOV_EXCL_LINE (failed item setting) } if( PyDict_SetItem( dict.get(), valuestr, newvalue ) != 0 ) { return 0; // LCOV_EXCL_LINE (failed item setting) } return dict.release(); } PyObject* deleted( CAtom* atom, Member* member, PyObject* value ) { cppy::ptr dict( PyDict_New() ); if( !dict ) { return 0; // LCOV_EXCL_LINE (failed dict creation) } if( PyDict_SetItem( dict.get(), typestr, deletestr ) != 0 ) { return 0; // LCOV_EXCL_LINE (failed item setting) } if( PyDict_SetItem( dict.get(), objectstr, pyobject_cast( atom ) ) != 0 ) { return 0; // LCOV_EXCL_LINE (failed item setting) } if( PyDict_SetItem( dict.get(), namestr, member->name ) != 0 ) { return 0; // LCOV_EXCL_LINE (failed item setting) } if( PyDict_SetItem( dict.get(), valuestr, value ) != 0 ) { return 0; // LCOV_EXCL_LINE (failed item setting) } return dict.release(); } PyObject* event( CAtom* atom, Member* member, PyObject* value ) { cppy::ptr dict( PyDict_New() ); if( !dict ) { return 0; // LCOV_EXCL_LINE (failed dict creation) } if( PyDict_SetItem( dict.get(), typestr, eventstr ) != 0 ) { return 0; // LCOV_EXCL_LINE (failed item setting) } if( PyDict_SetItem( dict.get(), objectstr, pyobject_cast( atom ) ) != 0 ) { return 0; // LCOV_EXCL_LINE (failed item setting) } if( PyDict_SetItem( dict.get(), namestr, member->name ) != 0 ) { return 0; // LCOV_EXCL_LINE (failed item setting) } if( PyDict_SetItem( dict.get(), valuestr, value ) != 0 ) { return 0; // LCOV_EXCL_LINE (failed item setting) } return dict.release(); } PyObject* property( CAtom* atom, Member* member, PyObject* oldvalue, PyObject* newvalue ) { cppy::ptr dict( PyDict_New() ); if( !dict ) { return 0; } if( PyDict_SetItem( dict.get(), typestr, propertystr ) != 0) { return 0; } if( PyDict_SetItem( dict.get(), objectstr, pyobject_cast( atom ) ) != 0 ) { return 0; } if( PyDict_SetItem( dict.get(), namestr, member->name ) != 0 ) { return 0; } if( PyDict_SetItem( dict.get(), oldvaluestr, oldvalue ) != 0 ) { return 0; } if( PyDict_SetItem( dict.get(), valuestr, newvalue ) != 0 ) { return 0; } return dict.release(); } } // namespace MemberChange bool init_memberchange() { static bool alloced = false; if( alloced ) { return true; } MemberChange::createstr = PyUnicode_InternFromString( "create" ); if( !MemberChange::createstr ) { return false; } MemberChange::updatestr = PyUnicode_InternFromString( "update" ); if( !MemberChange::updatestr ) { return false; } MemberChange::deletestr = PyUnicode_InternFromString( "delete" ); if( !MemberChange::deletestr ) { return false; } MemberChange::eventstr = PyUnicode_InternFromString( "event" ); if( !MemberChange::eventstr ) { return false; } MemberChange::propertystr = PyUnicode_InternFromString( "property" ); if( !MemberChange::propertystr ) { return false; } MemberChange::typestr = PyUnicode_InternFromString( "type" ); if( !MemberChange::typestr ) { return false; } MemberChange::objectstr = PyUnicode_InternFromString( "object" ); if( !MemberChange::objectstr ) { return false; } MemberChange::namestr = PyUnicode_InternFromString( "name" ); if( !MemberChange::namestr ) { return false; } MemberChange::valuestr = PyUnicode_InternFromString( "value" ); if( !MemberChange::valuestr ) { return false; } MemberChange::oldvaluestr = PyUnicode_InternFromString( "oldvalue" ); if( !MemberChange::oldvaluestr ) { return false; } alloced = true; return true; } } // namespace atom atom-0.12.1/atom/src/memberchange.h000066400000000000000000000016241506756731600170730ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #pragma once #include "catom.h" #include "member.h" namespace atom { namespace MemberChange { PyObject* created( CAtom* atom, Member* member, PyObject* value ); PyObject* updated( CAtom* atom, Member* member, PyObject* oldvalue, PyObject* newvalue ); PyObject* deleted( CAtom* atom, Member* member, PyObject* value ); PyObject* event( CAtom* atom, Member* member, PyObject* value ); PyObject* property( CAtom* atom, Member* member, PyObject* oldvalue, PyObject* newvalue ); } // namespace MemberChange bool init_memberchange(); } // namespace atom atom-0.12.1/atom/src/methodwrapper.cpp000066400000000000000000000202611506756731600176700ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #include #include "methodwrapper.h" #include "catom.h" #include "catompointer.h" #include "packagenaming.h" namespace atom { namespace { /*----------------------------------------------------------------------------- | MethodWrapper |----------------------------------------------------------------------------*/ void MethodWrapper_dealloc( MethodWrapper* self ) { Py_CLEAR( self->im_selfref ); Py_CLEAR( self->im_func ); Py_TYPE(self)->tp_free( pyobject_cast( self ) ); } PyObject* MethodWrapper__call__( MethodWrapper* self, PyObject* args, PyObject* kwargs ) { PyObject* im_self = PyWeakref_GET_OBJECT( self->im_selfref ); if( im_self != Py_None ) { cppy::ptr method( PyMethod_New( self->im_func, im_self ) ); if( !method ) return 0; return PyObject_Call( method.get(), args, kwargs ); } Py_RETURN_NONE; } PyObject* MethodWrapper_richcompare( MethodWrapper* self, PyObject* other, int op ) { if( op == Py_EQ ) { if( PyMethod_Check( other ) && PyMethod_GET_SELF( other ) ) { if( ( self->im_func == PyMethod_GET_FUNCTION( other ) ) && ( PyWeakref_GET_OBJECT( self->im_selfref ) == PyMethod_GET_SELF( other ) ) ) Py_RETURN_TRUE; Py_RETURN_FALSE; } else if( MethodWrapper::TypeCheck( other ) ) { MethodWrapper* wrapper = reinterpret_cast( other ); if( ( self->im_func == wrapper->im_func ) && ( self->im_selfref == wrapper->im_selfref ) ) Py_RETURN_TRUE; Py_RETURN_FALSE; } else Py_RETURN_FALSE; } Py_RETURN_NOTIMPLEMENTED; } int MethodWrapper__bool__( MethodWrapper* self ) { if( PyWeakref_GET_OBJECT( self->im_selfref ) != Py_None ) return 1; return 0; } static PyType_Slot MethodWrapper_Type_slots[] = { { Py_tp_dealloc, void_cast( MethodWrapper_dealloc ) }, /* tp_dealloc */ { Py_tp_call, void_cast( MethodWrapper__call__ ) }, /* tp_call */ { Py_tp_richcompare, void_cast( MethodWrapper_richcompare ) }, /* tp_richcompare */ { Py_tp_alloc, void_cast( PyType_GenericAlloc ) }, /* tp_alloc */ { Py_tp_free, void_cast( PyObject_Del ) }, /* tp_free */ { Py_nb_bool, void_cast( MethodWrapper__bool__ ) }, /* nb_bool */ { 0, 0 }, }; } // namespace // Initialize static variables (otherwise the compiler eliminates them) PyTypeObject* MethodWrapper::TypeObject = NULL; PyType_Spec MethodWrapper::TypeObject_Spec = { PACKAGE_TYPENAME( "MethodWrapper" ), /* tp_name */ sizeof( MethodWrapper ), /* tp_basicsize */ 0, /* tp_itemsize */ Py_TPFLAGS_DEFAULT, /* tp_flags */ MethodWrapper_Type_slots /* slots */ }; bool MethodWrapper::Ready() { // The reference will be handled by the module to which we will add the type TypeObject = pytype_cast( PyType_FromSpec( &TypeObject_Spec ) ); if( !TypeObject ) { return false; } return true; } /*----------------------------------------------------------------------------- | AtomMethodWrapper |----------------------------------------------------------------------------*/ namespace { void AtomMethodWrapper_dealloc( AtomMethodWrapper* self ) { Py_CLEAR( self->im_func ); // manual destructor since Python malloc'd and zero'd the struct self->pointer.~CAtomPointer(); Py_TYPE(self)->tp_free( pyobject_cast( self ) ); } PyObject* AtomMethodWrapper__call__( AtomMethodWrapper* self, PyObject* args, PyObject* kwargs ) { if( self->pointer.data() ) { PyObject* im_self = pyobject_cast( self->pointer.data() ); cppy::ptr method( PyMethod_New( self->im_func, im_self ) ); if( !method ) return 0; return PyObject_Call( method.get(), args, kwargs ); } Py_RETURN_NONE; } PyObject* AtomMethodWrapper_richcompare( AtomMethodWrapper* self, PyObject* other, int op ) { if( op == Py_EQ ) { if( PyMethod_Check( other ) && PyMethod_GET_SELF( other ) ) { if( ( self->im_func == PyMethod_GET_FUNCTION( other ) ) && ( pyobject_cast( self->pointer.data() ) == PyMethod_GET_SELF( other ) ) ) Py_RETURN_TRUE; Py_RETURN_FALSE; } else if( AtomMethodWrapper::TypeCheck( other ) ) { AtomMethodWrapper* wrapper = reinterpret_cast( other ); if( ( self->im_func == wrapper->im_func ) && ( self->pointer.data() == wrapper->pointer.data() ) ) Py_RETURN_TRUE; Py_RETURN_FALSE; } else Py_RETURN_FALSE; } Py_RETURN_NOTIMPLEMENTED; } int AtomMethodWrapper__bool__( AtomMethodWrapper* self ) { if( self->pointer.data() ) return 1; return 0; } static PyType_Slot AtomMethodWrapper_Type_slots[] = { { Py_tp_dealloc, void_cast( AtomMethodWrapper_dealloc ) }, /* tp_dealloc */ { Py_tp_call, void_cast( AtomMethodWrapper__call__ ) }, /* tp_call */ { Py_tp_richcompare, void_cast( AtomMethodWrapper_richcompare ) }, /* tp_richcompare */ { Py_tp_alloc, void_cast( PyType_GenericAlloc ) }, /* tp_alloc */ { Py_tp_free, void_cast( PyObject_Del ) }, /* tp_free */ { Py_nb_bool, void_cast( AtomMethodWrapper__bool__ ) }, /* nb_bool */ { 0, 0 }, }; } // namespace // Initialize static variables (otherwise the compiler eliminates them) PyTypeObject* AtomMethodWrapper::TypeObject = NULL; PyType_Spec AtomMethodWrapper::TypeObject_Spec = { PACKAGE_TYPENAME( "AtomMethodWrapper" ), /* tp_name */ sizeof( MethodWrapper ), /* tp_basicsize */ 0, /* tp_itemsize */ Py_TPFLAGS_DEFAULT, /* tp_flags */ AtomMethodWrapper_Type_slots /* slots */ }; bool AtomMethodWrapper::Ready() { // The reference will be handled by the module to which we will add the type TypeObject = pytype_cast( PyType_FromSpec( &TypeObject_Spec ) ); if( !TypeObject ) { return false; // LCOV_EXCL_LINE (failed type init) } return true; } /*----------------------------------------------------------------------------- | External API |----------------------------------------------------------------------------*/ PyObject* MethodWrapper::New( PyObject* method ) { if( !PyMethod_Check( method ) ) return cppy::type_error( method, "MethodType" ); if( !PyMethod_GET_SELF( method ) ) return cppy::type_error( "cannot wrap unbound method" ); cppy::ptr pywrapper; if( CAtom::TypeCheck( PyMethod_GET_SELF( method ) ) ) { pywrapper = PyType_GenericNew( AtomMethodWrapper::TypeObject, 0, 0 ); if( !pywrapper ) return 0; AtomMethodWrapper* wrapper = reinterpret_cast( pywrapper.get() ); wrapper->im_func = cppy::incref( PyMethod_GET_FUNCTION( method ) ); // placement new since Python malloc'd and zero'd the struct new( &wrapper->pointer ) CAtomPointer( catom_cast( PyMethod_GET_SELF( method ) ) ); } else { cppy::ptr wr( PyWeakref_NewRef( PyMethod_GET_SELF( method ), 0 ) ); if( !wr ) return 0; pywrapper = PyType_GenericNew( MethodWrapper::TypeObject, 0, 0 ); if( !pywrapper ) return 0; MethodWrapper* wrapper = reinterpret_cast( pywrapper.get() ); wrapper->im_func = cppy::incref( PyMethod_GET_FUNCTION( method ) ); wrapper->im_selfref = wr.release(); } return pywrapper.release(); } } // namespace atom atom-0.12.1/atom/src/methodwrapper.h000066400000000000000000000023061506756731600173350ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #pragma once #include "catompointer.h" namespace atom { // POD struct - all member fields are considered private struct MethodWrapper { PyObject_HEAD PyObject* im_func; PyObject* im_selfref; static PyType_Spec TypeObject_Spec; static PyTypeObject* TypeObject; static bool Ready(); static PyObject* New( PyObject* method ); static bool TypeCheck( PyObject* ob ) { return PyObject_TypeCheck( ob, TypeObject ) != 0; } }; // POD struct - all member fields are considered private struct AtomMethodWrapper { PyObject_HEAD PyObject* im_func; CAtomPointer pointer; // constructed with placement new static PyType_Spec TypeObject_Spec; static PyTypeObject* TypeObject; static bool Ready(); static bool TypeCheck( PyObject* ob ) { return PyObject_TypeCheck( ob, TypeObject ) != 0; } }; } // namespace atom atom-0.12.1/atom/src/modifyguard.h000066400000000000000000000034521506756731600167710ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #pragma once #include namespace atom { struct ModifyTask { ModifyTask() {} virtual ~ModifyTask() {} virtual void run() = 0; }; template class ModifyGuard { public: ModifyGuard( _T& owner ) : m_owner( owner ) { if( !m_owner.get_modify_guard() ) m_owner.set_modify_guard( this ); } ~ModifyGuard() { // If an exception occurred we store it and restore it after the // modification have been done as otherwise it can (Python 3) cause // boolean tests (PyObject_IsTrue) to fail for wrong reasons. bool exception_set = false; PyObject *type, *value, *traceback; if( PyErr_Occurred() ){ PyErr_Fetch(&type, &value, &traceback); exception_set = true; } if( m_owner.get_modify_guard() == this ) { m_owner.set_modify_guard( 0 ); std::vector::iterator it; std::vector::iterator end = m_tasks.end(); for( it = m_tasks.begin(); it != end; ++it ) { ( *it )->run(); delete *it; } } // Restore previous exception if one was set. if( exception_set ) PyErr_Restore(type, value, traceback); } void add_task( ModifyTask* task ) { m_tasks.push_back( task ); } private: _T& m_owner; std::vector m_tasks; }; } // namespace atom atom-0.12.1/atom/src/msstdint.h000066400000000000000000000176341506756731600163330ustar00rootroot00000000000000// ISO C9x compliant stdint.h for Microsoft Visual Studio // Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 // // Copyright (c) 2006-2024 Alexander Chemeris // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // 3. Neither the name of the product nor the names of its contributors may // be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO // EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // /////////////////////////////////////////////////////////////////////////////// #ifndef _MSC_VER // [ #error "Use this header only with Microsoft Visual C++ compilers!" #endif // _MSC_VER ] #ifndef _MSC_STDINT_H_ // [ #define _MSC_STDINT_H_ #if _MSC_VER > 1000 #pragma once #endif #if _MSC_VER >= 1600 // [ #include #else // ] _MSC_VER >= 1600 [ #include // For Visual Studio 6 in C++ mode and for many Visual Studio versions when // compiling for ARM we should wrap include with 'extern "C++" {}' // or compiler give many errors like this: // error C2733: second C linkage of overloaded function 'wmemchr' not allowed #ifdef __cplusplus extern "C" { #endif # include #ifdef __cplusplus } #endif // Define _W64 macros to mark types changing their size, like intptr_t. #ifndef _W64 # if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 # define _W64 __w64 # else # define _W64 # endif #endif // 7.18.1 Integer types // 7.18.1.1 Exact-width integer types // Visual Studio 6 and Embedded Visual C++ 4 doesn't // realize that, e.g. char has the same size as __int8 // so we give up on __intX for them. #if (_MSC_VER < 1300) typedef signed char int8_t; typedef signed short int16_t; typedef signed int int32_t; typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; #else typedef signed __int8 int8_t; typedef signed __int16 int16_t; typedef signed __int32 int32_t; typedef unsigned __int8 uint8_t; typedef unsigned __int16 uint16_t; typedef unsigned __int32 uint32_t; #endif typedef signed __int64 int64_t; typedef unsigned __int64 uint64_t; // 7.18.1.2 Minimum-width integer types typedef int8_t int_least8_t; typedef int16_t int_least16_t; typedef int32_t int_least32_t; typedef int64_t int_least64_t; typedef uint8_t uint_least8_t; typedef uint16_t uint_least16_t; typedef uint32_t uint_least32_t; typedef uint64_t uint_least64_t; // 7.18.1.3 Fastest minimum-width integer types typedef int8_t int_fast8_t; typedef int16_t int_fast16_t; typedef int32_t int_fast32_t; typedef int64_t int_fast64_t; typedef uint8_t uint_fast8_t; typedef uint16_t uint_fast16_t; typedef uint32_t uint_fast32_t; typedef uint64_t uint_fast64_t; // 7.18.1.4 Integer types capable of holding object pointers #ifdef _WIN64 // [ typedef signed __int64 intptr_t; typedef unsigned __int64 uintptr_t; #else // _WIN64 ][ typedef _W64 signed int intptr_t; typedef _W64 unsigned int uintptr_t; #endif // _WIN64 ] // 7.18.1.5 Greatest-width integer types typedef int64_t intmax_t; typedef uint64_t uintmax_t; // 7.18.2 Limits of specified-width integer types #if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 // 7.18.2.1 Limits of exact-width integer types #define INT8_MIN ((int8_t)_I8_MIN) #define INT8_MAX _I8_MAX #define INT16_MIN ((int16_t)_I16_MIN) #define INT16_MAX _I16_MAX #define INT32_MIN ((int32_t)_I32_MIN) #define INT32_MAX _I32_MAX #define INT64_MIN ((int64_t)_I64_MIN) #define INT64_MAX _I64_MAX #define UINT8_MAX _UI8_MAX #define UINT16_MAX _UI16_MAX #define UINT32_MAX _UI32_MAX #define UINT64_MAX _UI64_MAX // 7.18.2.2 Limits of minimum-width integer types #define INT_LEAST8_MIN INT8_MIN #define INT_LEAST8_MAX INT8_MAX #define INT_LEAST16_MIN INT16_MIN #define INT_LEAST16_MAX INT16_MAX #define INT_LEAST32_MIN INT32_MIN #define INT_LEAST32_MAX INT32_MAX #define INT_LEAST64_MIN INT64_MIN #define INT_LEAST64_MAX INT64_MAX #define UINT_LEAST8_MAX UINT8_MAX #define UINT_LEAST16_MAX UINT16_MAX #define UINT_LEAST32_MAX UINT32_MAX #define UINT_LEAST64_MAX UINT64_MAX // 7.18.2.3 Limits of fastest minimum-width integer types #define INT_FAST8_MIN INT8_MIN #define INT_FAST8_MAX INT8_MAX #define INT_FAST16_MIN INT16_MIN #define INT_FAST16_MAX INT16_MAX #define INT_FAST32_MIN INT32_MIN #define INT_FAST32_MAX INT32_MAX #define INT_FAST64_MIN INT64_MIN #define INT_FAST64_MAX INT64_MAX #define UINT_FAST8_MAX UINT8_MAX #define UINT_FAST16_MAX UINT16_MAX #define UINT_FAST32_MAX UINT32_MAX #define UINT_FAST64_MAX UINT64_MAX // 7.18.2.4 Limits of integer types capable of holding object pointers #ifdef _WIN64 // [ # define INTPTR_MIN INT64_MIN # define INTPTR_MAX INT64_MAX # define UINTPTR_MAX UINT64_MAX #else // _WIN64 ][ # define INTPTR_MIN INT32_MIN # define INTPTR_MAX INT32_MAX # define UINTPTR_MAX UINT32_MAX #endif // _WIN64 ] // 7.18.2.5 Limits of greatest-width integer types #define INTMAX_MIN INT64_MIN #define INTMAX_MAX INT64_MAX #define UINTMAX_MAX UINT64_MAX // 7.18.3 Limits of other integer types #ifdef _WIN64 // [ # define PTRDIFF_MIN _I64_MIN # define PTRDIFF_MAX _I64_MAX #else // _WIN64 ][ # define PTRDIFF_MIN _I32_MIN # define PTRDIFF_MAX _I32_MAX #endif // _WIN64 ] #define SIG_ATOMIC_MIN INT_MIN #define SIG_ATOMIC_MAX INT_MAX #ifndef SIZE_MAX // [ # ifdef _WIN64 // [ # define SIZE_MAX _UI64_MAX # else // _WIN64 ][ # define SIZE_MAX _UI32_MAX # endif // _WIN64 ] #endif // SIZE_MAX ] // WCHAR_MIN and WCHAR_MAX are also defined in #ifndef WCHAR_MIN // [ # define WCHAR_MIN 0 #endif // WCHAR_MIN ] #ifndef WCHAR_MAX // [ # define WCHAR_MAX _UI16_MAX #endif // WCHAR_MAX ] #define WINT_MIN 0 #define WINT_MAX _UI16_MAX #endif // __STDC_LIMIT_MACROS ] // 7.18.4 Limits of other integer types #if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 // 7.18.4.1 Macros for minimum-width integer constants #define INT8_C(val) val##i8 #define INT16_C(val) val##i16 #define INT32_C(val) val##i32 #define INT64_C(val) val##i64 #define UINT8_C(val) val##ui8 #define UINT16_C(val) val##ui16 #define UINT32_C(val) val##ui32 #define UINT64_C(val) val##ui64 // 7.18.4.2 Macros for greatest-width integer constants // These #ifndef's are needed to prevent collisions with . // Check out Issue 9 for the details. #ifndef INTMAX_C // [ # define INTMAX_C INT64_C #endif // INTMAX_C ] #ifndef UINTMAX_C // [ # define UINTMAX_C UINT64_C #endif // UINTMAX_C ] #endif // __STDC_CONSTANT_MACROS ] #endif // _MSC_VER >= 1600 ] #endif // _MSC_STDINT_H_ ] atom-0.12.1/atom/src/observer.h000066400000000000000000000022021506756731600162760ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2022-2024, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #pragma once #include #include "utils.h" namespace atom { extern PyObject* PyChangeType; namespace ChangeType { enum Type { Create = 1, Update = 2, Delete = 4, Event = 8, Property = 16, Container = 32, Any = 0xFF, }; } // end ChangeType struct Observer { Observer( cppy::ptr& observer, uint8_t change_types ) : m_observer( observer ), m_change_types( change_types ) {} ~Observer() {} bool match(const cppy::ptr& observer ) const { return m_observer == observer || utils::safe_richcompare( m_observer, observer, Py_EQ ); } inline bool enabled( uint8_t change_types ) const { return (m_change_types & change_types) != 0; } cppy::ptr m_observer; uint8_t m_change_types; }; } // namespace atom atom-0.12.1/atom/src/observerpool.cpp000066400000000000000000000176551506756731600175450ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #include "observerpool.h" #include "utils.h" namespace atom { namespace { struct BaseTask : public ModifyTask { BaseTask( ObserverPool& pool, cppy::ptr& topic, cppy::ptr& observer ) : m_pool( pool ), m_topic( topic ), m_observer( observer ) {} ObserverPool& m_pool; cppy::ptr m_topic; cppy::ptr m_observer; }; struct AddTask : public BaseTask { AddTask( ObserverPool& pool, cppy::ptr& topic, cppy::ptr& observer, uint8_t change_types ) : BaseTask( pool, topic, observer ), m_change_types(change_types) {} void run() { m_pool.add( m_topic, m_observer, m_change_types ); } uint8_t m_change_types; }; struct RemoveTask : public BaseTask { RemoveTask( ObserverPool& pool, cppy::ptr& topic, cppy::ptr& observer ) : BaseTask( pool, topic, observer ) {} void run() { m_pool.remove( m_topic, m_observer ); } }; struct RemoveTopicTask : ModifyTask { RemoveTopicTask( ObserverPool& pool, cppy::ptr& topic ) : m_pool( pool ), m_topic( topic ) {} void run() { m_pool.remove( m_topic ); } ObserverPool& m_pool; cppy::ptr m_topic; }; } // namespace bool ObserverPool::has_topic( cppy::ptr& topic ) { std::vector::iterator topic_it; std::vector::iterator topic_end = m_topics.end(); for( topic_it = m_topics.begin(); topic_it != topic_end; ++topic_it ) { if( topic_it->match( topic ) ) return true; } return false; } bool ObserverPool::has_observer( cppy::ptr& topic, cppy::ptr& observer, uint8_t change_types ) { uint32_t obs_offset = 0; std::vector::iterator topic_it; std::vector::iterator topic_end = m_topics.end(); for( topic_it = m_topics.begin(); topic_it != topic_end; ++topic_it ) { if( topic_it->match( topic ) ) { std::vector::iterator obs_it; std::vector::iterator obs_end; obs_it = m_observers.begin() + obs_offset; obs_end = obs_it + topic_it->m_count; for( ; obs_it != obs_end; ++obs_it ) { if( obs_it->match( observer ) && obs_it->enabled( change_types ) ) return true; } return false; } obs_offset += topic_it->m_count; } return false; } void ObserverPool::add( cppy::ptr& topic, cppy::ptr& observer, uint8_t change_types ) { if( m_modify_guard ) { ModifyTask* task = new AddTask( *this, topic, observer, change_types ); m_modify_guard->add_task( task ); return; } uint32_t obs_offset = 0; std::vector::iterator topic_it; std::vector::iterator topic_end = m_topics.end(); for( topic_it = m_topics.begin(); topic_it != topic_end; ++topic_it ) { if( topic_it->match( topic ) ) { std::vector::iterator obs_it; std::vector::iterator obs_end; std::vector::iterator obs_free; obs_it = m_observers.begin() + obs_offset; obs_end = obs_it + topic_it->m_count; obs_free = obs_end; for( ; obs_it != obs_end; ++obs_it ) { if( obs_it->match( observer ) ) { obs_it->m_change_types = change_types; return; } if( !obs_it->m_observer.is_truthy() ) obs_free = obs_it; } if( obs_free == obs_end ) { m_observers.insert( obs_end, Observer( observer, change_types ) ); ++topic_it->m_count; } else *obs_free = Observer( observer, change_types ); return; } obs_offset += topic_it->m_count; } m_topics.push_back( Topic( topic, 1 ) ); m_observers.push_back( Observer(observer, change_types) ); } void ObserverPool::remove( cppy::ptr& topic, cppy::ptr& observer ) { if( m_modify_guard ) { ModifyTask* task = new RemoveTask( *this, topic, observer ); m_modify_guard->add_task( task ); return; } uint32_t obs_offset = 0; std::vector::iterator topic_it; std::vector::iterator topic_end = m_topics.end(); for( topic_it = m_topics.begin(); topic_it != topic_end; ++topic_it ) { if( topic_it->match( topic ) ) { std::vector::iterator obs_it; std::vector::iterator obs_end; obs_it = m_observers.begin() + obs_offset; obs_end = obs_it + topic_it->m_count; for( ; obs_it != obs_end; ++obs_it ) { if( obs_it->match( observer ) ) { m_observers.erase( obs_it ); if( ( --topic_it->m_count ) == 0 ) m_topics.erase( topic_it ); return; } } return; } obs_offset += topic_it->m_count; } } void ObserverPool::remove( cppy::ptr& topic ) { if( m_modify_guard ) { ModifyTask* task = new RemoveTopicTask( *this, topic ); m_modify_guard->add_task( task ); return; } uint32_t obs_offset = 0; std::vector::iterator topic_it; std::vector::iterator topic_end = m_topics.end(); for( topic_it = m_topics.begin(); topic_it != topic_end; ++topic_it ) { if( topic_it->match( topic ) ) { m_observers.erase( m_observers.begin() + obs_offset, m_observers.begin() + (obs_offset + topic_it->m_count) ); m_topics.erase( topic_it ); return; } obs_offset += topic_it->m_count; } } bool ObserverPool::notify( cppy::ptr& topic, cppy::ptr& args, cppy::ptr& kwargs, uint8_t change_types ) { ModifyGuard guard( *this ); uint32_t obs_offset = 0; std::vector::iterator topic_it; std::vector::iterator topic_end = m_topics.end(); for( topic_it = m_topics.begin(); topic_it != topic_end; ++topic_it ) { if( topic_it->match( topic ) ) { std::vector::iterator obs_it; std::vector::iterator obs_end; obs_it = m_observers.begin() + obs_offset; obs_end = obs_it + topic_it->m_count; for( ; obs_it != obs_end; ++obs_it ) { if( obs_it->m_observer.is_truthy() ) { if( obs_it->enabled( change_types ) && !obs_it->m_observer.call( args, kwargs ) ) return false; } else { ModifyTask* task = new RemoveTask( *this, topic, obs_it->m_observer ); m_modify_guard->add_task( task ); } } return true; } obs_offset += topic_it->m_count; } return true; } int ObserverPool::py_traverse( visitproc visit, void* arg ) { int vret; std::vector::iterator topic_it; std::vector::iterator topic_end = m_topics.end(); for( topic_it = m_topics.begin(); topic_it != topic_end; ++topic_it ) { vret = visit( topic_it->m_topic.get(), arg ); if( vret ) return vret; } std::vector::iterator obs_it; std::vector::iterator obs_end = m_observers.end(); for( obs_it = m_observers.begin(); obs_it != obs_end; ++obs_it ) { vret = visit( obs_it->m_observer.get(), arg ); if( vret ) return vret; } return 0; } } //namespace atom atom-0.12.1/atom/src/observerpool.h000066400000000000000000000056351506756731600172050ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #pragma once #include #include #include "platstdint.h" #include "observer.h" #include "modifyguard.h" #include "utils.h" namespace atom { class ObserverPool { struct Topic { Topic( cppy::ptr& topic ) : m_topic( topic ), m_count( 0 ) {} Topic( cppy::ptr& topic, uint32_t count ) : m_topic( topic ), m_count( count ) {} ~Topic() {} bool match( cppy::ptr& topic ) { return m_topic == topic || utils::safe_richcompare( m_topic, topic, Py_EQ ); } cppy::ptr m_topic; uint32_t m_count; }; // ModifyGuard template interface friend class ModifyGuard; ModifyGuard* get_modify_guard() { return m_modify_guard; } void set_modify_guard( ModifyGuard* guard ) { m_modify_guard = guard; } public: ObserverPool() : m_modify_guard( 0 ) {} ~ObserverPool() {} bool has_topic( cppy::ptr& topic ); bool has_observer( cppy::ptr& topic, cppy::ptr& observer ) { return has_observer( topic, observer, ChangeType::Any ); } bool has_observer( cppy::ptr& topic, cppy::ptr& observer, uint8_t change_types ); void add( cppy::ptr& topic, cppy::ptr& observer, uint8_t member_changes ); void remove( cppy::ptr& topic, cppy::ptr& observer ); void remove( cppy::ptr& topic ); bool notify( cppy::ptr& topic, cppy::ptr& args, cppy::ptr& kwargs ) { return notify( topic, args, kwargs, ChangeType::Any ); } bool notify( cppy::ptr& topic, cppy::ptr& args, cppy::ptr& kwargs, uint8_t change_types ); Py_ssize_t py_sizeof() { Py_ssize_t size = sizeof( ModifyGuard* ); size += sizeof( std::vector ) + sizeof( Topic ) * m_topics.capacity(); size += sizeof( std::vector ) + sizeof( Observer ) * m_observers.capacity(); return size; }; int py_traverse( visitproc visit, void* arg ); void py_clear() { m_topics.clear(); // Clearing the vector may cause arbitrary side effects on item // decref, including calls into methods which mutate the vector. // To avoid segfaults, first make the vector empty, then let the // destructors run for the old items. std::vector empty; m_observers.swap( empty ); } private: ModifyGuard* m_modify_guard; std::vector m_topics; std::vector m_observers; ObserverPool(const ObserverPool& other); ObserverPool& operator=(const ObserverPool&); }; } // namespace atom atom-0.12.1/atom/src/packagenaming.h000066400000000000000000000007041506756731600172410ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #pragma once #define PACKAGE_PREFIX "atom.catom" #define PACKAGE_TYPENAME( name ) "atom.catom." name atom-0.12.1/atom/src/platstdint.h000066400000000000000000000011161506756731600166400ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2014-2024,, Nucleic | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #pragma once #ifdef _MSC_VER #define PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop)) #include "msstdint.h" #else #include #define PACK( __Declaration__ ) __Declaration__ __attribute__((__packed__)) #endif atom-0.12.1/atom/src/postgetattrbehavior.cpp000066400000000000000000000053371506756731600211160ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #include #include "member.h" namespace atom { bool Member::check_context( PostGetAttr::Mode mode, PyObject* context ) { switch( mode ) { case PostGetAttr::Delegate: if( !Member::TypeCheck( context ) ) { cppy::type_error( context, "Member" ); return false; } break; case PostGetAttr::ObjectMethod_Value: case PostGetAttr::ObjectMethod_NameValue: case PostGetAttr::MemberMethod_ObjectValue: if( !PyUnicode_Check( context ) ) { cppy::type_error( context, "str" ); return false; } break; default: break; } return true; } namespace { PyObject* no_op_handler( Member* member, CAtom* atom, PyObject* value ) { return cppy::incref( value ); } PyObject* delegate_handler( Member* member, CAtom* atom, PyObject* value ) { Member* delegate = member_cast( member->post_getattr_context ); return delegate->post_getattr( atom, value ); } PyObject* object_method_value_handler( Member* member, CAtom* atom, PyObject* value ) { return PyObject_CallMethodOneArg( pyobject_cast( atom ), member->post_getattr_context, value ); } PyObject* object_method_name_value_handler( Member* member, CAtom* atom, PyObject* value ) { PyObject* args[] = { pyobject_cast( atom ), member->name, value }; return PyObject_VectorcallMethod( member->post_getattr_context, args, 3 | PY_VECTORCALL_ARGUMENTS_OFFSET, 0 ); } PyObject* member_method_object_value_handler( Member* member, CAtom* atom, PyObject* value ) { PyObject* args[] = { pyobject_cast( member ), pyobject_cast( atom ), value }; return PyObject_VectorcallMethod( member->post_getattr_context, args, 3 | PY_VECTORCALL_ARGUMENTS_OFFSET, 0 ); } typedef PyObject* ( *handler )( Member* member, CAtom* atom, PyObject* value ); static handler handlers[] = { no_op_handler, delegate_handler, object_method_value_handler, object_method_name_value_handler, member_method_object_value_handler }; } // namespace PyObject* Member::post_getattr( CAtom* atom, PyObject* value ) { if( get_post_getattr_mode() >= sizeof( handlers ) ) return no_op_handler( this, atom, value ); // LCOV_EXCL_LINE return handlers[ get_post_getattr_mode() ]( this, atom, value ); } } // namespace atom atom-0.12.1/atom/src/postsetattrbehavior.cpp000066400000000000000000000062331506756731600211260ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #include #include "member.h" namespace atom { bool Member::check_context( PostSetAttr::Mode mode, PyObject* context ) { switch( mode ) { case PostSetAttr::Delegate: if( !Member::TypeCheck( context ) ) { cppy::type_error( context, "Member" ); return false; } break; case PostSetAttr::ObjectMethod_OldNew: case PostSetAttr::ObjectMethod_NameOldNew: case PostSetAttr::MemberMethod_ObjectOldNew: if( !PyUnicode_Check( context ) ) { cppy::type_error( context, "str" ); return false; } break; default: break; } return true; } namespace { int no_op_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { return 0; } int delegate_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { Member* delegate = member_cast( member->post_setattr_context ); return delegate->post_setattr( atom, oldvalue, newvalue ); } int object_method_old_new_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { PyObject* args[] = { pyobject_cast( atom ), oldvalue, newvalue }; cppy::ptr ok( PyObject_VectorcallMethod( member->post_setattr_context, args, 3 | PY_VECTORCALL_ARGUMENTS_OFFSET, 0 ) ); if( !ok ) return -1; return 0; } int object_method_name_old_new_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { PyObject* args[] = { pyobject_cast( atom ), member->name, oldvalue, newvalue }; cppy::ptr ok( PyObject_VectorcallMethod( member->post_setattr_context, args, 4 | PY_VECTORCALL_ARGUMENTS_OFFSET, 0 ) ); if( !ok ) return -1; return 0; } int member_method_object_old_new_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { PyObject* args[] = { pyobject_cast( member ), pyobject_cast( atom ), oldvalue, newvalue }; cppy::ptr ok( PyObject_VectorcallMethod( member->post_setattr_context, args, 4 | PY_VECTORCALL_ARGUMENTS_OFFSET, 0 ) ); if( !ok ) return -1; return 0; } typedef int ( *handler )( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ); static handler handlers[] = { no_op_handler, delegate_handler, object_method_old_new_handler, object_method_name_old_new_handler, member_method_object_old_new_handler }; } // namespace int Member::post_setattr( CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { if( get_post_setattr_mode() >= sizeof( handlers ) ) return no_op_handler( this, atom, oldvalue, newvalue ); // LCOV_EXCL_LINE return handlers[ get_post_setattr_mode() ]( this, atom, oldvalue, newvalue ); } } // namespace atom atom-0.12.1/atom/src/postvalidatebehavior.cpp000066400000000000000000000061001506756731600212220ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #include #include "member.h" namespace atom { bool Member::check_context( PostValidate::Mode mode, PyObject* context ) { switch( mode ) { case PostValidate::Delegate: if( !Member::TypeCheck( context ) ) { cppy::type_error( context, "Member" ); return false; } break; case PostValidate::ObjectMethod_OldNew: case PostValidate::ObjectMethod_NameOldNew: case PostValidate::MemberMethod_ObjectOldNew: if( !PyUnicode_Check( context ) ) { cppy::type_error( context, "str" ); return false; } break; default: break; } return true; } namespace { PyObject* no_op_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { return cppy::incref( newvalue ); } PyObject* delegate_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { Member* delegate = member_cast( member->post_validate_context ); return delegate->post_validate( atom, oldvalue, newvalue ); } PyObject* object_method_old_new_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { PyObject* args[] = { pyobject_cast( atom ), oldvalue, newvalue }; return PyObject_VectorcallMethod( member->post_validate_context, args, 3 | PY_VECTORCALL_ARGUMENTS_OFFSET, 0 ); } PyObject* object_method_name_old_new_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { PyObject* args[] = { pyobject_cast( atom ), member->name, oldvalue, newvalue }; return PyObject_VectorcallMethod( member->post_validate_context, args, 4 | PY_VECTORCALL_ARGUMENTS_OFFSET, 0 ); } PyObject* member_method_object_old_new_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { PyObject* args[] = { pyobject_cast( member ), pyobject_cast( atom ), oldvalue, newvalue }; return PyObject_VectorcallMethod( member->post_validate_context, args, 4 | PY_VECTORCALL_ARGUMENTS_OFFSET, 0 ); } typedef PyObject* ( *handler )( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ); static handler handlers[] = { no_op_handler, delegate_handler, object_method_old_new_handler, object_method_name_old_new_handler, member_method_object_old_new_handler }; } // namespace PyObject* Member::post_validate( CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { if( get_post_validate_mode() >= sizeof( handlers ) ) return no_op_handler( this, atom, oldvalue, newvalue ); // LCOV_EXCL_LINE return handlers[ get_post_validate_mode() ]( this, atom, oldvalue, newvalue ); } } // namespace atom atom-0.12.1/atom/src/propertyhelper.cpp000066400000000000000000000054171506756731600201010ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #include #include "behaviors.h" #include "catom.h" #include "member.h" #include "memberchange.h" #include "utils.h" namespace atom { namespace { PyObject* property_args( CAtom* atom, Member* member, PyObject* oldvalue, PyObject* newvalue ) { cppy::ptr args( PyTuple_New( 1 ) ); if( !args ) { return 0; } cppy::ptr change( MemberChange::property( atom, member, oldvalue, newvalue ) ); if( !change ) { return 0; } PyTuple_SET_ITEM( args.get(), 0, change.release() ); return args.release(); } } // namespace PyObject* reset_property( PyObject* mod, PyObject* args ) { if( PyTuple_GET_SIZE( args ) != 2 ) { return cppy::type_error( "reset_property() takes exactly 2 arguments" ); } PyObject* pymember = PyTuple_GET_ITEM( args, 0 ); PyObject* pyatom = PyTuple_GET_ITEM( args, 1 ); if( !Member::TypeCheck( pymember ) ) { return cppy::type_error( pymember, "Member" ); } if( !CAtom::TypeCheck( pyatom ) ) { return cppy::type_error( pyatom, "CAtom" ); } Member* member = member_cast( pymember ); CAtom* atom = catom_cast( pyatom ); if( member->index >= atom->get_slot_count() ) { return cppy::system_error( "invalid member index" ); } cppy::ptr oldptr( atom->get_slot( member->index ) ); atom->set_slot( member->index, 0 ); bool has_static = member->has_observers( ChangeType::Property ); bool has_dynamic = atom->has_observers( member->name ); if( has_static || has_dynamic ) { if( !oldptr ) { oldptr = cppy::incref( Py_None ); } cppy::ptr newptr( member->getattr( atom ) ); if( !newptr ) { return 0; } bool cached = member->get_getattr_mode() == GetAttr::CachedProperty; if( !cached || !utils::safe_richcompare( oldptr, newptr, Py_EQ ) ) { cppy::ptr argsptr( property_args( atom, member, oldptr.get(), newptr.get() ) ); if( !argsptr ) { return 0; } if( has_static && !member->notify( atom, argsptr.get(), 0, ChangeType::Property ) ) { return 0; } if( has_dynamic && !atom->notify( member->name, argsptr.get(), 0, ChangeType::Property ) ) { return 0; } } } Py_RETURN_NONE; } } // namespace atom atom-0.12.1/atom/src/propertyhelper.h000066400000000000000000000007471506756731600175470ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #pragma once #include namespace atom { PyObject* reset_property( PyObject* mod, PyObject* args ); } // namespace atom atom-0.12.1/atom/src/setattrbehavior.cpp000066400000000000000000000253731506756731600202260ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #include #include "member.h" #include "memberchange.h" #include "utils.h" namespace atom { bool Member::check_context( SetAttr::Mode mode, PyObject* context ) { switch( mode ) { case SetAttr::Delegate: if( !Member::TypeCheck( context ) ) { cppy::type_error( context, "Member" ); return false; } break; case SetAttr::Property: if( context != Py_None && !PyCallable_Check( context ) ) { cppy::type_error( context, "callable or None" ); return false; } break; case SetAttr::CallObject_ObjectValue: case SetAttr::CallObject_ObjectNameValue: if( !PyCallable_Check( context ) ) { cppy::type_error( context, "callable" ); return false; } break; case SetAttr::ObjectMethod_Value: case SetAttr::ObjectMethod_NameValue: case SetAttr::MemberMethod_ObjectValue: if( !PyUnicode_Check( context ) ) { cppy::type_error( context, "str" ); return false; } break; default: break; } return true; } namespace { int no_op_handler( Member* member, CAtom* atom, PyObject* value ) { return 0; } PyObject* created_args( CAtom* atom, Member* member, PyObject* value ) { cppy::ptr argsptr( PyTuple_New( 1 ) ); if( !argsptr ) return 0; cppy::ptr change( MemberChange::created( atom, member, value ) ); if( !change ) return 0; PyTuple_SET_ITEM( argsptr.get(), 0, change.release() ); return argsptr.release(); } PyObject* updated_args( CAtom* atom, Member* member, PyObject* oldvalue, PyObject* newvalue ) { cppy::ptr argsptr( PyTuple_New( 1 ) ); if( !argsptr ) return 0; cppy::ptr change( MemberChange::updated( atom, member, oldvalue, newvalue ) ); if( !change ) return 0; PyTuple_SET_ITEM( argsptr.get(), 0, change.release() ); return argsptr.release(); } int slot_handler( Member* member, CAtom* atom, PyObject* value ) { if( member->index >= atom->get_slot_count() ) { cppy::attribute_error( pyobject_cast( atom ), (char *)PyUnicode_AsUTF8( member->name ) ); return -1; } if( atom->is_frozen() ) { PyErr_SetString( PyExc_AttributeError, "can't set attribute of frozen Atom" ); return -1; } cppy::ptr oldptr( atom->get_slot( member->index ) ); cppy::ptr newptr( cppy::incref( value ) ); if( oldptr == newptr ) return 0; bool valid_old = oldptr.get() != 0; if( !valid_old ) oldptr.set( cppy::incref( Py_None ) ); newptr = member->full_validate( atom, oldptr.get(), newptr.get() ); if( !newptr ) return -1; atom->set_slot( member->index, newptr.get() ); if( member->get_post_setattr_mode() ) { if( member->post_setattr( atom, oldptr.get(), newptr.get() ) < 0 ) return -1; } if( ( !valid_old || oldptr != newptr ) && atom->get_notifications_enabled() ) { cppy::ptr argsptr; if( member->has_observers(ChangeType::Update | ChangeType::Create) ) { if( valid_old && utils::safe_richcompare( oldptr, newptr, Py_EQ ) ) return 0; if( valid_old ) argsptr = updated_args( atom, member, oldptr.get(), newptr.get() ); else argsptr = created_args( atom, member, newptr.get() ); if( !argsptr ) return -1; ChangeType::Type change_type = ( valid_old ) ? ChangeType::Update: ChangeType::Create; if( !member->notify( atom, argsptr.get(), 0, change_type ) ) return -1; } if( atom->has_observers( member->name ) ) { ChangeType::Type change_type = ChangeType::Any; if( !argsptr ) { if( valid_old && utils::safe_richcompare( oldptr, newptr, Py_EQ ) ) return 0; if( valid_old ) { change_type = ChangeType::Update; argsptr = updated_args( atom, member, oldptr.get(), newptr.get() ); } else { change_type = ChangeType::Create; argsptr = created_args( atom, member, newptr.get() ); } if( !argsptr ) return -1; } if( !atom->notify( member->name, argsptr.get(), 0, change_type ) ) return -1; } } return 0; } int constant_handler( Member* member, CAtom* atom, PyObject* value ) { cppy::type_error( "cannot set the value of a constant member" ); return -1; } int read_only_handler( Member* member, CAtom* atom, PyObject* value ) { if( member->index >= atom->get_slot_count() ) { cppy::attribute_error( pyobject_cast( atom ), (char *)PyUnicode_AsUTF8( member->name ) ); return -1; } cppy::ptr slot( atom->get_slot( member->index ) ); if( slot ) { cppy::type_error( "cannot change the value of a read only member" ); return -1; } return slot_handler( member, atom, value ); } PyObject* event_args( CAtom* atom, Member* member, PyObject* value ) { cppy::ptr argsptr( PyTuple_New( 1 ) ); if( !argsptr ) return 0; cppy::ptr change( MemberChange::event( atom, member, value ) ); if( !change ) return 0; PyTuple_SET_ITEM( argsptr.get(), 0, change.release() ); return argsptr.release(); } int event_handler( Member* member, CAtom* atom, PyObject* value ) { cppy::ptr valueptr( member->full_validate( atom, Py_None, value ) ); if( !valueptr ) return -1; if( atom->get_notifications_enabled() ) { cppy::ptr argsptr; if( member->has_observers( ChangeType::Event ) ) { argsptr = event_args( atom, member, valueptr.get() ); if( !argsptr ) return -1; if( !member->notify( atom, argsptr.get(), 0, ChangeType::Event ) ) return -1; } if( atom->has_observers( member->name ) ) { if( !argsptr ) { argsptr = event_args( atom, member, valueptr.get() ); if( !argsptr ) return -1; } if( !atom->notify( member->name, argsptr.get(), 0, ChangeType::Event ) ) return -1; } } return 0; } int signal_handler( Member* member, CAtom* atom, PyObject* value ) { cppy::type_error( "cannot set the value of a signal" ); return -1; } int delegate_handler( Member* member, CAtom* atom, PyObject* value ) { Member* delegate = member_cast( member->setattr_context ); return delegate->setattr( atom, value ); } int _mangled_property_handler( Member* member, CAtom* atom, PyObject* value ) { char* suffix = (char *)PyUnicode_AsUTF8( member->name ); cppy::ptr name( PyUnicode_FromFormat( "_set_%s", suffix ) ); if( !name ) return -1; cppy::ptr ok( PyObject_CallMethodOneArg( pyobject_cast( atom ), name.get(), value ) ); if( !ok ) return -1; return 0; } int property_handler( Member* member, CAtom* atom, PyObject* value ) { if( member->setattr_context != Py_None ) { PyObject* args[] = { pyobject_cast( atom ), value }; cppy::ptr ok( PyObject_Vectorcall( member->setattr_context, args, 2, 0 ) ); if( !ok ) return -1; return 0; } return _mangled_property_handler( member, atom, value ); } int call_object_object_value_handler( Member* member, CAtom* atom, PyObject* value ) { cppy::ptr valueptr( member->full_validate( atom, Py_None, value ) ); if( !valueptr ) return -1; PyObject* args[] = { pyobject_cast( atom ), valueptr.get() }; cppy::ptr ok( PyObject_Vectorcall( member->setattr_context, args, 2, 0 ) ); if( !ok ) return -1; return 0; } int call_object_object_name_value_handler( Member* member, CAtom* atom, PyObject* value ) { cppy::ptr valueptr( member->full_validate( atom, Py_None, value ) ); if( !valueptr ) return -1; PyObject* args[] = { pyobject_cast( atom ), member->name, valueptr.get() }; cppy::ptr ok( PyObject_Vectorcall( member->setattr_context, args, 3, 0 ) ); if( !ok ) return -1; return 0; } int object_method_value_handler( Member* member, CAtom* atom, PyObject* value ) { cppy::ptr valueptr( member->full_validate( atom, Py_None, value ) ); if( !valueptr ) return -1; cppy::ptr ok( PyObject_CallMethodOneArg( pyobject_cast( atom ), member->setattr_context, valueptr.get() ) ); if ( !ok ) return -1; return 0; } int object_method_name_value_handler( Member* member, CAtom* atom, PyObject* value ) { cppy::ptr valueptr( member->full_validate( atom, Py_None, value ) ); if( !valueptr ) return -1; PyObject* args[] = { pyobject_cast( atom ), member->name, valueptr.get() }; cppy::ptr ok( PyObject_VectorcallMethod( member->setattr_context, args, 3 | PY_VECTORCALL_ARGUMENTS_OFFSET, 0 ) ); if( !ok ) return -1; return 0; } int member_method_object_value_handler( Member* member, CAtom* atom, PyObject* value ) { cppy::ptr valueptr( member->full_validate( atom, Py_None, value ) ); if( !valueptr ) return -1; PyObject* args[] = { pyobject_cast( member ), pyobject_cast( atom ), valueptr.get() }; cppy::ptr ok( PyObject_VectorcallMethod( member->setattr_context, args, 3 | PY_VECTORCALL_ARGUMENTS_OFFSET, 0 ) ); if( !ok ) return -1; return 0; } typedef int ( *handler )( Member* member, CAtom* atom, PyObject* value ); static handler handlers[] = { no_op_handler, slot_handler, constant_handler, read_only_handler, event_handler, signal_handler, delegate_handler, property_handler, call_object_object_value_handler, call_object_object_name_value_handler, object_method_value_handler, object_method_name_value_handler, member_method_object_value_handler }; } // namespace int Member::setattr( CAtom* atom, PyObject* value ) { if( get_setattr_mode() >= sizeof( handlers ) ) return no_op_handler( this, atom, value ); // LCOV_EXCL_LINE return handlers[ get_setattr_mode() ]( this, atom, value ); } } // namespace atom atom-0.12.1/atom/src/signalconnector.cpp000066400000000000000000000131211506756731600201740ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #include "signalconnector.h" #include "packagenaming.h" namespace atom { namespace { #define FREELIST_MAX 128 static int numfree = 0; static SignalConnector* freelist[ FREELIST_MAX ]; void SignalConnector_clear( SignalConnector* self ) { Py_CLEAR( self->member ); Py_CLEAR( self->atom ); } int SignalConnector_traverse( SignalConnector* self, visitproc visit, void* arg ) { Py_VISIT( self->member ); Py_VISIT( self->atom ); #if PY_VERSION_HEX >= 0x03090000 // This was not needed before Python 3.9 (Python issue 35810 and 40217) Py_VISIT(Py_TYPE(self)); #endif return 0; } void SignalConnector_dealloc( SignalConnector* self ) { PyObject_GC_UnTrack( self ); SignalConnector_clear( self ); if( numfree < FREELIST_MAX ) freelist[ numfree++ ] = self; else Py_TYPE(self)->tp_free( pyobject_cast( self ) ); } PyObject* SignalConnector_richcompare( SignalConnector* self, PyObject* other, int op ) { if( op == Py_EQ ) { if( SignalConnector::TypeCheck( other ) ) { SignalConnector* connector = reinterpret_cast( other ); if( self->member == connector->member && self->atom == connector->atom ) Py_RETURN_TRUE; Py_RETURN_FALSE; } else Py_RETURN_FALSE; } Py_RETURN_NOTIMPLEMENTED; } PyObject* SignalConnector__call__( SignalConnector* self, PyObject* args, PyObject* kwargs ) { // XXX validate the Signal args and kwargs? if( self->atom->get_notifications_enabled() ) { if( self->member->has_observers() ) { if( !self->member->notify( self->atom, args, kwargs ) ) return 0; } if( self->atom->has_observers( self->member->name ) ) { if( !self->atom->notify( self->member->name, args, kwargs ) ) return 0; } } Py_RETURN_NONE; } PyObject* SignalConnector_emit( SignalConnector* self, PyObject* args, PyObject* kwargs ) { return SignalConnector__call__( self, args, kwargs ); } PyObject* SignalConnector_connect( SignalConnector* self, PyObject* callback ) { if( !self->atom->observe( self->member->name, callback ) ) return 0; Py_RETURN_NONE; } PyObject* SignalConnector_disconnect( SignalConnector* self, PyObject* callback ) { if( !self->atom->unobserve( self->member->name, callback ) ) return 0; Py_RETURN_NONE; } static PyMethodDef SignalConnector_methods[] = { { "emit", ( PyCFunction )SignalConnector_emit, METH_VARARGS | METH_KEYWORDS, "Emit the signal with positional and keywords arguments. This is equivalent to calling the signal." }, { "connect", ( PyCFunction )SignalConnector_connect, METH_O, "Connect a callback to the signal. This is equivalent to observing the signal." }, { "disconnect", ( PyCFunction )SignalConnector_disconnect, METH_O, "Disconnect a callback from the signal. This is equivalent to unobserving the signal." }, { 0 } // sentinel }; static PyType_Slot SignalConnector_Type_slots[] = { { Py_tp_dealloc, void_cast( SignalConnector_dealloc ) }, /* tp_dealloc */ { Py_tp_traverse, void_cast( SignalConnector_traverse ) }, /* tp_traverse */ { Py_tp_clear, void_cast( SignalConnector_clear ) }, /* tp_clear */ { Py_tp_methods, void_cast( SignalConnector_methods ) }, /* tp_methods */ { Py_tp_call, void_cast( SignalConnector__call__ ) }, /* tp_call */ { Py_tp_richcompare, void_cast( SignalConnector_richcompare ) }, /* tp_richcompare */ { Py_tp_alloc, void_cast( PyType_GenericAlloc ) }, /* tp_alloc */ { Py_tp_free, void_cast( PyObject_GC_Del ) }, /* tp_free */ { 0, 0 }, }; } // namespace // Initialize static variables (otherwise the compiler eliminates them) PyTypeObject* SignalConnector::TypeObject = NULL; PyType_Spec SignalConnector::TypeObject_Spec = { PACKAGE_TYPENAME( "SignalConnector" ), /* tp_name */ sizeof( SignalConnector ), /* tp_basicsize */ 0, /* tp_itemsize */ Py_TPFLAGS_DEFAULT| Py_TPFLAGS_HAVE_GC, /* tp_flags */ SignalConnector_Type_slots /* slots */ }; bool SignalConnector::Ready() { // The reference will be handled by the module to which we will add the type TypeObject = pytype_cast( PyType_FromSpec( &TypeObject_Spec ) ); if( !TypeObject ) { return false; } return true; } PyObject* SignalConnector::New( atom::Member* member, atom::CAtom* atom ) { PyObject* pyconnector; if( numfree > 0 ) { pyconnector = pyobject_cast( freelist[ --numfree ] ); _Py_NewReference( pyconnector ); } else { pyconnector = PyType_GenericAlloc( SignalConnector::TypeObject, 0 ); if( !pyconnector ) { return 0; // LCOV_EXCL_LINE (allocation failed, impossible) } } Py_INCREF( pyobject_cast( atom ) ); Py_INCREF( pyobject_cast( member ) ); SignalConnector* connector = reinterpret_cast( pyconnector ); connector->member = member; connector->atom = atom; return pyconnector; } } // namespace atom atom-0.12.1/atom/src/signalconnector.h000066400000000000000000000015211506756731600176420ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #pragma once #include #include "catom.h" #include "member.h" namespace atom { // POD struct - all member fields are considered private struct SignalConnector { PyObject_HEAD Member* member; CAtom* atom; static PyType_Spec TypeObject_Spec; static PyTypeObject* TypeObject; static bool Ready(); static PyObject* New( Member* member, CAtom* atom ); static bool TypeCheck( PyObject* ob ) { return PyObject_TypeCheck( ob, TypeObject ) != 0; } }; } atom-0.12.1/atom/src/sortedmap.cpp000066400000000000000000000421241506756731600170070ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #include #include #include #include #include #include "packagenaming.h" #include "utils.h" #ifdef __clang__ #pragma clang diagnostic ignored "-Wdeprecated-writable-strings" #endif #ifdef __GNUC__ #pragma GCC diagnostic ignored "-Wwrite-strings" #endif namespace { class MapItem { public: MapItem() {} MapItem( PyObject* key, PyObject* value ) : m_key( cppy::incref( key ) ), m_value( cppy::incref( value ) ) { } MapItem( cppy::ptr& key, cppy::ptr& value ) : m_key( key ), m_value( value ) { } MapItem( cppy::ptr& key, PyObject* value ) : m_key( key ), m_value( cppy::incref( value ) ) { } MapItem( PyObject* key, cppy::ptr& value ) : m_key( cppy::incref( key ) ), m_value( value ) { } ~MapItem() { } PyObject* key() { return m_key.get(); } PyObject* value() { return m_value.get(); } void update( PyObject* value ) { m_value = cppy::incref( value ); } struct CmpLess { // All three operators are needed in order to keep the // MSVC debug version of std::lower_bound happy. bool operator()( MapItem& first, MapItem& second ) { if( first.m_key == second.m_key ) return false; return atom::utils::safe_richcompare( first.m_key.get(), second.m_key.get(), Py_LT ); } bool operator()( MapItem& first, PyObject* second ) { if( first.m_key == second ) return false; return atom::utils::safe_richcompare( first.m_key.get(), second, Py_LT ); } bool operator()( PyObject* first, MapItem& second ) { if( first == second.m_key ) return false; return atom::utils::safe_richcompare( first, second.m_key.get(), Py_LT ); } }; struct CmpEq { bool operator()( MapItem& first, MapItem& second ) { if( first.m_key == second.m_key ) return true; return atom::utils::safe_richcompare( first.m_key.get(), second.m_key.get(), Py_EQ ); } bool operator()( MapItem& first, PyObject* second ) { if( first.m_key == second ) return true; return atom::utils::safe_richcompare( first.m_key.get(), second, Py_EQ ); } bool operator()( PyObject* first, MapItem& second ) { if( first == second.m_key ) return true; return atom::utils::safe_richcompare( first, second.m_key.get(), Py_EQ ); } }; private: cppy::ptr m_key; cppy::ptr m_value; }; struct SortedMap { typedef std::vector Items; PyObject_HEAD Items* m_items; static PyType_Spec TypeObject_Spec; static PyTypeObject* TypeObject; static bool Ready(); PyObject* getitem( PyObject* key, PyObject* default_value = 0 ) { Items::iterator it = std::lower_bound( m_items->begin(), m_items->end(), key, MapItem::CmpLess() ); if( it == m_items->end() ) { if( default_value ) return cppy::incref( default_value ); return lookup_fail( key ); } if( MapItem::CmpEq()( *it, key ) ) return cppy::incref( it->value() ); if( default_value ) return cppy::incref( default_value ); return lookup_fail( key ); } int setitem( PyObject* key, PyObject* value ) { Items::iterator it = std::lower_bound( m_items->begin(), m_items->end(), key, MapItem::CmpLess() ); if( it == m_items->end() ) m_items->push_back( MapItem( key, value ) ); else if( MapItem::CmpEq()( *it, key ) ) it->update( value ); else m_items->insert( it, MapItem( key, value ) ); return 0; } int delitem( PyObject* key ) { Items::iterator it = std::lower_bound( m_items->begin(), m_items->end(), key, MapItem::CmpLess() ); if( it == m_items->end() ) { lookup_fail( key ); return -1; } if( MapItem::CmpEq()( *it, key ) ) { m_items->erase( it ); return 0; } lookup_fail( key ); return -1; } bool contains( PyObject* key ) { Items::iterator it = std::lower_bound( m_items->begin(), m_items->end(), key, MapItem::CmpLess() ); if( it == m_items->end() ) return false; return MapItem::CmpEq()( *it, key ); } PyObject* pop( PyObject* key, PyObject* default_value=0 ) { Items::iterator it = std::lower_bound( m_items->begin(), m_items->end(), key, MapItem::CmpLess() ); if( it == m_items->end() ) { if( default_value ) return cppy::incref( default_value ); return lookup_fail( key ); } if( MapItem::CmpEq()( *it, key ) ) { PyObject* res = cppy::incref( it->value() ); m_items->erase( it ); return res; } if( default_value ) return cppy::incref( default_value ); return lookup_fail( key ); } PyObject* keys() { PyObject* pylist = PyList_New( m_items->size() ); if( !pylist ) return 0; Py_ssize_t listidx = 0; Items::iterator it; Items::iterator end_it = m_items->end(); for( it = m_items->begin(); it != end_it; ++it ) { PyList_SET_ITEM( pylist, listidx, cppy::incref( it->key() ) ); ++listidx; } return pylist; } PyObject* values() { PyObject* pylist = PyList_New( m_items->size() ); if( !pylist ) return 0; Py_ssize_t listidx = 0; Items::iterator it; Items::iterator end_it = m_items->end(); for( it = m_items->begin(); it != end_it; ++it ) { PyList_SET_ITEM( pylist, listidx, cppy::incref( it->value() ) ); ++listidx; } return pylist; } PyObject* items() { PyObject* pylist = PyList_New( m_items->size() ); if( !pylist ) return 0; Py_ssize_t listidx = 0; Items::iterator it; Items::iterator end_it = m_items->end(); for( it = m_items->begin(); it != end_it; ++it ) { PyObject* pytuple = PyTuple_New( 2 ); if( !pytuple ) return 0; PyTuple_SET_ITEM( pytuple, 0, cppy::incref( it->key() ) ); PyTuple_SET_ITEM( pytuple, 1, cppy::incref( it->value() ) ); PyList_SET_ITEM( pylist, listidx, pytuple ); ++listidx; } return pylist; } static PyObject* lookup_fail( PyObject* key ) { cppy::ptr pystr( PyObject_Str( key ) ); if( !pystr ) return 0; cppy::ptr pytuple( PyTuple_Pack( 1, key ) ); if (!pytuple) return 0; PyErr_SetObject(PyExc_KeyError, pytuple.get()); return 0; } }; PyObject* SortedMap_new( PyTypeObject* type, PyObject* args, PyObject* kwargs ) { PyObject* map = 0; static char* kwlist[] = { "map", 0 }; if( !PyArg_ParseTupleAndKeywords( args, kwargs, "|O:__new__", kwlist, &map ) ) return 0; PyObject* self = PyType_GenericNew( type, 0, 0 ); if( !self ) { return 0; // LCOV_EXCL_LINE (allocation failed, very unlikely) } SortedMap* cself = reinterpret_cast( self ); cself->m_items = new SortedMap::Items(); cppy::ptr seq; if( map ) { if( PyDict_Check( map ) ) { seq = PyObject_GetIter( PyDict_Items( map ) ); if( !seq ) { return 0; // LCOV_EXCL_LINE (dict items failed, very unlikely) } } else { seq = PyObject_GetIter( map ); if( !seq ) return 0; } } if( seq ) { cppy::ptr item; while( (item = PyIter_Next( seq.get() )) ) { if( PySequence_Length( item.get() ) != 2) return cppy::type_error( item.get(), "pairs of objects" ); cself->setitem( PySequence_GetItem( item.get(), 0 ), PySequence_GetItem( item.get(), 1 ) ); } } return self; } // Clearing the vector may cause arbitrary side effects on item // decref, including calls into methods which mutate the vector. // To avoid segfaults, first make the vector empty, then let the // destructors run for the old items. int SortedMap_clear( SortedMap* self ) { SortedMap::Items empty; self->m_items->swap( empty ); return 0; } int SortedMap_traverse( SortedMap* self, visitproc visit, void* arg ) { SortedMap::Items::iterator it; SortedMap::Items::iterator end_it = self->m_items->end(); for( it = self->m_items->begin(); it != end_it; ++it ) { Py_VISIT( it->key() ); Py_VISIT( it->value() ); } #if PY_VERSION_HEX >= 0x03090000 // This was not needed before Python 3.9 (Python issue 35810 and 40217) Py_VISIT(Py_TYPE(self)); #endif return 0; } void SortedMap_dealloc( SortedMap* self ) { PyObject_GC_UnTrack( self ); SortedMap_clear( self ); delete self->m_items; self->m_items = 0; Py_TYPE(self)->tp_free( reinterpret_cast( self ) ); } Py_ssize_t SortedMap_length( SortedMap* self ) { return static_cast( self->m_items->size() ); } PyObject* SortedMap_subscript( SortedMap* self, PyObject* key ) { return self->getitem( key ); } int SortedMap_ass_subscript( SortedMap* self, PyObject* key, PyObject* value ) { if( !value ) return self->delitem( key ); return self->setitem( key, value ); } int SortedMap_contains( SortedMap* self, PyObject* key ) { return self->contains( key ); } PyObject* SortedMap_get( SortedMap* self, PyObject*const *args, Py_ssize_t nargs ) { if( nargs == 1 ) return self->getitem( args[0], Py_None ); if( nargs == 2 ) return self->getitem( args[0], args[1] ); std::ostringstream ostr; if( nargs > 2 ) ostr << "get() expected at most 2 arguments, got " << nargs; else ostr << "get() expected at least 1 argument, got " << nargs; return cppy::type_error( ostr.str().c_str() ); } PyObject* SortedMap_pop( SortedMap* self, PyObject*const *args, Py_ssize_t nargs ) { if( nargs == 1 ) return self->pop( args[0] ); if( nargs == 2 ) return self->getitem( args[0], args[1] ); std::ostringstream ostr; if( nargs > 2 ) ostr << "pop() expected at most 2 arguments, got " << nargs; else ostr << "pop() expected at least 1 argument, got " << nargs; return cppy::type_error( ostr.str().c_str() ); } PyObject* SortedMap_clearmethod( SortedMap* self ) { // Clearing the vector may cause arbitrary side effects on item // decref, including calls into methods which mutate the vector. // To avoid segfaults, first make the vector empty, then let the // destructors run for the old items. SortedMap::Items empty; self->m_items->swap( empty ); Py_RETURN_NONE; } PyObject* SortedMap_keys( SortedMap* self ) { return self->keys(); } PyObject* SortedMap_values( SortedMap* self ) { return self->values(); } PyObject* SortedMap_items( SortedMap* self ) { return self->items(); } PyObject* SortedMap_iter( SortedMap* self ) { cppy::ptr keys( self->keys() ); if( !keys ) return 0; return PyObject_GetIter( keys.get() ); } PyObject* SortedMap_copy( SortedMap* self ) { PyTypeObject* type = pytype_cast( Py_TYPE(self) ); PyObject* copy = type->tp_alloc( type, 0 ); if( !copy ) return 0; SortedMap* ccopy = reinterpret_cast( copy ); ccopy->m_items = new SortedMap::Items(); *ccopy->m_items = *self->m_items; return copy; } PyObject* SortedMap_repr( SortedMap* self ) { std::ostringstream ostr; ostr << "sortedmap(["; SortedMap::Items::iterator it; SortedMap::Items::iterator end_it = self->m_items->end(); for( it = self->m_items->begin(); it != end_it; ++it ) { cppy::ptr keystr( PyObject_Repr( it->key() ) ); if( !keystr ) return 0; cppy::ptr valstr( PyObject_Repr( it->value() ) ); if( !valstr ) return 0; ostr << "(" << PyUnicode_AsUTF8( keystr.get() ) << ", "; ostr << PyUnicode_AsUTF8( valstr.get() ) << "), "; } if( self->m_items->size() > 0 ) ostr.seekp( -2, std::ios_base::cur ); ostr << "])"; return PyUnicode_FromString( ostr.str().c_str() ); } PyObject* SortedMap_contains_bool( SortedMap* self, PyObject* key ) { if( self->contains( key ) ) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } PyObject* SortedMap_sizeof( SortedMap* self, PyObject* args ) { Py_ssize_t size = Py_TYPE(self)->tp_basicsize; size += sizeof( SortedMap::Items ); size += sizeof( MapItem ) * self->m_items->capacity(); return PyLong_FromSsize_t( size ); } static PyMethodDef SortedMap_methods[] = { { "get", ( PyCFunction )SortedMap_get, METH_FASTCALL, "" }, { "pop", ( PyCFunction )SortedMap_pop, METH_FASTCALL, "" }, { "clear", ( PyCFunction)SortedMap_clearmethod, METH_NOARGS, "" }, { "keys", ( PyCFunction )SortedMap_keys, METH_NOARGS, "" }, { "values", ( PyCFunction )SortedMap_values, METH_NOARGS, "" }, { "items", ( PyCFunction )SortedMap_items, METH_NOARGS, "" }, { "copy", ( PyCFunction )SortedMap_copy, METH_NOARGS, "" }, { "__contains__", ( PyCFunction )SortedMap_contains_bool, METH_O | METH_COEXIST, "" }, { "__getitem__", ( PyCFunction )SortedMap_subscript, METH_O | METH_COEXIST, "" }, { "__sizeof__", ( PyCFunction )SortedMap_sizeof, METH_NOARGS, "__sizeof__() -> size of object in memory, in bytes" }, { 0 } // sentinel }; static PyType_Slot SortedMap_Type_slots[] = { { Py_tp_dealloc, void_cast( SortedMap_dealloc ) }, /* tp_dealloc */ { Py_tp_traverse, void_cast( SortedMap_traverse ) }, /* tp_traverse */ { Py_tp_clear, void_cast( SortedMap_clear ) }, /* tp_clear */ { Py_tp_methods, void_cast( SortedMap_methods ) }, /* tp_methods */ { Py_tp_repr, void_cast( SortedMap_repr ) }, /* tp_repr */ { Py_tp_new, void_cast( SortedMap_new ) }, /* tp_new */ { Py_tp_iter, void_cast( SortedMap_iter ) }, /* tp_iter */ { Py_tp_alloc, void_cast( PyType_GenericAlloc ) }, /* tp_alloc */ { Py_mp_length, void_cast( SortedMap_length ) }, /* mp_length */ { Py_mp_subscript, void_cast( SortedMap_subscript ) }, /* mp_subscript */ { Py_mp_ass_subscript, void_cast( SortedMap_ass_subscript ) }, /* mp_ass_subscript */ { Py_sq_contains, void_cast( SortedMap_contains ) }, /* sq_contains */ { 0, 0 }, }; // Initialize static variables (otherwise the compiler eliminates them) PyTypeObject* SortedMap::TypeObject = NULL; PyType_Spec SortedMap::TypeObject_Spec = { PACKAGE_TYPENAME( "sortedmap.sortedmap" ), /* tp_name */ sizeof( SortedMap ), /* tp_basicsize */ 0, /* tp_itemsize */ Py_TPFLAGS_DEFAULT| Py_TPFLAGS_HAVE_GC, /* tp_flags */ SortedMap_Type_slots /* slots */ }; bool SortedMap::Ready() { // The reference will be handled by the module to which we will add the type TypeObject = pytype_cast( PyType_FromSpec( &TypeObject_Spec ) ); if( !TypeObject ) { return false; // LCOV_EXCL_LINE (failed to create type, very unlikely) } return true; } // Module creation static PyMethodDef sortedmap_methods[] = { { 0 } // Sentinel }; int sortedmap_modexec( PyObject *mod ) { if( !SortedMap::Ready() ) { return -1; // LCOV_EXCL_LINE (failed to init type, very unlikely) } cppy::ptr sortedmap( pyobject_cast( SortedMap::TypeObject ) ); if( PyModule_AddObject( mod, "sortedmap", sortedmap.get() ) < 0 ) { return false; // LCOV_EXCL_LINE (failed to add type to module, very unlikely) } sortedmap.release(); return 0; } PyModuleDef_Slot sortedmap_slots[] = { {Py_mod_exec, reinterpret_cast( sortedmap_modexec ) }, {0, NULL} }; static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "sortedmap", "sortedmap extension module", 0, sortedmap_methods, sortedmap_slots, NULL, NULL, NULL }; } // namespace PyMODINIT_FUNC PyInit_sortedmap( void ) { return PyModuleDef_Init( &moduledef ); } atom-0.12.1/atom/src/utils.h000066400000000000000000000075451506756731600156260ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #pragma once #include #include namespace atom { namespace utils { inline PyObject* py_bool( bool val ) { return cppy::incref( val ? Py_True : Py_False ); } inline bool str_check( PyObject* obj ) { return ( PyUnicode_CheckExact( obj ) || PyObject_TypeCheck( obj, &PyUnicode_Type ) ); } inline bool is_type_or_tuple_of_types( PyObject* pyo ) { if( PyType_Check( pyo ) ) { return true; } if( !PyTuple_Check( pyo ) ) { return false; } Py_ssize_t count = PyTuple_GET_SIZE( pyo ); for( Py_ssize_t i = 0; i < count; ++i ) { if( !is_type_or_tuple_of_types( PyTuple_GET_ITEM( pyo, i ) ) ) { return false; } } return true; } /** * A fallback 3way comparison function for when PyObject_RichCompareBool * fails to compare "unorderable types" on Python 3. * * This is based on Python 2's `default_3way_compare`. * * This function will not change the Python exception state. */ inline int fallback_3way_compare( PyObject* first, PyObject* second ) { // Compare pointer values if the types are the same. if( Py_TYPE( first ) == Py_TYPE( second ) ) { Py_uintptr_t fp = reinterpret_cast( first ); Py_uintptr_t sp = reinterpret_cast( second ); return (fp < sp) ? -1 : (fp > sp) ? 1 : 0; } // None is smaller than anything. if( first == Py_None ) return -1; if( second == Py_None ) return 1; // Compare based on type names, numbers are smaller. const char* fn = PyNumber_Check( first ) ? "" : Py_TYPE( first )->tp_name; const char* sn = PyNumber_Check( second ) ? "" : Py_TYPE( second )->tp_name; int c = strcmp( fn, sn ); if( c < 0 ) return -1; if( c > 0 ) return 1; // Finally, fall back to comparing type pointers. Py_uintptr_t ftp = reinterpret_cast( Py_TYPE( first ) ); Py_uintptr_t stp = reinterpret_cast( Py_TYPE( second ) ); return ftp < stp ? -1 : 1; } /** * A safe richcomparison that will never fail and fallback to 3way compare * if the conventional rich compare fails. */ inline bool safe_richcompare( PyObject* first, PyObject* second, int opid ) { // Start with Python's rich compare. int r = PyObject_RichCompareBool( first, second, opid ); // Handle a successful comparison. if( r == 1 ) return true; if( r == 0 ) return false; // Clear the error if one happened because we attempted an invalid // comparison. if( PyErr_Occurred() ) PyErr_Clear(); // Fallback to the Python 2 default 3 way compare. int c = fallback_3way_compare( first, second ); // Convert the 3way comparison result based on the `opid`. switch (opid) { case Py_EQ: return c == 0; case Py_NE: return c != 0; case Py_LE: return c <= 0; case Py_GE: return c >= 0; case Py_LT: return c < 0; case Py_GT: return c > 0; } // Return `false` if the `opid` is not handled. return false; } inline bool safe_richcompare( const cppy::ptr &first, PyObject* second, int opid ) { return safe_richcompare( first.get(), second, opid ); } inline bool safe_richcompare( PyObject* first, const cppy::ptr &second, int opid ) { return safe_richcompare( first, second.get(), opid ); } // Passing by reference avoids an extra incref/decref pair inline bool safe_richcompare( const cppy::ptr &first, const cppy::ptr &second, int opid ) { return safe_richcompare( first.get(), second.get(), opid ); } } // namespace utils } // namespace atom atom-0.12.1/atom/src/validatebehavior.cpp000066400000000000000000000717711506756731600203340ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2013-2025, Nucleic Development Team. | | Distributed under the terms of the Modified BSD License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ #include #include #include #include #include "member.h" #include "atomlist.h" #include "atomdict.h" #include "atomset.h" namespace atom { namespace { bool validate_type_tuple_types( PyObject* type_tuple_types ) { if( PyTuple_Check( type_tuple_types ) ) { Py_ssize_t len = PySequence_Size( type_tuple_types ); for( Py_ssize_t i = 0; i < len; i++ ) { if( !PyType_Check( PyTuple_GET_ITEM( type_tuple_types, i ) ) ) { PyErr_Format( PyExc_TypeError, "Expected type or tuple of types. Got a tuple containing an instance of `%s` instead.", Py_TYPE( PyTuple_GET_ITEM( type_tuple_types, i ) )->tp_name ); return false; } } return true; } if( !PyType_Check( type_tuple_types ) ) { cppy::type_error( type_tuple_types, "type or tuple of types" ); return false; } return true; } } // namespace bool Member::check_context( Validate::Mode mode, PyObject* context ) { switch( mode ) { case Validate::Tuple: case Validate::List: case Validate::ContainerList: case Validate::Set: if( context != Py_None && !Member::TypeCheck( context ) ) { cppy::type_error( context, "Member or None" ); return false; } break; case Validate::FixedTuple: { if( !PyTuple_Check( context ) ) { cppy::type_error( context, "tuple of types or Members" ); return false; } Py_ssize_t len = PyTuple_GET_SIZE( context ); for( Py_ssize_t i = 0; i < len; i++ ) { PyObject* t = PyTuple_GET_ITEM( context, i ); if( !Member::TypeCheck( t ) ) { cppy::type_error( context, "tuple of types or Members" ); return false; } } break; } case Validate::Dict: { if( !PyTuple_Check( context ) ) { cppy::type_error( context, "2-tuple of Member or None" ); return false; } if( PyTuple_GET_SIZE( context ) != 2 ) { cppy::type_error( context, "2-tuple of Member or None" ); return false; } PyObject* k = PyTuple_GET_ITEM( context, 0 ); PyObject* v = PyTuple_GET_ITEM( context, 1 ); if( k != Py_None && !Member::TypeCheck( k ) ) { cppy::type_error( context, "2-tuple of Member or None" ); return false; } if( v != Py_None && !Member::TypeCheck( v ) ) { cppy::type_error( context, "2-tuple of Member or None" ); return false; } break; } case Validate::DefaultDict: { if( !PyTuple_Check( context ) ) { cppy::type_error( context, "3-tuple: Member|None, Member|None, Callable[[], Any]" ); return false; } if( PyTuple_GET_SIZE( context ) != 3 ) { cppy::type_error( context, "3-tuple: Member|None, Member|None, Callable[[], Any]" ); return false; } PyObject* k = PyTuple_GET_ITEM( context, 0 ); PyObject* v = PyTuple_GET_ITEM( context, 1 ); PyObject* f = PyTuple_GET_ITEM( context, 2 ); if( k != Py_None && !Member::TypeCheck( k ) ) { cppy::type_error( context, "3-tuple: Member|None, Member|None, Callable[[], Any]" ); return false; } if( v != Py_None && !Member::TypeCheck( v ) ) { cppy::type_error( context, "3-tuple: Member|None, Member|None, Callable[[], Any]" ); return false; } if( PyCallable_Check( f ) == 0) { cppy::type_error( context, "3-tuple: Member|None, Member|None, Callable[[], Any]" ); return false; } break; } case Validate::OptionalInstance: case Validate::Instance: case Validate::Subclass: { return validate_type_tuple_types( context ); } case Validate::OptionalTyped: case Validate::Typed: if( !PyType_Check( context ) ) { cppy::type_error( context, "type" ); return false; } break; case Validate::Enum: if( !PySequence_Check( context ) ) { cppy::type_error( context, "sequence" ); return false; } break; case Validate::FloatRange: case Validate::FloatRangePromote: { if( !PyTuple_Check( context ) ) { cppy::type_error( context, "2-tuple of float or None" ); return false; } if( PyTuple_GET_SIZE( context ) != 2 ) { cppy::type_error( context, "2-tuple of float or None" ); return false; } PyObject* start = PyTuple_GET_ITEM( context, 0 ); PyObject* end = PyTuple_GET_ITEM( context, 1 ); if( start != Py_None && !PyFloat_Check( start ) ) { cppy::type_error( context, "2-tuple of float or None" ); return false; } if( end != Py_None && !PyFloat_Check( end ) ) { cppy::type_error( context, "2-tuple of float or None" ); return false; } break; } case Validate::Range: { if( !PyTuple_Check( context ) ) { cppy::type_error( context, "2-tuple of int or None" ); return false; } if( PyTuple_GET_SIZE( context ) != 2 ) { cppy::type_error( context, "2-tuple of int or None" ); return false; } PyObject* start = PyTuple_GET_ITEM( context, 0 ); PyObject* end = PyTuple_GET_ITEM( context, 1 ); if( start != Py_None && !PyLong_Check( start ) ) { cppy::type_error( context, "2-tuple of int or None" ); return false; } if( end != Py_None && !PyLong_Check( end ) ) { cppy::type_error( context, "2-tuple of int or None" ); return false; } break; } case Validate::Coerced: { if( !PyTuple_Check( context ) ) { cppy::type_error( context, "2-tuple of (type, callable)" ); return false; } if( PyTuple_GET_SIZE( context ) != 2 ) { PyErr_Format( PyExc_TypeError, "Expected 2-tuple of (type, callable). Got a tuple of length %d instead.", PyTuple_GET_SIZE( context ) ); return false; } PyObject* type = PyTuple_GET_ITEM( context, 0 ); PyObject* coercer = PyTuple_GET_ITEM( context, 1 ); if( !validate_type_tuple_types( type ) ) { return false; } if( !PyCallable_Check( coercer ) ) { cppy::type_error( context, "2-tuple of (type, callable)" ); return false; } break; } case Validate::Delegate: if( !Member::TypeCheck( context ) ) { cppy::type_error( context, "Member" ); return false; } break; case Validate::ObjectMethod_OldNew: case Validate::ObjectMethod_NameOldNew: case Validate::MemberMethod_ObjectOldNew: if( !PyUnicode_Check( context ) ) { cppy::type_error( context, "str" ); return false; } break; default: break; } return true; } namespace { std::string name_from_type_tuple_types( PyObject* type_tuple_types ) { // This should never be used if the input can be something else than a type // or a tuple of types. std::ostringstream ostr; if( PyType_Check( type_tuple_types ) ) { PyTypeObject* type = pytype_cast( type_tuple_types ); ostr << type->tp_name; } else { ostr << "("; Py_ssize_t len = PySequence_Size( type_tuple_types ); for( Py_ssize_t i = 0; i < len; i++ ) { PyTypeObject* type = pytype_cast( PyTuple_GET_ITEM( type_tuple_types, i ) ); ostr << type->tp_name; if( i != len-1 ) ostr << ", "; } ostr << ")"; } return ostr.str(); } PyObject* validate_type_fail( Member* member, CAtom* atom, PyObject* newvalue, const char* type ) { PyErr_Format( PyExc_TypeError, "The '%s' member on the '%s' object must be of type '%s'. " "Got object of type '%s' instead.", PyUnicode_AsUTF8( member->name ), Py_TYPE( pyobject_cast( atom ) )->tp_name, type, Py_TYPE( newvalue )->tp_name ); return 0; } PyObject* no_op_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { return cppy::incref( newvalue ); } PyObject* bool_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { if( newvalue == Py_True || newvalue == Py_False ) { return cppy::incref( newvalue ); } return validate_type_fail( member, atom, newvalue, "bool" ); } PyObject* long_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { if( PyLong_Check( newvalue ) ) { return cppy::incref( newvalue ); } return validate_type_fail( member, atom, newvalue, "int" ); } PyObject* long_promote_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { if( PyLong_Check( newvalue ) ) { return cppy::incref( newvalue ); } if( PyFloat_Check( newvalue ) ) { double value = PyFloat_AS_DOUBLE( newvalue ); return PyLong_FromDouble( value ); } return validate_type_fail( member, atom, newvalue, "int" ); } PyObject* float_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { if( PyFloat_Check( newvalue ) ) { return cppy::incref( newvalue ); } return validate_type_fail( member, atom, newvalue, "float" ); } PyObject* float_promote_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { if( PyFloat_Check( newvalue ) ) { return cppy::incref( newvalue ); } if( PyLong_Check( newvalue ) ) { double value = PyLong_AsDouble( newvalue ); if( value == -1.0 && PyErr_Occurred() ) { return 0; } return PyFloat_FromDouble( value ); } return validate_type_fail( member, atom, newvalue, "float" ); } PyObject* bytes_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { if( PyBytes_Check( newvalue ) ) { return cppy::incref( newvalue ); } return validate_type_fail( member, atom, newvalue, "bytes" ); } PyObject* bytes_promote_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { if( PyBytes_Check( newvalue ) ) { return cppy::incref( newvalue ); } if( PyUnicode_Check( newvalue ) ) { return PyUnicode_AsUTF8String( newvalue ); } return validate_type_fail( member, atom, newvalue, "bytes" ); } PyObject* str_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { if( PyUnicode_Check( newvalue ) ) { return cppy::incref( newvalue ); } return validate_type_fail( member, atom, newvalue, "str" ); } PyObject* str_promote_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { if( PyUnicode_Check( newvalue ) ) { return cppy::incref( newvalue ); } if( PyBytes_Check( newvalue ) ) { return PyUnicode_FromString( PyBytes_AS_STRING( newvalue ) ); } return validate_type_fail( member, atom, newvalue, "str" ); } PyObject* tuple_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { if( !PyTuple_Check( newvalue ) ) { return validate_type_fail( member, atom, newvalue, "tuple" ); } cppy::ptr tupleptr( cppy::incref( newvalue ) ); if( member->validate_context != Py_None ) { Py_ssize_t size = PyTuple_GET_SIZE( newvalue ); cppy::ptr tuplecopy = PyTuple_New( size ); if( !tuplecopy ) { return 0; } Member* item_member = member_cast( member->validate_context ); for( Py_ssize_t i = 0; i < size; ++i ) { cppy::ptr item( cppy::incref( PyTuple_GET_ITEM( tupleptr.get(), i ) ) ); cppy::ptr valid_item( item_member->full_validate( atom, Py_None, item.get() ) ); if( !valid_item ) { return 0; } PyTuple_SET_ITEM( tuplecopy.get(), i, valid_item.release() ); } tupleptr = tuplecopy; } return tupleptr.release(); } PyObject* fixed_tuple_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { if( !PyTuple_Check( newvalue ) ) { return validate_type_fail( member, atom, newvalue, "tuple" ); } cppy::ptr tupleptr( cppy::incref( newvalue ) ); // Create a copy in which to store the validated values Py_ssize_t size = PyTuple_GET_SIZE( newvalue ); cppy::ptr tuplecopy = PyTuple_New( size ); if( !tuplecopy ) { return 0; } // Check the size match the expected size Py_ssize_t expected_size = PyTuple_GET_SIZE( member->validate_context ); if( size != expected_size ) { PyErr_Format( PyExc_TypeError, "The '%s' member on the '%s' object must be of a '%d-tuple'. " "Got tuple of length %d instead", PyUnicode_AsUTF8( member->name ), Py_TYPE( pyobject_cast( atom ) )->tp_name, expected_size, size ); return 0; } // Validate each single item for( Py_ssize_t i = 0; i < size; ++i ) { Member* item_member = member_cast( PyTuple_GET_ITEM( member->validate_context, i ) ); cppy::ptr item( cppy::incref( PyTuple_GET_ITEM( tupleptr.get(), i ) ) ); cppy::ptr valid_item( item_member->full_validate( atom, Py_None, item.get() ) ); if( !valid_item ) { return 0; } PyTuple_SET_ITEM( tuplecopy.get(), i, valid_item.release() ); } tupleptr = tuplecopy; return tupleptr.release(); } template PyObject* common_list_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { if( !PyList_Check( newvalue ) ) return validate_type_fail( member, atom, newvalue, "list" ); Member* validator = 0; if( member->validate_context != Py_None ) { validator = member_cast( member->validate_context ); } Py_ssize_t size = PyList_GET_SIZE( newvalue ); cppy::ptr listptr( ListFactory()( member, atom, validator, size ) ); if( !listptr ) { return 0; } if( !validator ) { for( Py_ssize_t i = 0; i < size; ++i ) PyList_SET_ITEM( listptr.get(), i, cppy::incref( PyList_GET_ITEM( newvalue, i ) ) ); } else { for( Py_ssize_t i = 0; i < size; ++i ) { PyObject* item = PyList_GET_ITEM( newvalue, i ); cppy::ptr valid_item( validator->full_validate( atom, Py_None, item ) ); if( !valid_item ) { return 0; } PyList_SET_ITEM( listptr.get(), i, valid_item.release() ); } } return listptr.release(); } class AtomListFactory { public: PyObject* operator()( Member* member, CAtom* atom, Member* validator, Py_ssize_t size ) { return atom::AtomList::New( size, atom, validator ); } }; class AtomCListFactory { public: PyObject* operator()( Member* member, CAtom* atom, Member* validator, Py_ssize_t size ) { return atom::AtomCList::New( size, atom, validator, member ); } }; PyObject* list_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { return common_list_handler( member, atom, oldvalue, newvalue ); } PyObject* container_list_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { return common_list_handler( member, atom, oldvalue, newvalue ); } PyObject* set_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { if( !PyAnySet_Check( newvalue ) ) return validate_type_fail( member, atom, newvalue, "set" ); // Get the validator if it exists Member* validator = 0; if( member->validate_context != Py_None ) { validator = member_cast( member->validate_context ); } // Create a new atom set and update it. cppy::ptr newset( atom::AtomSet::New( atom, validator ) ); if( !newset ) { return 0; } if( atom::AtomSet::Update( atomset_cast( newset.get() ), newvalue) < 0 ) { return 0; } return newset.release(); } PyObject* dict_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { if( !PyDict_Check( newvalue ) ) return validate_type_fail( member, atom, newvalue, "dict" ); // Get the key validator if it exists PyObject* k = PyTuple_GET_ITEM( member->validate_context, 0 ); Member* key_validator = 0; if( k != Py_None ) { key_validator = member_cast( k ); } // Get the value validator if it exists PyObject* v = PyTuple_GET_ITEM( member->validate_context, 1 ); Member* value_validator = 0; if( v != Py_None ) { value_validator = member_cast( v ); } // Create a new atom dict and update it. cppy::ptr newdict( atom::AtomDict::New( atom, key_validator, value_validator ) ); if( !newdict ) { std::cout << "Failed to create atomdict" << std::flush; return 0; } if( atom::AtomDict::Update( atomdict_cast( newdict.get() ), newvalue ) < 0 ) { return 0; } return newdict.release(); } PyObject* default_dict_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { if( !PyDict_Check( newvalue ) ) return validate_type_fail( member, atom, newvalue, "dict" ); // Get the key validator if it exists PyObject* k = PyTuple_GET_ITEM( member->validate_context, 0 ); Member* key_validator = 0; if( k != Py_None ) { key_validator = member_cast( k ); } // Get the value validator if it exists PyObject* v = PyTuple_GET_ITEM( member->validate_context, 1 ); Member* value_validator = 0; if( v != Py_None ) { value_validator = member_cast( v ); } // Get the value factory if it exists PyObject* factory = PyTuple_GET_ITEM( member->validate_context, 2 ); // Create a new atom dict and update it. cppy::ptr newdict( atom::DefaultAtomDict::New( atom, key_validator, value_validator, factory) ); if( !newdict ) { std::cout << "Failed to create atomdefaultdict" << std::flush; return 0; } if( atom::AtomDict::Update( atomdict_cast( newdict.get() ), newvalue ) < 0 ) { return 0; } return newdict.release(); } PyObject* non_optional_instance_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { int res = PyObject_IsInstance( newvalue, member->validate_context ); if( res < 0 ) return 0; if( res == 1 ) return cppy::incref( newvalue ); return validate_type_fail( member, atom, newvalue, name_from_type_tuple_types( member->validate_context ).c_str() ); } PyObject* instance_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { if( newvalue == Py_None ) return cppy::incref( newvalue ); return non_optional_instance_handler( member, atom, oldvalue, newvalue ); } PyObject* non_optional_typed_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { PyTypeObject* type = pytype_cast( member->validate_context ); if( PyObject_TypeCheck( newvalue, type ) ) return cppy::incref( newvalue ); return validate_type_fail( member, atom, newvalue, type->tp_name ); } PyObject* typed_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { if( newvalue == Py_None ) return cppy::incref( newvalue ); return non_optional_typed_handler( member, atom, oldvalue, newvalue ); } PyObject* subclass_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { if( !PyType_Check( newvalue ) ) { PyErr_Format( PyExc_TypeError, "The '%s' member on the '%s' object must be a subclass of '%s'. " "Got instance of '%s' instead.", PyUnicode_AsUTF8( member->name ), Py_TYPE( pyobject_cast( atom ) )->tp_name, name_from_type_tuple_types( member->validate_context ).c_str(), Py_TYPE( newvalue )->tp_name ); return 0; } int res = PyObject_IsSubclass( newvalue, member->validate_context ); if( res < 0 ) return 0; if( res == 1 ) return cppy::incref( newvalue ); if( PyType_Check( newvalue ) ) { PyTypeObject* type = pytype_cast( newvalue ); PyErr_Format( PyExc_TypeError, "The '%s' member on the '%s' object must be a subclass of '%s'. " "Got class '%s' instead.", PyUnicode_AsUTF8( member->name ), Py_TYPE( pyobject_cast( atom ) )->tp_name, name_from_type_tuple_types( member->validate_context ).c_str(), type->tp_name ); } return 0; } PyObject* enum_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { int res = PySequence_Contains( member->validate_context, newvalue ); if( res < 0 ) return 0; if( res == 1 ) return cppy::incref( newvalue ); return PyErr_Format( PyExc_ValueError, "invalid enum value for '%s' of '%s'", PyUnicode_AsUTF8( member->name ), Py_TYPE( pyobject_cast( atom ) )->tp_name ); } PyObject* callable_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { if( newvalue == Py_None ) return cppy::incref( newvalue ); if( PyCallable_Check( newvalue ) ) return cppy::incref( newvalue ); return validate_type_fail( member, atom, newvalue, "callable" ); } PyObject* float_range_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { if( !PyFloat_Check( newvalue ) ) return validate_type_fail( member, atom, newvalue, "float" ); PyObject* low = PyTuple_GET_ITEM( member->validate_context, 0 ); PyObject* high = PyTuple_GET_ITEM( member->validate_context, 1 ); double value = PyFloat_AS_DOUBLE( newvalue ); if( low != Py_None ) { if( PyFloat_AS_DOUBLE( low ) > value ) return PyErr_Format( PyExc_ValueError, "range value for '%s' of '%s' too small", PyUnicode_AsUTF8( member->name ), Py_TYPE( pyobject_cast( atom ) )->tp_name ); } if( high != Py_None ) { if( PyFloat_AS_DOUBLE( high ) < value ) return PyErr_Format( PyExc_ValueError, "range value for '%s' of '%s' too large", PyUnicode_AsUTF8( member->name ), Py_TYPE( pyobject_cast( atom ) )->tp_name ); } return cppy::incref( newvalue ); } PyObject* float_range_promote_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { if( PyLong_Check( newvalue ) ) { double value = PyLong_AsDouble( newvalue ); if( value == -1.0 && PyErr_Occurred() ) return 0; cppy::ptr convertedvalue( PyFloat_FromDouble( value ) ); return float_range_handler( member, atom, oldvalue, convertedvalue.get() ); } return float_range_handler( member, atom, oldvalue, newvalue ); } PyObject* range_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { if( !PyLong_Check( newvalue ) ) return validate_type_fail( member, atom, newvalue, "int" ); PyObject* low = PyTuple_GET_ITEM( member->validate_context, 0 ); PyObject* high = PyTuple_GET_ITEM( member->validate_context, 1 ); if( low != Py_None ) { if( PyObject_RichCompareBool( low , newvalue, Py_GT ) ) return PyErr_Format( PyExc_ValueError, "range value for '%s' of '%s' too small", PyUnicode_AsUTF8( member->name ), Py_TYPE( pyobject_cast( atom ) )->tp_name ); } if( high != Py_None ) { if( PyObject_RichCompareBool( high , newvalue, Py_LT ) ) return PyErr_Format( PyExc_ValueError, "range value for '%s' of '%s' too large", PyUnicode_AsUTF8( member->name ), Py_TYPE( pyobject_cast( atom ) )->tp_name ); } return cppy::incref( newvalue ); } PyObject* coerced_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { PyObject* type = PyTuple_GET_ITEM( member->validate_context, 0 ); int res = PyObject_IsInstance( newvalue, type ); if( res == 1 ) return cppy::incref( newvalue ); if( res == -1 ) return 0; PyObject* coercer = PyTuple_GET_ITEM( member->validate_context, 1 ); cppy::ptr coerced( PyObject_CallOneArg( coercer, newvalue ) ); if( !coerced ) return 0; res = PyObject_IsInstance( coerced.get(), type ); if( res == 1 ) return coerced.release(); if( res == -1 ) return 0; return cppy::type_error( "could not coerce value to an appropriate type" ); } PyObject* delegate_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { Member* delegate = member_cast( member->validate_context ); return delegate->validate( atom, oldvalue, newvalue ); } PyObject* object_method_old_new_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { PyObject* args[] = { pyobject_cast( atom ), oldvalue, newvalue }; return PyObject_VectorcallMethod( member->validate_context, args, 3 | PY_VECTORCALL_ARGUMENTS_OFFSET, 0 ); } PyObject* object_method_name_old_new_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { PyObject* args[] = { pyobject_cast( atom ), member->name, oldvalue, newvalue }; return PyObject_VectorcallMethod( member->validate_context, args, 4 | PY_VECTORCALL_ARGUMENTS_OFFSET, 0 ); } PyObject* member_method_object_old_new_handler( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { PyObject* args[] = { pyobject_cast( member ), pyobject_cast( atom ), oldvalue, newvalue }; return PyObject_VectorcallMethod( member->validate_context, args, 4 | PY_VECTORCALL_ARGUMENTS_OFFSET, 0 ); } typedef PyObject* ( *handler )( Member* member, CAtom* atom, PyObject* oldvalue, PyObject* newvalue ); static handler handlers[] = { no_op_handler, bool_handler, long_handler, long_promote_handler, float_handler, float_promote_handler, bytes_handler, bytes_promote_handler, str_handler, str_promote_handler, tuple_handler, fixed_tuple_handler, list_handler, container_list_handler, set_handler, dict_handler, default_dict_handler, instance_handler, non_optional_instance_handler, typed_handler, non_optional_typed_handler, subclass_handler, enum_handler, callable_handler, float_range_handler, float_range_promote_handler, range_handler, coerced_handler, delegate_handler, object_method_old_new_handler, object_method_name_old_new_handler, member_method_object_old_new_handler }; } // namespace PyObject* Member::validate( CAtom* atom, PyObject* oldvalue, PyObject* newvalue ) { if( get_validate_mode() >= sizeof( handlers ) ) return no_op_handler( this, atom, oldvalue, newvalue ); // LCOV_EXCL_LINE return handlers[ get_validate_mode() ]( this, atom, oldvalue, newvalue ); } } // namespace atom atom-0.12.1/atom/subclass.py000066400000000000000000000060521506756731600157070ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from .catom import DefaultValue, Member, Validate class Subclass(Member): """A value which allows objects subtypes of a given type. Values will be tested using the `PyObject_IsSubclass` C API call. This call is equivalent to `issubclass(value, kind)` and all the same rules apply. A Subclass member cannot be set to None. """ __slots__ = () def __init__(self, kind, default=None): """Initialize a Subclass member. Parameters ---------- kind : type or tuple of types The allowed type or types for the subclass. This will be used as the default value if no default is given. default : type, optional The default value for the member. If this is not provided, 'kind' will be used as the default. """ self.set_default_value_mode(DefaultValue.Static, default or kind) self.set_validate_mode(Validate.Subclass, kind) class ForwardSubclass(Subclass): """A Subclass which delays resolving the type definition. The first time the value is accessed or modified, the type will be resolved and the forward subclass will behave identically to a normal subclass. """ __slots__ = "resolve" def __init__(self, resolve): """Initialize a ForwardSubclass member. resolve : callable A callable which takes no arguments and returns the type or tuple of types to use for validating the subclass values. """ self.resolve = resolve self.set_default_value_mode(DefaultValue.MemberMethod_Object, "default") self.set_validate_mode(Validate.MemberMethod_ObjectOldNew, "validate") def default(self, owner): """Called to retrieve the default value. This is called the first time the default value is retrieved for the member. It resolves the type and updates the internal default handler to behave like a normal Subclass member. """ kind = self.resolve() self.set_default_value_mode(DefaultValue.Static, kind) return kind def validate(self, owner, old, new): """Called to validate the value. This is called the first time a value is validated for the member. It resolves the type and updates the internal validate handler to behave like a normal Subclass member. """ kind = self.resolve() self.set_validate_mode(Validate.Subclass, kind) return self.do_validate(owner, old, new) def clone(self): """Create a clone of the ForwardSubclass object.""" clone = super(ForwardSubclass, self).clone() clone.resolve = self.resolve return clone atom-0.12.1/atom/subclass.pyi000066400000000000000000000043111506756731600160540ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from typing import Callable, Tuple, Type, TypeVar, Union, overload from .catom import Member T = TypeVar("T") T1 = TypeVar("T1") T2 = TypeVar("T2") class Subclass(Member[T, T]): # No default @overload def __new__(cls, kind: Type[T], default: None = None) -> Subclass[Type[T]]: ... @overload def __new__( cls, kind: Tuple[Type[T]], default: None = None ) -> Subclass[Type[T]]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1]], default: None = None ) -> Subclass[Union[Type[T], Type[T1]]]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1], Type[T2]], default: None = None ) -> Subclass[Union[Type[T], Type[T1], Type[T2]]]: ... # With default @overload def __new__(cls, kind: Type[T], default: Type[T]) -> Subclass[Type[T]]: ... @overload def __new__(cls, kind: Tuple[Type[T]], default: Type[T]) -> Subclass[Type[T]]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1]], default: Union[Type[T], Type[T1]] ) -> Subclass[Union[Type[T], Type[T1]]]: ... @overload def __new__( cls, kind: Tuple[Type[T], Type[T1], Type[T2]], default: Union[Type[T], Type[T1], Type[T2]], ) -> Subclass[Union[Type[T], Type[T1], Type[T2]]]: ... class ForwardSubclass(Subclass[T]): @overload def __new__(cls, resolve: Callable[[], Type[T]]) -> ForwardSubclass[Type[T]]: ... @overload def __new__( cls, resolve: Callable[[], Tuple[Type[T]]] ) -> ForwardSubclass[Type[T]]: ... @overload def __new__( cls, resolve: Callable[[], Tuple[Type[T], Type[T1]]] ) -> ForwardSubclass[Union[Type[T], Type[T1]]]: ... @overload def __new__( cls, resolve: Callable[[], Tuple[Type[T], Type[T1], Type[T2]]] ) -> ForwardSubclass[Union[Type[T], Type[T1], Type[T2]]]: ... atom-0.12.1/atom/tuple.py000066400000000000000000000116051506756731600152210ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from typing import Tuple as TTuple from .catom import DefaultValue, Member, Validate from .instance import Instance from .typing_utils import extract_types, is_optional class Tuple(Member): """A member which allows tuple values. If item validation is used, then assignment will create a copy of the original tuple before validating the items, since validation may change the item values. """ __slots__ = ("item",) def __init__(self, item=None, default=()): """Initialize a Tuple. Parameters ---------- item : Member, type, or tuple of types, optional A member to use for validating the types of items allowed in the tuple. This can also be a type object or a tuple of types, in which case it will be wrapped with an Instance member. If this is not given, no item validation is performed. default : tuple, optional The default tuple of values. """ if item is not None and not isinstance(item, Member): opt, types = is_optional(extract_types(item)) item = Instance(types, optional=opt) self.item = item self.set_default_value_mode(DefaultValue.Static, default) self.set_validate_mode(Validate.Tuple, item) def set_name(self, name): """Set the name of the member. This method ensures that the item member name is also updated. """ super(Tuple, self).set_name(name) if self.item is not None: self.item.set_name(name + "|item") def set_index(self, index): """Assign the index to this member. This method ensures that the item member index is also updated. """ super(Tuple, self).set_index(index) if self.item is not None: self.item.set_index(index) def clone(self): """Create a clone of the tuple. This will clone the internal tuple item if one is in use. """ clone = super(Tuple, self).clone() item = self.item if item is not None: clone.item = item_clone = item.clone() mode, _ctxt = self.validate_mode clone.set_validate_mode(mode, item_clone) else: clone.item = None return clone class FixedTuple(Member): """A member which allows tuple values with a fixed number of items. Items are always validated and can be of different types. Assignment will create a copy of the original tuple before validating the items, since validation may change the item values. """ #: Members used to validate each element of the tuple. items: TTuple[Member, ...] __slots__ = ("items",) def __init__(self, *items, default=None): """Initialize a Tuple. Parameters ---------- items : Iterable[Member | type | tuple[type, ...]] A member to use for validating the types of items allowed in the tuple. This can also be a type object or a tuple of types, in which case it will be wrapped with an Instance member. default : tuple, optional The default tuple of values. """ mitems = [] for i in items: if not isinstance(i, Member): opt, types = is_optional(extract_types(i)) i = Instance(types, optional=opt) mitems.append(i) self.items = mitems if default is None: self.set_default_value_mode(DefaultValue.NonOptional, None) else: self.set_default_value_mode(DefaultValue.Static, default) self.set_validate_mode(Validate.FixedTuple, tuple(mitems)) def set_name(self, name): """Set the name of the member. This method ensures that the item member name is also updated. """ super().set_name(name) for i, item in enumerate(self.items): item.set_name(name + f"|item_{i}") def set_index(self, index): """Assign the index to this member. This method ensures that the item member index is also updated. """ super().set_index(index) for item in self.items: item.set_index(index) def clone(self): """Create a clone of the tuple. This will clone the internal tuple item if one is in use. """ clone = super().clone() clone.items = items_clone = tuple(i.clone() for i in self.items) mode, _ = self.validate_mode clone.set_validate_mode(mode, items_clone) return clone atom-0.12.1/atom/tuple.pyi000066400000000000000000000054321506756731600153730ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from typing import ( Any, Optional, Tuple as TTuple, Type, TypeVar, Union, overload, ) from typing_extensions import Unpack from .catom import Member T = TypeVar("T") T1 = TypeVar("T1") T2 = TypeVar("T2") class Tuple(Member[TTuple[T, ...], TTuple[T, ...]]): @overload def __new__( cls, kind: None = None, default: Optional[TTuple[Any, ...]] = None ) -> Tuple[Any]: ... @overload def __new__( cls, kind: Type[T], default: Optional[TTuple[T, ...]] = None ) -> Tuple[T]: ... @overload def __new__( cls, kind: TTuple[Type[T]], default: Optional[TTuple[T, ...]] = None ) -> Tuple[T]: ... @overload def __new__( cls, kind: TTuple[Type[T], Type[T1]], default: Optional[TTuple[Union[T, T1], ...]] = None, ) -> Tuple[Union[T, T1]]: ... @overload def __new__( cls, kind: TTuple[Type[T], Type[T1], Type[T2]], default: Optional[TTuple[Union[T, T1, T2], ...]] = None, ) -> Tuple[Union[T, T1, T2]]: ... @overload def __new__( cls, kind: Member[T, Any], default: Optional[TTuple[T]] = None ) -> Tuple[T]: ... TT = TypeVar("TT", bound=tuple) # FIXME technically we can allow tuple of types in place of just types but that # is not expected to serve often. class FixedTuple(Member[TT, TT]): @overload def __new__( cls, *items: Unpack[TTuple[Member[T, Any]]], default: Optional[TTuple[T]] = None ) -> FixedTuple[TTuple[T]]: ... @overload def __new__( cls, *items: Unpack[TTuple[Member[T, Any], Member[T1, Any]]], default: Optional[TTuple[T, T1]] = None, ) -> FixedTuple[TTuple[T, T1]]: ... @overload def __new__( cls, *items: Unpack[TTuple[Member[T, Any], Member[T1, Any], Member[T2, Any]]], default: Optional[TTuple[T, T1, T2]] = None, ) -> FixedTuple[TTuple[T, T1, T2]]: ... @overload def __new__( cls, *items: Unpack[TTuple[Type[T]]], default: Optional[TTuple[T]] = None ) -> FixedTuple[TTuple[T]]: ... @overload def __new__( cls, *items: Unpack[TTuple[Type[T], Type[T1]]], default: Optional[TTuple[T, T1]] = None, ) -> FixedTuple[TTuple[T, T1]]: ... @overload def __new__( cls, *items: Unpack[TTuple[Type[T], Type[T1], Type[T2]]], default: Optional[TTuple[T, T1, T2]] = None, ) -> FixedTuple[TTuple[T, T1, T2]]: ... atom-0.12.1/atom/typed.py000066400000000000000000000165331506756731600152220ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from .catom import DefaultValue, GetState, Member, Validate from .typing_utils import extract_types, is_optional class Typed(Member): """A value which allows objects of a given type or types. Values will be tested using the `PyObject_TypeCheck` C API call. This call is equivalent to `type(obj) in cls.mro()`. It is less flexible but can be faster than Instance. Use Instance when allowing you need a tuple of types or (abstract) types relying on custom __isinstancecheck__ and Typed when the value type is explicit. If optional is True, the value of a Typed may be set to None, otherwise None is not considered as a valid value. """ __slots__ = () def __init__(self, kind, args=None, kwargs=None, *, factory=None, optional=None): """Initialize an Typed. Parameters ---------- kind : type The allowed type for the value. args : tuple, optional If 'factory' is None, then 'kind' is a callable type and these arguments will be passed to the constructor to create the default value. kwargs : dict, optional If 'factory' is None, then 'kind' is a callable type and these keywords will be passed to the constructor to create the default value. factory : callable, optional An optional factory to use for creating the default value. If this is not provided and 'args' and 'kwargs' is None, then the default value will be None, which will raised if accessed when optional is False. optional : bool | None, optional Boolean indicating if None is a valid value for the member. By default, the value is inferred to be True if no args or factory is provided. """ opt, (kind,) = is_optional(extract_types(kind)) if opt and optional is False: raise ValueError( "The type passed to Typed is declared optional but optional was " "explicitly set to False" ) # If opt is False we preserve optional as None for backward compatibility. optional = optional if optional is not None else (opt or None) if factory is not None: self.set_default_value_mode(DefaultValue.CallObject, factory) elif args is not None or kwargs is not None: args = args or () kwargs = kwargs or {} def factory(): return kind(*args, **kwargs) self.set_default_value_mode(DefaultValue.CallObject, factory) elif optional is False: self.set_default_value_mode(DefaultValue.NonOptional, None) optional = ( optional if optional is not None else factory is None and args is None and kwargs is None ) if optional: self.set_validate_mode(Validate.OptionalTyped, kind) else: self.set_validate_mode(Validate.Typed, kind) # Allow to create a pickle with an unset typed value self.set_getstate_mode(GetState.IncludeNonDefault, None) class ForwardTyped(Typed): """A Typed which delays resolving the type definition. The first time the value is accessed or modified, the type will be resolved and the forward typed will behave identically to a normal typed. """ __slots__ = ("args", "kwargs", "optional", "resolve") def __init__(self, resolve, args=None, kwargs=None, *, factory=None, optional=None): """Initialize a ForwardTyped. resolve : callable A callable which takes no arguments and returns the type to use for validating the values. This type can be a generic but must resolve to a single type. list[int] is valid but Optional[int] is not. args : tuple, optional If 'factory' is None, then 'resolve' will return a callable type and these arguments will be passed to the constructor to create the default value. kwargs : dict, optional If 'factory' is None, then 'resolve' will return a callable type and these keywords will be passed to the constructor to create the default value. factory : callable, optional An optional factory to use for creating the default value. If this is not provided and 'args' and 'kwargs' is None, then the default value will be None, which will raised if accessed when optional is False. optional : bool | None, optional Boolean indicating if None is a valid value for the member. By default, the value is inferred to be True if no args or factory is provided. """ self.resolve = resolve self.args = args self.kwargs = kwargs if factory is not None: self.set_default_value_mode(DefaultValue.CallObject, factory) elif args is not None or kwargs is not None: mode = DefaultValue.MemberMethod_Object self.set_default_value_mode(mode, "default") elif optional is False: self.set_default_value_mode(DefaultValue.NonOptional, None) self.optional = ( optional if optional is not None else factory is None and args is None and kwargs is None ) if not self.optional: # Allow to create a pickle with an unset typed value self.set_getstate_mode(GetState.IncludeNonDefault, None) self.set_validate_mode(Validate.MemberMethod_ObjectOldNew, "validate") def default(self, owner): """Called to retrieve the default value. This is called the first time the default value is retrieved for the member. It resolves the type and updates the internal default handler to behave like a normal Typed member. """ (kind,) = extract_types(self.resolve()) args = self.args or () kwargs = self.kwargs or {} def factory(): return kind(*args, **kwargs) self.set_default_value_mode(DefaultValue.CallObject, factory) return kind(*args, **kwargs) def validate(self, owner, old, new): """Called to validate the value. This is called the first time a value is validated for the member. It resolves the type and updates the internal validate handler to behave like a normal Typed member. """ (kind,) = extract_types(self.resolve()) if self.optional: self.set_validate_mode(Validate.OptionalTyped, kind) else: self.set_validate_mode(Validate.Typed, kind) return self.do_validate(owner, old, new) def clone(self): """Create a clone of the ForwardTyped instance.""" clone = super(ForwardTyped, self).clone() clone.resolve = self.resolve clone.args = self.args clone.kwargs = self.kwargs clone.optional = self.optional return clone atom-0.12.1/atom/typed.pyi000066400000000000000000000071761506756731600153760ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from typing import ( Any, Callable, Dict, Literal, Optional, Tuple, Type, TypeVar, overload, ) from .catom import Member T = TypeVar("T") class Typed(Member[T, T]): @overload def __new__( cls, kind: Type[T], args: None = None, kwargs: None = None, *, factory: None = None, optional: None = None, ) -> Typed[Optional[T]]: ... @overload def __new__( cls, kind: Type[T], args: Tuple[Any, ...], kwargs: Optional[Dict[str, Any]] = None, *, factory: None = None, optional: None = None, ) -> Typed[T]: ... @overload def __new__( cls, kind: Type[T], args: None = None, *, kwargs: Dict[str, Any], factory: None = None, optional: None = None, ) -> Typed[T]: ... @overload def __new__( cls, kind: Type[T], args: None = None, kwargs: None = None, *, factory: Callable[[], T], optional: None = None, ) -> Typed[T]: ... @overload def __new__( cls, kind: Type[T], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: Optional[Callable[[], T]] = None, optional: Literal[True], ) -> Typed[Optional[T]]: ... @overload def __new__( cls, kind: Type[T], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: Optional[Callable[[], T]] = None, optional: Literal[False], ) -> Typed[T]: ... class ForwardTyped(Member[T, T]): @overload def __new__( cls, kind: Callable[[], Type[T]], args: None = None, kwargs: None = None, *, factory: None = None, optional: None = None, ) -> ForwardTyped[Optional[T]]: ... @overload def __new__( cls, kind: Callable[[], Type[T]], args: Tuple[Any, ...], kwargs: Optional[Dict[str, Any]] = None, *, factory: None = None, optional: None = None, ) -> ForwardTyped[T]: ... @overload def __new__( cls, kind: Callable[[], Type[T]], args: None = None, *, kwargs: Dict[str, Any], factory: None = None, optional: None = None, ) -> ForwardTyped[T]: ... @overload def __new__( cls, kind: Callable[[], Type[T]], args: None = None, kwargs: None = None, *, factory: Callable[[], T], optional: None = None, ) -> ForwardTyped[T]: ... @overload def __new__( cls, kind: Callable[[], Type[T]], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: Optional[Callable[[], T]] = None, optional: Literal[True], ) -> ForwardTyped[Optional[T]]: ... @overload def __new__( cls, kind: Callable[[], Type[T]], args: Optional[Tuple[Any, ...]] = None, kwargs: Optional[Dict[str, Any]] = None, *, factory: Optional[Callable[[], T]] = None, optional: Literal[False], ) -> ForwardTyped[T]: ... atom-0.12.1/atom/typing_utils.py000066400000000000000000000115501506756731600166210ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- import sys from itertools import chain from types import GenericAlias, UnionType from typing import ( TYPE_CHECKING, Any, List, Literal, NewType, Sequence, Tuple, TypedDict, TypeVar, Union, get_args, get_origin, ) if sys.version_info >= (3, 14): import annotationlib _INVALID_TYPES = (str, annotationlib.ForwardRef) else: _INVALID_TYPES = (str,) GENERICS = (type(List), type(List[int]), GenericAlias) UNION = (UnionType,) TypeLike = Union[type, TypeVar, UnionType, GenericAlias, NewType] if TYPE_CHECKING: from .atom import Atom class _ChangeDict(TypedDict): type: Union[ Literal["create"], Literal["update"], Literal["delete"], Literal["event"], Literal["property"], Literal["container"], ] name: str object: "Atom" value: Any class ChangeDict(_ChangeDict, total=False): oldvalue: Any # ContainerList specific entries, present only when type == "container" operation: Union[ Literal["reverse"], Literal["__delitem__"], Literal["__iadd__"], Literal["__imul__"], Literal["__setitem__"], Literal["append"], Literal["extend"], Literal["insert"], Literal["pop"], Literal["remove"], Literal["sort"], ] # The following are present based on the operation value olditem: Any # operation in ("__setitem__",) newitem: Any # operation in ("__setitem__",) item: Any # operation in ("append", "insert", "pop", "remove", "__delitem__") index: int # operation in ("insert", "pop") items: Sequence[Any] # operation in ("extend", "__iadd__") count: int # operation in ("__imul__") key: Any # operation in ("sort") reverse: bool # operation in ("sort") def _extract_types(kind: TypeLike) -> Tuple[type, ...]: """Extract a tuple of types from a type-like object""" if isinstance(kind, _INVALID_TYPES): raise TypeError( f"Str-based annotations ({kind!r}) are not supported in atom Members." ) ret: List[Any] if isinstance(kind, GENERICS): args = get_args(kind) origin = get_origin(kind) if origin is Union: ret = list(chain.from_iterable(extract_types(a) for a in args)) else: ret = [origin] elif UNION and isinstance(kind, UNION): ret = list(chain.from_iterable(extract_types(a) for a in get_args(kind))) else: ret = [kind] extracted: List[type] = [] for t in ret: if isinstance(t, TypeVar): b = t.__bound__ if b is not None: if isinstance(b, str): raise ValueError( "Forward reference in type var bounds are not supported." ) extracted.extend(_extract_types(b)) elif t.__constraints__: raise ValueError("Constraints in type var are not supported.") else: extracted.append(object) if t.__contravariant__: raise ValueError("TypeVar used in Atom object cannot be contravariant") # NewType only exists for the sake of type checkers so we fall back to # the supertype for runtime checks. elif isinstance(t, NewType): extracted.extend(_extract_types(t.__supertype__)) elif t is Any: extracted.append(object) else: if not isinstance(t, type): raise TypeError( f"Failed to extract types from {kind}. " f"The extraction yielded {t} which is not a type. " "One case in which this can occur is when using unions of " "Literal, and the issues can be worked around by using a " "single literal containing all the values." ) extracted.append(t) return tuple(extracted) def extract_types(kind: Union[TypeLike, Tuple[TypeLike, ...]]) -> Tuple[type, ...]: """Extract a tuple of types from a type-like object or tuple.""" return tuple( chain.from_iterable( _extract_types(k) for k in (kind if isinstance(kind, tuple) else (kind,)) ) ) NONE_TYPE = type(None) def is_optional(kinds: Tuple[type, ...]) -> Tuple[bool, Tuple[type, ...]]: """Determine if a tuple of types contains NoneType.""" if NONE_TYPE in kinds: return True, tuple(k for k in kinds if k is not NONE_TYPE) else: return False, kinds atom-0.12.1/codecov.yml000066400000000000000000000005011506756731600147140ustar00rootroot00000000000000codecov: notify: require_ci_to_pass: yes coverage: precision: 2 status: project: yes patch: yes changes: yes parsers: gcov: branch_detection: conditional: no loop: yes method: no macro: no comment: layout: "header, diff" behavior: default require_changes: no atom-0.12.1/docs/000077500000000000000000000000001506756731600135035ustar00rootroot00000000000000atom-0.12.1/docs/Makefile000066400000000000000000000110611506756731600151420ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* examples: python source/examples/example_doc_generator.py @echo html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/enaml.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/enaml.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/enaml" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/enaml" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." atom-0.12.1/docs/make.bat000066400000000000000000000014271506756731600151140ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=source set BUILDDIR=build if "%1" == "" goto help %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.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% :end popd atom-0.12.1/docs/requirements.txt000066400000000000000000000001321506756731600167630ustar00rootroot00000000000000sphinx>=5 sphinx-rtd-theme>=1 graphviz packaging git+https://github.com/nucleic/cppy@main atom-0.12.1/docs/source/000077500000000000000000000000001506756731600150035ustar00rootroot00000000000000atom-0.12.1/docs/source/advanced/000077500000000000000000000000001506756731600165505ustar00rootroot00000000000000atom-0.12.1/docs/source/advanced/containers.rst000066400000000000000000000040571506756731600214550ustar00rootroot00000000000000.. _advanced-containers: Atom's containers ================= .. include:: ../substitutions.sub Atom uses custom containers to implement type validation and notifications. It implements two list subclasses and one dictionary subclass to this effect. .. note:: Currently the typed validated dictionary is not a subclass of the Python builtin dictionary type. This can cause some unexpected issues in particular when assigning the value stored in a |Dict| member to another |Dict| which will fail. To circumvent this issue one should call ``dict`` on the content of the first member. This is a known problem and will be fixed in a future version of atom. Usually, users should not instantiate those containers manually, in particular because they need a reference to both the member and the instance to which they are tied. Atom provides however an alternative mapping type which uses less memory than a regular dictionary in particular when the mapping contain only few objects: |sortedmap|. |sortedmap| ----------- |sortedmap| can be imported from ``atom.datastructures.api``. Contrary to a regular Python dictionary, |sortedmap| does not requires the keys to be hashable, however they should be sortable. |sortedmap| will fall back on the Python 2 behavior to order any Python object based on the class name and the object id if two objects cannot be compared otherwise. In terms of memory efficiency, here is a quick comparison: +-------------+-------------+---------------+ | | dict | sortedmap | +=============+=============+===============+ | empty | 240 | 72 | +-------------+-------------+---------------+ | 1 key | 240 | 88 | +-------------+-------------+---------------+ | 2 key | 240 | 104 | +-------------+-------------+---------------+ | 100 key | 4704 | 2120 | +-------------+-------------+---------------+ |sortedmap| is not meant to replace dictionaries but can be valuable when a large number of small containers is necessary. atom-0.12.1/docs/source/advanced/customization.rst000066400000000000000000000141671506756731600222230ustar00rootroot00000000000000.. _advanced-customization: Member customization: advanced techniques ========================================= .. include:: ../substitutions.sub In the basics, we covered how to customize the behavior of a member by using prefixed method in a class definition. However this is not the only way to customize members. The following section will first describe how a member determine the action to take at each step of the getting, setting process, before giving more details about the behaviors taht can be used to build custom members. Members inner working --------------------- For each step of the process described of :ref:`basis-members`, Members check the mode identifying the action it should take. Based on this flag, it will either call a builtin function or the appropriate user provided function or method. One can inspect the mode set on the member by accesing the matching attribute as described in the table below. This attribute is a tuple containing two items. The first item is the flag value matching the member behavior, the second item depend on the value of the flag. The behavior of the member can be modified by calling the matching ``set_`` method, to set the flag to a new and provide the additional item matching the flag value. For all steps of the getattr, setattr process, you can invoke them separatly by calling the mathcing ``do_`` method. Among them, |Member.do_full_validate| is special in that it will run both the validate and post_validate steps on the provided value. The following table summarizes the different steps along with the flags and the aforementionned attributes and methods: +---------------+----------------+---------------------------------------------------------------+---------------------------+ | Steps | Mode | Mode (getter/setter) | Manual running | +===============+================+===============================================================+===========================+ | getattr | |GetAttr| | |Member.getattr_mode| / |Member.set_getattr_mode| | |Member.do_getattr| | +---------------+----------------+---------------------------------------------------------------+---------------------------+ | post_getattr | |PostGetAttr| | |Member.post_getattr_mode| / |Member.set_post_getattr_mode| | |Member.do_post_getattr| | +---------------+----------------+---------------------------------------------------------------+---------------------------+ | setattr | |SetAttr| | |Member.setattr_mode| / |Member.set_setattr_mode| | |Member.do_setattr| | +---------------+----------------+---------------------------------------------------------------+---------------------------+ | post_setattr | |PostSetAttr| | |Member.post_setattr_mode| / |Member.set_post_setattr_mode| | |Member.do_post_setattr| | +---------------+----------------+---------------------------------------------------------------+---------------------------+ | validate | |Validate| | |Member.validate_mode| / |Member.set_validate_mode| | |Member.do_validate| | +---------------+----------------+---------------------------------------------------------------+---------------------------+ | post_validate | |PostValidate| | |Member.post_validate_mode| / |Member.set_post_validate_mode| | |Member.do_post_validate| | +---------------+----------------+---------------------------------------------------------------+---------------------------+ | default_value | |DefaultValue| | |Member.default_value_mode| / |Member.set_default_value_mode| | |Member.do_default_value| | +---------------+----------------+---------------------------------------------------------------+---------------------------+ .. note:: Delattr works on the same model but the flag is not exposed as part of the public API. You can still access the mode using |Member.delattr_mode| Behaviors for custom members ---------------------------- In order to create custom members, you can either subclass |Member| and set the modes in the ``__init__`` method, or set the modes after instantiating the member. The modes that can be used in conjunction with custom callable or methods are listed below and expected signature of the callable or the method can be directly inferred from the mode. When specifying a method, the second item of the mode should be the name of the method. In the following, **Object** always refers to an |Atom| subclass instance and **Name** to the member name. - |Getattr|: - CallObject_Object - CallObject_ObjectName - ObjectMethod - ObjectMethod_Name - MemberMethod_Object - |PostGetAttr|: - ObjectMethod_Value - ObjectMethod_NameValue - MemberMethod_ObjectValue - |SetAttr|: - CallObject_ObjectValue - CallObject_ObjectNameValue - ObjectMethod_Value - ObjectMethod_NameValue - MemberMethod_ObjectValue - |PostSetAttr|: - ObjectMethod_OldNew - ObjectMethod_NameOldNew - MemberMethod_ObjectOldNew - |DefaultValue|: - CallObject - CallObject_Object - CallObject_ObjectName - ObjectMethod - ObjectMethod_Name - MemberMethod_Object - |Validate|: - ObjectMethod_OldNew - ObjectMethod_NameOldNew - MemberMethod_ObjectOldNew - |PostValidate|: - ObjectMethod_OldNew - ObjectMethod_NameOldNew - MemberMethod_ObjectOldNew - |GetState|: - ObjectMethod_Name - MemberMethod_Object .. note:: It is recommended to avoid customizing specialized members that may make some assumptions regarding the values of the other modes. Using ``__init_subclass__`` --------------------------- Starting with atom 0.10.0, ``__init_subclass__`` can be meaningfully used to customize members as illustrated in :ref:`ex-pickling`. When accessing members in ``__init_subclass__`` one should however be careful to only modify members that belong to the class being customized. Otherwise a parent class could see its behavior change which is definitively undesirable. To avoid this issue |clone_if_needed| can be used to clone a member if it does not belong to a class and update the class to use the clone. It returns the original member or its clone. atom-0.12.1/docs/source/advanced/index.rst000066400000000000000000000011421506756731600204070ustar00rootroot00000000000000.. _advanced-index: Advanced features of atom ========================= The discussion provided in the previous section of the docs should cover most usecases. However atom does offer some advanced features that advanced users can take advantage of. Those will be described in the following sections .. toctree:: :maxdepth: 1 The Property Member Atom and weak references Atom's containers Advanced members customisation Manual notifications Static type checking explained atom-0.12.1/docs/source/advanced/manual_notifications.rst000066400000000000000000000015701506756731600235130ustar00rootroot00000000000000.. _advanced-manual-notifications: Manual notifications ==================== .. include:: ../substitutions.sub Atom object usually fire notifications at the proper times. However, in some cases (Property member, manual handling of container change), it may be desirable to manually fire a notification. This is possible but require some care. First, when manually notifying, you are responsible for building the change dictionary that will be passed to the handlers. You may refer to :ref:`basis-observation` for a description of the content of this dictionary for normal notifications. Second, because atom handle separately the static observers and the dynamic observers, you will to be sure to call both kinds. To notify the static observers, you should call the |Member.notify| method, while to notify dynamic obsevers you need to call the |Atom.notify| method on the instance. atom-0.12.1/docs/source/advanced/property.rst000066400000000000000000000046751506756731600212020ustar00rootroot00000000000000.. _advanced-property: The Property member =================== .. include:: ../substitutions.sub The |Property| member looks a lot like a normal Python property which makes it quite different from other members. In particular because there is no way from atom to know when the value returned by a |Property| changes, **no notifications are emitted by default when getting or setting it**. Defining a Property member -------------------------- Defining a |Property| and the getter, setter and deleter associated to it can be done in several equivalent manners illustrated below: .. code-block:: Python from atom.api import Atom, Property, Value def get_v(owner): return owner.v def set_v(owner, value): owner.v = value def del_v(owner): del owner.v class MyAtom(Atom): v = Value() p1 = Property(get_v, set_v, del_v) p2 = Property() def _get_p2(self): return get_v(self) def _set_p2(self, value): set_v(self, value def _del_p2(self): del_v(self) p3 = Property() p3.getter def _get(self): return get_v(self) p3.setter def _set(self, value): set_v(self, value) p3.deleter def _del(self): del_v(self) Cached properties ----------------- For **read-only** properties, atom offers the option to cache the value returned by the getter. This can be convenient if the getter performs an expensive operation. The cache can be reset at a later time by deleting the property or by calling the :py:meth:`atom.property.Property.reset` method of the member as illustrated below: .. code-block:: python from atom.api import Atom, Property, cached_property class MyAtom(Atom): cp1 = Property(cached=True) def _get_cp1(self): print('Called cp1') @cached_property def cp2(self): print('Called cp2') a = MyAtom() a.cp1 a.cp1 del a.cp1 a.cp1 a.cp2 a.cp2 MyAtom.cp2.reset(a) a.cp2 Running this code will print "Called cp1/2" only twice each. Notifications from a Property ----------------------------- As mentionned in the introduction, |Property| does not fire notifications upon get/setattr. However it will always fire notifications upon deletion/reset. To manually fire notifications from a property, please refer to :ref:`advanced-manual-notifications`. atom-0.12.1/docs/source/advanced/typing.rst000066400000000000000000000146021506756731600206170ustar00rootroot00000000000000.. _advanced-typing: Static type checking explained =============================== .. include:: ../substitutions.sub Since an atom member is a Python descriptor in which the validation step is allowed to perform a type conversion (ex with `Coerced`), the types may be different when reading and writing the member value. Therefore, the type hint is logically generic over 2 types: - the type that will be returned when accessing the member, which we will refer to as the getter or read type `T` - the type that the member can be set to, which we will refer to as the setter or write type `S` In general, the type hints shipped with Atom are sufficient to narrow down the type of the members without requiring any manual annotation. For example: .. code-block:: class MyAtom(Atom): i = Int() i_f = Int(strict=False) l = List(int) d = Dict((str, bytes), List(int)) will be typed as something equivalent to: .. code-block:: class MyAtom(Atom): i: Member[int, int] i_f: Member[int, float] l: Member[TList[int], TList[int]] d: Member[TDict[Union[str, bytes], TList[int]], TDict[Union[str, bytes], TList[int]]] .. note:: Since many member names conflict with name found in the typing module we will add a leading `T` to types coming from typing. However in real code we recommend rather aliasing the Atom members with a leading `A` as illustrated in the next example. Note that starting with Python 3.9 generic aliases allow to directly use list, dict, set in type annotations which avoids conflicts. However, in some cases, static typing can be more strict than Atom validation such as for tuples and we may not want to validate at runtime the size of the tuple (even though it may be a good idea to do so). .. code-block:: from typing import Tuple from Atom.api import Atom, Tuple as ATuple class MyAtom(Atom): t: "Member[Tuple[int, int], Tuple[int, int]]" = ATuple[int] # type: ignore Let's walk through this case. .. code-block:: from typing import Tuple from Atom.api import Atom, Tuple as ATuple First, since Atom and typing share many names, one must be careful to disambiguate the names. Starting with Python 3.9, one can use generic aliases to limit the conflicts by using native types rather than typing classes. .. code-block:: class MyAtom(Atom): t: "Member[Tuple[int, int], Tuple[int, int]]" = ATuple[int] # type: ignore Here we want to specify, that our tuple member is expected to store 2-tuple of int. Since Atom does not enforce the length of a tuple, its type hint looks like `Member[Tuple[T, ...], Tuple[T, ...]]` and makes the assumption that no fancy type conversion occurs. If we want to go further we need a type hint and this is where things get a bit more complicated. Member is actually defined in C and does not inherit from Protocol. However, atom members implement `__getitem__` allowing the the use of generic aliases. .. note:: If the line becomes too long it can be split on multiple lines as follows: .. code-block:: class MyAtom(Atom): t: "Member[Tuple[int, int], Tuple[int, int]]" t = ATuple[int] # type: ignore Similarly if one implements custom member subclasses and desire to make it compatible with type annotations, one can define the type hint as follow: .. code-block:: class MyMember(Member[int, str]): ... .. note:: The above is valid outside of a .pyi file only under Python 3.9+. .. note:: One can use types from the ``typing`` module or generic aliases in any place where a type or a tuple of type is expected. Note however that when using ``typing.List[int]`` or ``list[T]``, etc in such a position the content of the container will not be validated at runtime by Atom. ``Optional``, ``Union`` and ``Callable`` from the ``typing`` module can also be used, however because they are not seen as proper types by type checkers this will break static type checking. The recommended workaround is to use ``Typed`` or ``Instance`` as appropriate for the first two cases and a separate annotation for the ``typing.Callable`` case. Member typing in term of Member[T, S] ------------------------------------- Below we give the typing of most Atom member in term of Member to clarify the behavior of each member with respect to typing. We also indicate their default typing, but please note that the presence/value of some argument at the member creation will influence the inferred type. .. code-block:: Value[T] = Member[T, T] # default: Value[Any] Constant[T] = Member[T, NoReturn] # default: Constant[Any] ReadOnly[T] = Member[T, T] # default: ReadOnly[Any] Callable[T] = Member[T, T] # default: Callable[TCallable] Bool[S] = Member[bool, S] # default: Bool[bool] Int[S] = Member[int, S] # default: Int[int] Float[S] = Member[float, S] # default: Float[float] Range[S] = Member[int, S] # default: Range[int] FloatRange[S] = Member[float, S] # default: FloatRange[float] Bytes[S] = Member[bytes, S] # default: Bytes[Union[bytes, str]] Str[S] = Member[str, S] # default: Str[Union[bytes, str]] List[T] = Member[TList[T], TList[T]] # List() -> List[Any] # List(int) -> List[int] # List(List(int)) -> List[TList[int]] Set[T] = Member[TSet[T], TSet[T]] # Set() -> Set[Any] # Set(int) -> Set[int] Dict[KT, VT] = Member[TDict[KT, VT], TDict[KT, VT]] # Dict() -> Dict[Any, Any] # Dict(str, int) -> Dict[str, int] # Dict(str, List[int]) -> Dict[str, TList[int]] Typed[T] = Member[T, T] # Typed(int) -> Typed[Optional[int]] # Typed(int, optional=False) -> Typed[int] ForwardTyped[T] = Member[T, T] Instance[T] = Member[T, T] ForwardInstance[T] = Member[T, T] .. note:: All members that can take a tuple of types as argument (List, Dict, etc) have type hints for up to a tuple of 3 types as argument. Supporting more types would make type checking even slower, so we suggest using manual annotation. Finally the case of |Coerced| is a bit special, since we cannot teach type checkers to see a type both as a type and a callable. As a consequence for type checking to be correct when the type itself handle the coercion the type should be manually specified as coercer:: c = Coerced(int, coercer=int) atom-0.12.1/docs/source/advanced/weakref.rst000066400000000000000000000036121506756731600207300ustar00rootroot00000000000000.. _advanced-weakref: Atom and weak references ======================== .. include:: ../substitutions.sub Because atom objects are slotted by default and do not have an instance an instance dictionary, they do not support weak references by defaults. Depending on the context in which you need weak references, you have two options: - if you need weak references to interact with an external library (such as pyqt), you will need to enable the standard weakref mechanism. - if you use weak references only internally and memory is a concern (Python standard weak references have a not so small overhead), you can use an alternative mechanism provided by atom. Enabling default weak references -------------------------------- In order to use the standard weak references of Python, you simply need to add the proper slot to your object as illustrated below: .. code-block:: python from atom.api import Atom MyWeakRefAtom(Atom): __slots__ = ('__weakref__',) .. note:: Starting with atom 0.8.0 you can use the metaclass keyword argument `enable_weakrefs` to achieve the same result. .. code-block:: python from atom.api import Atom MyWeakRefAtom(Atom, enable_weakrefs=True): pass Using atom builtin weak references: |atomref| --------------------------------------------- To create a weak reference to atom object using the builtin mechanism, you simply have to create an instance of |atomref| using the object to reference as argument. In order to access the object referenced by the |atomref|, you simply need to call it which will return the object. If the referenced object is not alive anymore, |atomref| will return None. .. code-block:: python import gc from atom.api import Atom, atomref class MyAtom(Atom): pass obj = MyAtom() ref = atomref(obj) assert obj is ref() del obj gc.collect() assert ref() is None atom-0.12.1/docs/source/api/000077500000000000000000000000001506756731600155545ustar00rootroot00000000000000atom-0.12.1/docs/source/api/atom.api.rst000066400000000000000000000001631506756731600200160ustar00rootroot00000000000000atom.api module =============== .. automodule:: atom.api :members: :undoc-members: :show-inheritance: atom-0.12.1/docs/source/api/atom.atom.rst000066400000000000000000000001661506756731600202100ustar00rootroot00000000000000atom.atom module ================ .. automodule:: atom.atom :members: :undoc-members: :show-inheritance: atom-0.12.1/docs/source/api/atom.catom.rst000066400000000000000000000001711506756731600203470ustar00rootroot00000000000000atom.catom module ================= .. automodule:: atom.catom :members: :undoc-members: :show-inheritance: atom-0.12.1/docs/source/api/atom.coerced.rst000066400000000000000000000001771506756731600206560ustar00rootroot00000000000000atom.coerced module =================== .. automodule:: atom.coerced :members: :undoc-members: :show-inheritance: atom-0.12.1/docs/source/api/atom.containerlist.rst000066400000000000000000000002211506756731600221160ustar00rootroot00000000000000atom.containerlist module ========================= .. automodule:: atom.containerlist :members: :undoc-members: :show-inheritance: atom-0.12.1/docs/source/api/atom.datastructures.rst000066400000000000000000000002701506756731600223210ustar00rootroot00000000000000atom.datastructures package =========================== Submodules ---------- .. automodule:: atom.datastructures.sortedmap :members: :undoc-members: :show-inheritance: atom-0.12.1/docs/source/api/atom.delegator.rst000066400000000000000000000002051506756731600212100ustar00rootroot00000000000000atom.delegator module ===================== .. automodule:: atom.delegator :members: :undoc-members: :show-inheritance: atom-0.12.1/docs/source/api/atom.dict.rst000066400000000000000000000001661506756731600201730ustar00rootroot00000000000000atom.dict module ================ .. automodule:: atom.dict :members: :undoc-members: :show-inheritance: atom-0.12.1/docs/source/api/atom.enum.rst000066400000000000000000000001661506756731600202140ustar00rootroot00000000000000atom.enum module ================ .. automodule:: atom.enum :members: :undoc-members: :show-inheritance: atom-0.12.1/docs/source/api/atom.event.rst000066400000000000000000000001711506756731600203650ustar00rootroot00000000000000atom.event module ================= .. automodule:: atom.event :members: :undoc-members: :show-inheritance: atom-0.12.1/docs/source/api/atom.instance.rst000066400000000000000000000002021506756731600210430ustar00rootroot00000000000000atom.instance module ==================== .. automodule:: atom.instance :members: :undoc-members: :show-inheritance: atom-0.12.1/docs/source/api/atom.list.rst000066400000000000000000000001661506756731600202230ustar00rootroot00000000000000atom.list module ================ .. automodule:: atom.list :members: :undoc-members: :show-inheritance: atom-0.12.1/docs/source/api/atom.meta.rst000066400000000000000000000007041506756731600201740ustar00rootroot00000000000000atom.meta package ================= Submodules ---------- .. automodule:: atom.meta.atom_meta :members: :undoc-members: :show-inheritance: .. automodule:: atom.meta.annotation_utils :members: :undoc-members: :show-inheritance: .. automodule:: atom.meta.member_modifiers :members: :undoc-members: :show-inheritance: .. automodule:: atom.meta.observation :members: :undoc-members: :show-inheritance: atom-0.12.1/docs/source/api/atom.property.rst000066400000000000000000000002021506756731600211230ustar00rootroot00000000000000atom.property module ==================== .. automodule:: atom.property :members: :undoc-members: :show-inheritance: atom-0.12.1/docs/source/api/atom.rst000066400000000000000000000011441506756731600172460ustar00rootroot00000000000000atom package ============ Atom does not export any name at the root of the package (`__init__.py` is empty). To access the most commonly used component import the `api.py` module found in each package. Subpackages ----------- .. toctree:: atom.datastructures atom.meta Submodules ---------- .. toctree:: atom.api atom.atom atom.meta atom.catom atom.coerced atom.containerlist atom.delegator atom.dict atom.enum atom.event atom.instance atom.list atom.property atom.scalars atom.signal atom.subclass atom.tuple atom.typed atom.typing_utils atom-0.12.1/docs/source/api/atom.scalars.rst000066400000000000000000000001771506756731600207020ustar00rootroot00000000000000atom.scalars module =================== .. automodule:: atom.scalars :members: :undoc-members: :show-inheritance: atom-0.12.1/docs/source/api/atom.signal.rst000066400000000000000000000001741506756731600205240ustar00rootroot00000000000000atom.signal module ================== .. automodule:: atom.signal :members: :undoc-members: :show-inheritance: atom-0.12.1/docs/source/api/atom.subclass.rst000066400000000000000000000002021506756731600210560ustar00rootroot00000000000000atom.subclass module ==================== .. automodule:: atom.subclass :members: :undoc-members: :show-inheritance: atom-0.12.1/docs/source/api/atom.tuple.rst000066400000000000000000000001711506756731600203750ustar00rootroot00000000000000atom.tuple module ================= .. automodule:: atom.tuple :members: :undoc-members: :show-inheritance: atom-0.12.1/docs/source/api/atom.typed.rst000066400000000000000000000001711506756731600203710ustar00rootroot00000000000000atom.typed module ================= .. automodule:: atom.typed :members: :undoc-members: :show-inheritance: atom-0.12.1/docs/source/api/atom.typing_utils.rst000066400000000000000000000002161506756731600217760ustar00rootroot00000000000000atom.typing_utils module ======================== .. automodule:: atom.typing_utils :members: :undoc-members: :show-inheritance: atom-0.12.1/docs/source/basis/000077500000000000000000000000001506756731600161045ustar00rootroot00000000000000atom-0.12.1/docs/source/basis/basis.rst000066400000000000000000000204751506756731600177470ustar00rootroot00000000000000.. _basis-basis: Anatomy ======= .. include:: ../substitutions.sub Since atom is designed to allow to define compact objects, the best way to illustrate how it works is to study a class definition making use of it. This example will serve to introduce key concepts that will be explained in more details in the following sections. .. code-block:: python from atom.api import Atom, Value, Int, List, set_default, observe class CompactObject(Atom): """Compact object generating notifications. """ untyped_value = Value() int_value = Int(10) list_value = List().tag(pref=True) def _post_setattr_int_value(self, old, new): self.untyped_value = (old, new) def _observe_int_value(self, change): print(change) @observe('list_value') def notify_change(self, change): print(change) class NewCompactObject(Atom): """Subclass with different default values. """ list_value = default_value([1, 2]) def _default_int_value(self): return 1 First note that contrary to a number of projects, atom does not export any objects in the top level atom package. To access the publicly available names you should import from `atom.api`. .. code-block:: python from atom.api import Atom, Value, Int, List, default_value, observe Here we import several things: - |Atom|: This is the base class to use for all objects relying on Atom. It provides the some basic methods that will be described later on or in the API documentation. (This class inherits from a more basic class CAtom) - |Value|, |Int|, |List|: Those are members. One can think of them as advanced properties (ie they are descriptors). They define the attributes that are available on the instances of the class. They also provide type validation. - |observe|: This is a decorator. As we will see later, it can be used to call the decorated method when a member value 'change'. - |set_default|: Members can have a default value and this object is used to alter it when subclassing an Atom object. Now, that the imports are hopefully clear (or at least clearer), let's move to the beginning of the first class definition. .. code-block:: python class CompactObject(Atom): """Compact object generating notifications. """ untyped_value = Value() int_value = Int(10) list_value = List().tag(pref=True) Here we define a class and add to it three members. Those three members will be the attributes, that can be manipulated on the class instances. In particular, the following will crash while it would work for a usual python object: .. code-block:: python obj = CompactObject() obj.non_defined = 0 This may be surprising, since on usual Python objects one can define new attributes on instances. This limitation is the price to pay for the compacity of Atom objects. .. note:: This limitation should rarely be an issue and if it is one can get dynamic attributes back by adding the following line to the class definition:: __slots__ = ('__dict__',) Ok, so each member will be one instance attribute. Now, let's look at them in more details. Our first member is a simple |Value|. This member actaully does not perform any type validation and can be used when the attributes can really store anything. Our second member is an |Int|. This member will validate that the assigned value is actually an integer and the default value is 10 instead of 0. Finally, we have |List| which obviously can only be a list. In addition, we tagged the member. Tags are actually metadata attached to the descriptors. They have no built-in use in atom but they can be used to filter on an instance members when filtering them. Refer to the :doc:`metadata.py <../examples/ex_metadata>` example for an illustration. .. note:: All the available members are described in details in :ref:`basis-members` Coming back to the class definition, we now reached the methods definitions. .. code-block:: python def _post_setattr_int_value(self, old, new): self.untyped_value = (old, new) def _observe_int_value(self, change): print(change) @observe('list_value') def notify_change(self, change): print(change) Here we define three methods. None of these are meant to be called directly by the user-code but will be called by the framework at appropriate times. - ``_post_setattr_int_value``: This function will be called right after setting the value of ``int_value``, as its name indicates. It will get both the value of the member before the setting operation (old) and the value that was just set (set). - ``_observe_int_value``: This function will be called each the value of ``int_value`` changes (not necessarily through a setattr operation). It is passed of dictionary containing a bunch of information about the actual modification. We will describe the content of this dictionary in details in :ref:`basis-observation`. - ``notify_changes``: Because this function is decorated with the observe decorator, it will be called each time ``list_value``. Note however, that changes to the container or its content, e.g. through ``append`` will not be caught. .. note:: Prefixed methods (_post_setattr, _observe, ...) are discussed in more details in :ref:`basis-mangled-methods`. .. note:: Here, we have only seen observer definition from within a class. It IS possible to define observers on instances and this will be discussed in :ref:`basis-observation`. Now we can look at the second class definition and discuss default values a bit more. .. code-block:: python class NewCompactObject(CompactObject): """Subclass with different default values. """ list_value = set_default([1, 2]) def _default_int_value(self): return 1 In this subclass, we simply alter the default values of two of the members. We do that in two ways: - using |set_default| which indicates to the framework that it should create a copy of the member existing of the base class and change the default value. - using a specially named method starting with ``_default_`` followed by the member name. To clarify what this does, we look at what happens after we create instances of each of our classes. .. code-block:: python obj1 = CompactObject() print(obj1.int_value) print(obj1.list_value) obj2 = NewCompactObject() print(obj2.int_value) print(obj2.list_value) The output of this block will be: - ``10``: which match the specified default value in the class definition - ``[]``: which corresponds to the absence of a specific default value for a list. - ``1``: which corresponds to the value returned by the method used to compute the default value. - ``[1, 2]`` which corresponds to the default value we specified using |set_default|. .. note:: First note, that even though we did not define ``__init__`` methods, we can pass any of the members of the class as a keyword argument, in which case the argument will be used to set the value of the corresponding member. .. code-block:: python obj1 = CompactObject(untyped_value='e') .. note:: Atom objects can be frozen using |Atom.freeze| at any time of their lifetime to forbid further modifications. .. note:: Atom objects can be pickled. Starting with atom 0.9.0 only pickleable members will be pickled (Constant is not pickled since it cannot be restored) and the fact that an object is frozen is preserved across pickling-unpickling. .. note:: Starting with atom 0.10.0, ``__init_subclass__`` can be used to further customize an Atom class. It can for example comes in handy to customize pickling to be limited to public members in a way that applies to all subclasses. See :ref:`advanced-customization` for more details. Conclusion ---------- This brief introduction should have given some basics concerning Atom working. The next three sections will cover in more details three points introduced here: the members, notifications and in particular observers specific to an instance, and finally the specially named methods used to alter default member behaviors. .. note:: Starting with atom 0.8.0 atom classes can also infer their members from type annotations see :ref:`basis-typing` atom-0.12.1/docs/source/basis/index.rst000066400000000000000000000011551506756731600177470ustar00rootroot00000000000000.. _basis-get_started: =============== Getting Started =============== Getting started with atom is easy. The following sections will cover the basics of atom and should cover the needs of most users. In particular it will try to shed lights on the following points: - how to define a new Atom object - how type validation works - how the observer pattern works .. toctree:: :maxdepth: 1 Installation Anatomy Members Observation and notifications Basic customization Using type annotations atom-0.12.1/docs/source/basis/installation.rst000066400000000000000000000035141506756731600213420ustar00rootroot00000000000000.. _basis-installation: .. include:: ../substitutions.sub =============== Installing Atom =============== Atom is supported on Python 2.7, and 3.4+. Installing it is a straight-forward process. There are three approaches to choose from. The easy way: Pre-compiled packages ----------------------------------- The easiest way to install atom is through pre-compiled packages. Atom is distributed pre-compiled in two-forms. Conda packages ^^^^^^^^^^^^^^ If you use the `Anaconda`_ Python distribution platform (or `Miniconda`_, its lighter-weight companion), the latest release of Atom can be installed using conda from the default channel or the conda-forge channel:: $ conda install atom $ conda install atom -c conda-forge .. _Anaconda: https://store.continuum.io/cshop/anaconda .. _Miniconda: https://conda.io/miniconda.html Wheels ^^^^^^ If you don't use Anaconda, you can install Atom pre-compiled, through PIP, for most common platforms:: $ pip install atom Compiling it yourself: The Hard Way ----------------------------------- Building atom from scratch requires Python and a C++ compiler. On Unix platform getting a C++ compiler properly configured is generally straighforward. On Windows, starting with Python 3.6 the free version of the Microsoft toolchain should work out of the box. Installing atom is then as simple as:: $ pip install . .. note:: For MacOSX users on OSX Mojave, one needs to set MACOSX_DEPLOYMENT_TARGET to higher than 10.9 to force teh compiler to use the new C++ stdlib:: $ export MACOSX_DEPLOYMENT_TARGET=10.10 Supported Platforms ------------------- Atom is known to run on Windows, OSX, and Linux; and compiles cleanly with MSVC, Clang, GCC, and MinGW. If you encounter a bug, please report it on the `Issue Tracker`_. .. _Issue Tracker: http://github.com/nucleic/enaml/issues atom-0.12.1/docs/source/basis/mangled_methods.rst000066400000000000000000000076131506756731600217770ustar00rootroot00000000000000.. _basis-mangled-methods: Customimizing members: specially named methods ============================================== .. include:: ../substitutions.sub Atom offers multiple ways to customize the inner working of members. The easiest one is to use specially named methods on your class definition. Since this covers most of the use cases, it is the only one that is covered here more details and advanced methods can be found in :ref:`advanced-customization`. The customization method should use the name of the member to customize with one of the following prefixes depending on the operation to customize: - ``_default_``: to define default values. - ``_observe_``: to define a static observer. - ``_validate_``: to define a custom validation algorithm. - ``_post_getattr_``: to customize the post-getattr step. - ``_post_setattr_``: to customize the post-setattr step. - ``_post_validate_``: to customize the post-setattr step. - ``_getstate_``: to determine if a member should be pickled Default values -------------- A default value handler should take no argument and return the default value for the member. .. code-block:: python class MyAtom(Atom): v = Value() def _default_v(self): return [{}, 1, 'a'] Static observers ---------------- A static observer is basically an observer so it should take the change dictionary as argument (save for |Signal|). .. code-block:: python class MyAtom(Atom): v = Value() def _observe_v(self, change): print(change) Validation ---------- A validation handler should accept both the old value of the member and the new value to validate. It should return a valid value. .. code-block:: python class MyAtom(Atom): v = Value() def _validate_v(self, old, new): if old and not isinstance(new, type(old)): raise TypeError() return new Post-operation methods ---------------------- Post-gettatr should take a single argument, ie the value that was retrieve during the *get* step, and return whatever value it decides to. .. code-block:: python class MyAtom(Atom): v = Value() def _post_getattr_v(self, value): print('v was accessed') return value Post-setattr and post-validate both take the old and the new value of the member as input, and post-validate should return a valid value. .. code-block:: python class MyAtom(Atom): v = Value() def _post_setattr_v(self, old, new): print('v was set') return value class MyAtom(Atom): v = Value() def _post_validate_v(self, old, new): print('v was validated') return value Pickle ------ Getstate method should take as single argument the name of the member they apply to and return a bool indicating whether or not the member value should be included in the object pickle. .. code-block:: python class MyAtom(Atom): v = Value() def _getstate_v(self, name): print('do not pickle v') return False .. note:: Contrary to other operations, answering to the question whether a member should be pickled or not can be done for a given Atom class and has little to do with the current state of the class instance. For such cases, it is possible to directly set the member getstate mode as follows: .. code:: python from atom.api import GetState class A(Atom): # This member will never be pickled. unpickeable = Value() unpickeable.set_getstate_mode(GetState.Exclude, None) Useful variants from the |GetState| enum are: - Include: also include the member value in the pickle - Exclude: never include the member value in the pickle - IncludeNonDefault: include the member value only if it already exists (i.e. we won't need to invoke default to get a value). atom-0.12.1/docs/source/basis/members.rst000066400000000000000000000263621506756731600203010ustar00rootroot00000000000000.. _basis-members: Introducing the members ======================= .. include:: ../substitutions.sub As we have seen in the introduction, members are used in the class definition of an atom object to define the fields that will exist on each instance of that class. As such, members are central to atom. The following sections will shed some lights on the different members that come with atom and also how they work which will come handy when we will discuss how you can customize the behaviors of members later in this guide. .. note:: Starting with atom 0.7, atom ships with type hints allowing type checkers to resolve the values behind a member. More details about how typing works in atom and how to add custom type hints can be found in :ref:`advanced-typing` Member workings --------------- From a technical point of view, members are descriptors like properties and they can do different things when you try to access or set the attribute. Member reading ~~~~~~~~~~~~~~ Let's first look at what happen when you access an attribute: .. code-block:: python class Custom(Atom): value = Int() obj = Custom() obj.value obj.value Since we did not pass a value for ``value`` when instantiating our class (we did not do ``obj=Custom(value=1)``), when we first access ``value`` it does not have any value. As a consequence the framework will fetch the default value. As we have seen in the introduction, the default value can be specified in several ways, either as argument to the member, or using |set_default| or even by using a specially named method (more on that in :ref:`basis-mangled-methods`). Once the framework has fetched the default value it will *validate* it. In particular here, we are going to check that we did get an integer for example. The details of the validation will obviously depend on the member. If the value is valid, next a post-validation method will be called that can some do further processing. By default this is a no-op and we will see in :ref:`basis-mangled-methods` how this can be customized. With this process complete, the state of our object has changed since we created the value stored in that instance. This corresponds to a *create* that will be sent to the observers if any is registered. The observer called, the value can now be stored (so that we don't go through this again) and is now ready to be returned to the sure, the *get* step is complete. However before doing that we will actually perform a *post-gettatr* step. Once again this is a no-op by default but can be customized. On further accesses, since the value exists, we will go directly retrieve the value and perform the *post-getattr*, and no notification will be generated. To summarize: .. digraph:: getattr :align: center a [label="A value was previously set?"]; a->b[label="Yes"]; a->c[label="No"]; b[label="get the value"]; c[label="retrieve the default value"]; c->d; d[label="validate the value"]; d->e; e[label="run post-validation"]; e->f; f[label="store the value"]; f->g; g[label="call observers"]; g->i; c->i; i[label="run post-getattr"]; i->j; j[label="return the result of post-getattr" ]; Member writing ~~~~~~~~~~~~~~~ Setting a value follows a very similar pattern. First the value is of course validated (and post-validated). It is then actually stored (*set*). Next as for the *get* and *validate* operation, a *post-setattr* step is run. As for the other *post* by default this won't do anything. Finally is any observer is attached, the observers are notified. To summarize: .. digraph:: setattr :align: center a [label="validate the value"]; a->b; b[label="run post-validation"]; b->c; c[label="store the value"]; c->d; d[label="run post-setattr"]; d->e; e[label="call observers"]; Members introduction -------------------- Now that the behavior of members is a bit less enigmatic let's introduce the members that comes with atom. Members for simple values ~~~~~~~~~~~~~~~~~~~~~~~~~ Atom provides the following members for basic scalars types: - |Value|: a member that can receives any value, no validation is performed - |Int|: an integer value. One can choose if it is allowed to cast the assigned values (float to int), the default is true. - |Range|: an integer value that is clamped to fall within a range. - |Float|: a floating point value. One can choose if it is allowed to cast the assigned values (int to float, ...), the default is true. - |FloatRange|: a floating point value that is clamped to fall within a range. - |Bytes|, |Str|: bytes and unicode strings. One can choose if it is allowed to cast the assigned values (str to bytes, ...), the default is false. - |Enum|: a value that can only take a finite set of values. Note that this is unrelated to the enum module. Containers and type validation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Atom also provides members for three basic containers tuple, list and dictionaries: |Tuple|, |List|, |Dict|, |DefaultDict|. In each case, you can specify the type of the values (key and value for dict), using members, as follows: .. code-block:: python class MyAtom(Atom): t = Tuple(Int()) l = List(Float()) d = Dict(Str(), Int()) d = DefaultDict(Str(), Int()) Alternatively, you can pass simple Python types. In this cases they will be wrapped in an |Instance| member that will be introduced in the next section. .. code-block:: python class MyAtom(Atom): t = Tuple(int) l = List(float) d = Dict(str, int) dd = DefaultDict(str, int) .. note:: Note that you cannot (by default) enforce a specific number of items in a tuple. .. note:: For |DefaultDict|, the default value factory can be inferred from the value member. It can also be specified using the ``missing`` keyword argument. .. note:: In order to enforce type validation of container, atom has to use custom subclass. As a consequence, when assigning to a member, the original container is copied. This copy on assignment behavior can cause some surprises if you modify the original container after assigning it. One additional important point, atom does not track the content of the container. As a consequence, in place modifications of the container do not trigger any notifications. One workaround can be to copy the container, modify it and re-assign it. Another option for lists is to use a |ContainerList| member, which uses a special list subclass sending notifications when the list is modified. Enforcing custom types ~~~~~~~~~~~~~~~~~~~~~~ Sticking to simple types can quickly be limiting and this is why atom provides member to enforce that the value is simply of a certain type or a subclass: - |Instance|: the value must pass ``isinstance(value, types))``. Using |Instance| once can specify a tuple of types. - |Typed|: the value must of the specified type or a subtypes. Only one type can be specified. This check is equivalent to `type(obj) in cls.mro()`. It is less flexible but faster than |Instance|. Use |Instance| when allowing you need a tuple of types or (abstract) types relying on custom __isinstancecheck__and |Typed| when the value type is explicit. - |Subclass|: the value must be a class and a subclass of the specified type. .. note :: By default, |Typed| and |Instance| consider ``None`` to be a valid value if no way to build a default value was provided. One can explicitly specify if ``None`` is a valid value by using the ``optional`` keyword argument when creating the argument. New in atom 0.7.0, previously None was always a valid value. .. note:: If a |Typed| or |Instance| member is created with ``optional=False`` and no mean of creating a default value (no ``args``, ``kwargs`` or ``factory``), trying to access the member value before setting it will result in a ValueError. .. note:: Even though, generic aliases (i.e. list[int], introduced in `PEP 585 `_ ) are not proper types they can be used. Note however that just like ``isinstance(a, list[int])``, a member ``Instance(list[int])`` does not check the type of the items of a. In some cases, the type is not accessible when the member is instantiated (because it will be created later in the same file for example), atom also provides |ForwardTyped|, |ForwardInstance|, |ForwardSubclass|. Those three members rather than taking a type or a tuple of type as argument, accept a callable taking no argument and returning the type(s) to use for validation. .. code-block:: python class Leaf(Atom): node = ForwardTyped(lambda : Node) class Node(Atom): parent = ForwardTyped(lambda : Node) leaves = List(Typed(Leaf)) In some cases, the same information may be conveniently represented either by a custom class or something simpler, like a tuple. One example of such a use case is a color: a color can be easily represented by the four components (red, green, blue, alpha) but in a library may be represented by a custom class. Atom provides the |Coerced| member to allow to enforce a particular type while also allowing seamless conversion from alternative representations. The conversion can occur in two ways as illustrated below: - by calling the specified types on the provided value - by calling an alternative coercer function provided to the member .. code-block:: python class Color(object): def __init__(self, components): self.red, self.green. self.blue, self.alpha = components def dict_to_color(color_dict): components = [] for c in ('red', 'green', 'blue', 'alpha') components.append(color_dict[c]) return Color(components) class MyAtom(Atom): color = Coerced(Color) color2 = Coerced(Color, coercer=dict_to_color) Memory less members ~~~~~~~~~~~~~~~~~~~ Atom also provides two members that do not remember the value they are provided, but that can be used to fire notifications: - |Event|: this is a member to which each time a value is assigned to, a notification is fired. Additionally one can specify the type of value that are accepted. An alternative way to fire the notification is to call the object you get when accessing the member. - |Signal|: this member is similar to Qt signal. One cannot be assigned to it, however one can call it on instances, and when called the notifier will be called **with the arguments and keyword arguments passed to the signal**. Note that this is at odds with the general behavior of observers described in :ref:`basis-observation`. The example below illustrates how those members work: .. code-block:: python class MyAtom(Atom): s = Signal() e = Event() @observe('s', 'e') def print_value(self, change): print(change) obj = MyAtom() obj.e = 2 obj.e(1) obj.s(2) obj.s.emit(1) |Delegator| ~~~~~~~~~~~ This last member is a bit special. It does not do anything by itself but can be used to copy the behaviors of another member. In addition, any observer attached to the delegator will also be attached to the delegate member. |Property| ~~~~~~~~~~ The |Property| member is a special case and it will be discussed in details in :ref:`advanced-property`. atom-0.12.1/docs/source/basis/observation.rst000066400000000000000000000153041506756731600211740ustar00rootroot00000000000000.. _basis-observation: Notifications and observers =========================== .. include:: ../substitutions.sub One key feature of the framework in addition to small memory footprint and type validation is the implementation of the observer pattern. In :ref:`basis-basis`, we introduced the notion of static observers. Here we will discuss them in more details along with dynamic observers. We will also describe in depth the possible signature for notification handlers and the arguments they receive upon invocation. .. note:: The point at which notifications are fired has been discussed in :ref:`basis-members`. Static and dynamic observers ---------------------------- An observer is a callable that is called each time a member changes. For most members it will be: - when the member get value for the first time either through an assignment or a first access when the default value is used. We will refer to this as a 'create' event. - whenever a different value is assigned to the member. We will refer to this as an 'update' event. - when the value of a member is deleted. We will refer to this as a delete event. .. note:: The |ContainerList| member is a special case since it can emit notifications when elements are added or removed from the list. This will be referred to as 'container' events. The distinction between static and dynamic observers comes from the moment at which the binding of the observer to the member is defined. In the case of static observers, this is done at the time of the class definition and hence affects all instances of the class. On the contrary, dynamic observers are bound to a specific instance at a later time. The next two sections will focus on how to manage static and dynamic observers binding, while the following sections will focus on the signature of the handlers and the content of the notification dictionary passed to the handlers in different situations. Static observers ~~~~~~~~~~~~~~~~ Static observers can be bound to a member in three ways: - declaring a method matching the name of the member to observe but whose name starts with ``_observe_`` - using the |observe| decorator on method. The decorator can take an arbitrary number of arguments which allows to tie the same observer to multiple members. In addition, |observe| accept as argument a dot separated name to automatically observe a member of an atom object stored in a member. Note that this mechanism is limited to a single depth (hence a single dot in the name). - finally one can manage manually static observer using the following methods defined on the base class of all members: + |add_static_observer| which takes a callable and an optional flag indicating which change types to emit + |remove_static_observer| which takes a single callable as argument Dynamic observers ~~~~~~~~~~~~~~~~~ Dynamic observers are managed using the |Atom.observe| and |Atom.unobserve| methods of the |Atom| class. To observe one needs to pass the name of the member to observe and the callback function. When unobserving, you can either pass just the member name to remove all observers at once or a name and a callback to remove specific observer. .. note:: Two specific members have an additional way to manage observers: - |Event|: expose the methods |bind| and |unbind| which takes as single argument the callback to bind. - |Signal|: similarly |Signal| exposes |connect| and |disconnect| which match Qt signals. Notification handlers --------------------- Now that we discussed all kind of observers and how to manage them, it is more than time to discuss the expected signatures of callback and what information the callback is passed when called. For observers connected to all members except |Signal|, the callback should accept a single argument which is usually called *change*. This argument is a dictionary with ``str`` as keys which are described below: - ``'type'``: A string describing the event that triggered the notification: + ``'created'``: when accessing or assigning to a member that has no previous value. + ``'update'``: when assigning a new value to a member with a previous value. + ``'delete'``: when deleting a member (using ``del`` or ``delattr``) + ``'container'``: when doing inplace modification on a the of |ContainerList|. - ``'object'``: This is the |Atom| instance that triggered the notification. - ``'name'``: Name of the member from which the notification originate. - ``'value'``: New value of the member (or old value of the member in the case of a delete event). - ``'oldvalue'``: Old value of the member in the case of an update. .. note:: As of 0.8.0 ``observe`` and ``add_static_observer`` also accepts an optional ``ChangeType`` flag which can be used to selectively enable or disable which change ``type`` events are generated. .. code-block:: python from atom.api import Atom, Int, ChangeType class Widget(Atom): count = Int() def on_change(change): print(change["type"]) Widget.count.add_static_observer(on_change, ChangeType.UPDATE | ChangeType.DELETE) w = Widget() w.count # Will not emit a "create" event since it was disabled w.count += 1 # Will trigger an "update" event del w.count # Will trigger a "delete" event .. warning:: If you attach twice the same callback function to a member, the second call will override the change type flag of the observer. In the case of ``'container'`` events emitted by |ContainerList| the change dictionary can contains additional information (note that ``'value'`` and ``'oldvalue'`` are present): - ``'operation'``: a str describing the operation that took place (append, extend, \_\_setitem\_\_, insert, \_\_delitem\_\_, pop, remove, reverse, sort, \_\_imul\_\_, \_\_iadd\_\_) - ``'item'``: the item that was modified if the modification affected a single item. - ``'items'``: the items that were modified if the modification affected multiple items. .. note:: As mentioned previously, |Signal| emits notifications in a different format. When calling (emitting) the signal, it will pass whatever arguments and keyword arguments it was passed as is to the observers as illustrated below. .. code-block:: python class MyAtom(Atom): s = Signal() def print_pair(name, value): print(name, value) a = MyAtom() a.s.connect(print_pair) a.s('a', 1) Suppressing notifications ------------------------- If for any reason you need to prevent notifications to be propagated you can use the |Atom.suppress_notifications| context manager. Inside this context manager, notifications will not be propagated. atom-0.12.1/docs/source/basis/typing.rst000066400000000000000000000055531506756731600201600ustar00rootroot00000000000000.. _basis-typing: Using type annotations ====================== .. include:: ../substitutions.sub .. versionadded:: 0.8.0 Atom objects support both type hints and static type checking in addition to runtime validation. Atom understands standard Python type hints, and uses them to infer the appropriate Atom members. Type hints can also be used to specify default values. Note that one can freely mix standard atom members and type annotations. .. note:: Str-like annotations are not supported. The following example creates a class that performs run-time type checking on all instance attributes, sets appropriate default values, and supports static type checking like any other atom object. .. code-block:: class MyAtom(Atom): s: str = "Hello" lst: list[int] = [1, 2, 3] num: Optional[float] n = Int() The default attribute values for each instance are set to appropriate values. .. code-block:: my_atom = MyAtom() assert my_atom.n == 0 ``typing.Optional`` attributes have a default value of ``None`` if no default is specified. .. code-block:: assert my_atom.num is None Mutable default values for ``list`` and ``dict`` are OK as the default value will be copied for each new instance. .. code-block:: assert my_atom.lst == [1, 2, 3] The following statements will fail static type checking and cause Atom to raise a runtime ``TypeError`` exception. .. code-block:: my_atom.n = "Not an integer" my_atom.s = 5 .. note:: The above class definition is basically translated by atom into: .. code-block:: class MyAtom(Atom): s = Str("Hello") lst = List(Int(), default=[1, 2, 3]) num = Typed(float) n = Int() .. note:: By default, atom will generate runtime checks for the content of list, dict and set but it will not check the content of inner containers. For example for the annotation ``list[list[int]]``, atom will check that the provided list contains list but it will not check that those list contains int. The depth at which containers are validated is controlled by the metaclass keyword argument `type_containers` which default to 1. To fully omit validating the content of containers one can write: .. code-block:: class MyAtom(Atom, type_containers=0): lst: list[int] = [1, 2, 3] Which will be equivalent at runtime to, but allow type checker to validate the content of the list: .. code-block:: class MyAtom(Atom): s = Str("Hello") lst = List(default=[1, 2, 3]) .. versionadded:: 0.12.0 ``Literal`` are now supported and represented using the |Enum| member. However ``Literal`` cannot appear in union since it is not a "real" type. If your union is only composed of ``Literal``, you can use a single ``Literal`` in an equivalent manner. atom-0.12.1/docs/source/conf.py000066400000000000000000000126441506756731600163110ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # # This file does only contain a selection of the most common options. For a # full list see the documentation: # http://www.sphinx-doc.org/en/master/config # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. import os import sys from packaging.version import parse sys.path.insert(0, os.path.abspath(".")) sys.path.insert(0, os.path.abspath("../")) from atom.version import __version__ # -- Project information ----------------------------------------------------- project = "atom" copyright = "2013-2025, Nucleic Development Team" author = "Nucleic Development Team" # The short X.Y version. _v = parse(__version__) version = "{}.{}".format(_v.major, _v.minor) # The full version, including alpha/beta/rc tags. release = __version__ # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "sphinx.ext.napoleon", "sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinx.ext.graphviz", ] graphviz_output_format = "svg" # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = ".rst" # The main toctree document. main_doc = "index" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. pygments_style = None # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ['_static'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # The default sidebars (for documents that don't match any pattern) are # defined by theme itself. Builtin themes are using these templates by # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', # 'searchbox.html']``. # # html_sidebars = {} # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. htmlhelp_basename = "atomdoc" # -- Options for LaTeX output ------------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (main_doc, "atom.tex", "atom Documentation", "Nucleic Development Team", "manual"), ] # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [(main_doc, "atom", "atom Documentation", [author], 1)] # -- Options for Texinfo output ---------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( main_doc, "atom", "atom Documentation", author, "atom", "One line description of project.", "Miscellaneous", ), ] # -- Options for Epub output ------------------------------------------------- # Bibliographic Dublin Core info. epub_title = project # The unique identifier of the text. This can be a ISBN number # or the project homepage. # # epub_identifier = '' # A unique identification for the text. # # epub_uid = '' # A list of files that should not be packed into the epub file. epub_exclude_files = ["search.html"] # -- Extension configuration ------------------------------------------------- atom-0.12.1/docs/source/developer_notes/000077500000000000000000000000001506756731600202005ustar00rootroot00000000000000atom-0.12.1/docs/source/developer_notes/index.rst000066400000000000000000000025401506756731600220420ustar00rootroot00000000000000.. _developer: Developer notes ================ These notes are meant to help developers and contributors with regards to some details of the implementation and coding style of the project. Python codebase --------------- The Python codebase currently targets Python 3.7+. Isort, black, flake8 and mypy are used to ensure a consistent style check the code. The code make use of Numpy style docstring. Atom use build tools compliant with PEP 517. It can be installed using pip and wheels and sdist can be built using https://github.com/pypa/build from the root of the project:: python -m build . Python C++ bindings ------------------- The bindings are hand-written and relies on cppy (https://github.com/nucleic/cppy). Atom tries to use a reasonably modern C API and to support sub-interpreter, this has a couple of consequences: - static variables use is limited to cases that cannot lead to state leakage between multiple sub-interpreters. Note that this is currently not heavily tested and may require some improvements. - all the non exported symbol are enclosed in anonymous namespaces - atom does not use static types and only dynamical types (note that the type slots and related structures are stored in a static variable) - modules use the multi-phases initialization mechanism as defined in PEP 489 -- Multi-phase extension module initialization atom-0.12.1/docs/source/examples/000077500000000000000000000000001506756731600166215ustar00rootroot00000000000000atom-0.12.1/docs/source/examples/ex_coersion.rst000066400000000000000000000011741506756731600216730ustar00rootroot00000000000000.. NOTE: This RST file was generated by `make examples`. Do not edit it directly. See docs/source/examples/example_doc_generator.py .. _ex-coersion: Coersion Example =============================================================================== Demonstration of the basic use of the Coerced member. .. TIP:: To see this example in action, download it from :download:`coersion <../../../examples/api/coersion.py>` and run:: $ python coersion.py Example Atom Code ------------------------------------------------------------------------------- .. literalinclude:: ../../../examples/api/coersion.py :language: python atom-0.12.1/docs/source/examples/ex_composition.rst000066400000000000000000000017271506756731600224210ustar00rootroot00000000000000.. NOTE: This RST file was generated by `make examples`. Do not edit it directly. See docs/source/examples/example_doc_generator.py .. _ex-composition: Composition Example =============================================================================== Demonstrate the use of Compostion of Atom objects. 1. If the class has not been declared, use a ForwardTyped - Note the use of lambda, because "Person" is not yet defined 2. A Typed object can be instantiated three ways: - Provide args, kwargs, or a factory in the definition - Provide a _default_* static constructor - Provide a pre-created object in the constructor .. TIP:: To see this example in action, download it from :download:`composition <../../../examples/api/composition.py>` and run:: $ python composition.py Example Atom Code ------------------------------------------------------------------------------- .. literalinclude:: ../../../examples/api/composition.py :language: python atom-0.12.1/docs/source/examples/ex_containers.rst000066400000000000000000000012031506756731600222100ustar00rootroot00000000000000.. NOTE: This RST file was generated by `make examples`. Do not edit it directly. See docs/source/examples/example_doc_generator.py .. _ex-containers: Containers Example =============================================================================== Demonstration of the member handling containers. .. TIP:: To see this example in action, download it from :download:`containers <../../../examples/api/containers.py>` and run:: $ python containers.py Example Atom Code ------------------------------------------------------------------------------- .. literalinclude:: ../../../examples/api/containers.py :language: python atom-0.12.1/docs/source/examples/ex_default_value.rst000066400000000000000000000015431506756731600226720ustar00rootroot00000000000000.. NOTE: This RST file was generated by `make examples`. Do not edit it directly. See docs/source/examples/example_doc_generator.py .. _ex-default_value: Default Value Example =============================================================================== Demonstrate all the ways to initialize a value 1. Pass the value directly 2. Assign the default value explicitly 3. Provide the value during initialization of the object 4. Provide factory callable that returns a value 5. Use a _default_* static method .. TIP:: To see this example in action, download it from :download:`default_value <../../../examples/api/default_value.py>` and run:: $ python default_value.py Example Atom Code ------------------------------------------------------------------------------- .. literalinclude:: ../../../examples/api/default_value.py :language: python atom-0.12.1/docs/source/examples/ex_employee.rst000066400000000000000000000012031506756731600216620ustar00rootroot00000000000000.. NOTE: This RST file was generated by `make examples`. Do not edit it directly. See docs/source/examples/example_doc_generator.py .. _ex-employee: Employee Example =============================================================================== Simple example of a class hierarchy built on atom. .. TIP:: To see this example in action, download it from :download:`employee <../../../examples/tutorial/employee.py>` and run:: $ python employee.py Example Atom Code ------------------------------------------------------------------------------- .. literalinclude:: ../../../examples/tutorial/employee.py :language: python atom-0.12.1/docs/source/examples/ex_hello_world.rst000066400000000000000000000012231506756731600223570ustar00rootroot00000000000000.. NOTE: This RST file was generated by `make examples`. Do not edit it directly. See docs/source/examples/example_doc_generator.py .. _ex-hello_world: Hello World Example =============================================================================== Hello world example: how to write an atom class. .. TIP:: To see this example in action, download it from :download:`hello_world <../../../examples/tutorial/hello_world.py>` and run:: $ python hello_world.py Example Atom Code ------------------------------------------------------------------------------- .. literalinclude:: ../../../examples/tutorial/hello_world.py :language: python atom-0.12.1/docs/source/examples/ex_inventory.rst000066400000000000000000000012171506756731600221050ustar00rootroot00000000000000.. NOTE: This RST file was generated by `make examples`. Do not edit it directly. See docs/source/examples/example_doc_generator.py .. _ex-inventory: Inventory Example =============================================================================== Example on using type hints to create Atom subclasses. .. TIP:: To see this example in action, download it from :download:`inventory <../../../examples/typehints/inventory.py>` and run:: $ python inventory.py Example Atom Code ------------------------------------------------------------------------------- .. literalinclude:: ../../../examples/typehints/inventory.py :language: python atom-0.12.1/docs/source/examples/ex_metadata.rst000066400000000000000000000012031506756731600216230ustar00rootroot00000000000000.. NOTE: This RST file was generated by `make examples`. Do not edit it directly. See docs/source/examples/example_doc_generator.py .. _ex-metadata: Metadata Example =============================================================================== Example demonstrating the use of metadata to filter members. .. TIP:: To see this example in action, download it from :download:`metadata <../../../examples/api/metadata.py>` and run:: $ python metadata.py Example Atom Code ------------------------------------------------------------------------------- .. literalinclude:: ../../../examples/api/metadata.py :language: python atom-0.12.1/docs/source/examples/ex_numeric.rst000066400000000000000000000011651506756731600215140ustar00rootroot00000000000000.. NOTE: This RST file was generated by `make examples`. Do not edit it directly. See docs/source/examples/example_doc_generator.py .. _ex-numeric: Numeric Example =============================================================================== Demonstration of the member handling numeric values. .. TIP:: To see this example in action, download it from :download:`numeric <../../../examples/api/numeric.py>` and run:: $ python numeric.py Example Atom Code ------------------------------------------------------------------------------- .. literalinclude:: ../../../examples/api/numeric.py :language: python atom-0.12.1/docs/source/examples/ex_observe.rst000066400000000000000000000011721506756731600215150ustar00rootroot00000000000000.. NOTE: This RST file was generated by `make examples`. Do not edit it directly. See docs/source/examples/example_doc_generator.py .. _ex-observe: Observe Example =============================================================================== Demonstration of the use of static and dynamic observers. .. TIP:: To see this example in action, download it from :download:`observe <../../../examples/api/observe.py>` and run:: $ python observe.py Example Atom Code ------------------------------------------------------------------------------- .. literalinclude:: ../../../examples/api/observe.py :language: python atom-0.12.1/docs/source/examples/ex_observe_hints.rst000066400000000000000000000012361506756731600227230ustar00rootroot00000000000000.. NOTE: This RST file was generated by `make examples`. Do not edit it directly. See docs/source/examples/example_doc_generator.py .. _ex-observe_hints: Observe Hints Example =============================================================================== Demonstration of the use of static and dynamic observers. .. TIP:: To see this example in action, download it from :download:`observe_hints <../../../examples/api/observe_hints.py>` and run:: $ python observe_hints.py Example Atom Code ------------------------------------------------------------------------------- .. literalinclude:: ../../../examples/api/observe_hints.py :language: python atom-0.12.1/docs/source/examples/ex_person.rst000066400000000000000000000011621506756731600213550ustar00rootroot00000000000000.. NOTE: This RST file was generated by `make examples`. Do not edit it directly. See docs/source/examples/example_doc_generator.py .. _ex-person: Person Example =============================================================================== Simple class using atom and static observers. .. TIP:: To see this example in action, download it from :download:`person <../../../examples/tutorial/person.py>` and run:: $ python person.py Example Atom Code ------------------------------------------------------------------------------- .. literalinclude:: ../../../examples/tutorial/person.py :language: python atom-0.12.1/docs/source/examples/ex_pickling.rst000066400000000000000000000011741506756731600216520ustar00rootroot00000000000000.. NOTE: This RST file was generated by `make examples`. Do not edit it directly. See docs/source/examples/example_doc_generator.py .. _ex-pickling: Pickling Example =============================================================================== Demonstration of the basic use of the Coerced member. .. TIP:: To see this example in action, download it from :download:`pickling <../../../examples/api/pickling.py>` and run:: $ python pickling.py Example Atom Code ------------------------------------------------------------------------------- .. literalinclude:: ../../../examples/api/pickling.py :language: python atom-0.12.1/docs/source/examples/ex_property.rst000066400000000000000000000011721506756731600217340ustar00rootroot00000000000000.. NOTE: This RST file was generated by `make examples`. Do not edit it directly. See docs/source/examples/example_doc_generator.py .. _ex-property: Property Example =============================================================================== Demonstration of the basics of the Property member. .. TIP:: To see this example in action, download it from :download:`property <../../../examples/api/property.py>` and run:: $ python property.py Example Atom Code ------------------------------------------------------------------------------- .. literalinclude:: ../../../examples/api/property.py :language: python atom-0.12.1/docs/source/examples/ex_range.rst000066400000000000000000000011311506756731600211370ustar00rootroot00000000000000.. NOTE: This RST file was generated by `make examples`. Do not edit it directly. See docs/source/examples/example_doc_generator.py .. _ex-range: Range Example =============================================================================== Demonstration of the ranges members. .. TIP:: To see this example in action, download it from :download:`range <../../../examples/api/range.py>` and run:: $ python range.py Example Atom Code ------------------------------------------------------------------------------- .. literalinclude:: ../../../examples/api/range.py :language: python atom-0.12.1/docs/source/examples/ex_static_type_checking.rst000066400000000000000000000013471506756731600242370ustar00rootroot00000000000000.. NOTE: This RST file was generated by `make examples`. Do not edit it directly. See docs/source/examples/example_doc_generator.py .. _ex-static_type_checking: Static Type Checking Example =============================================================================== Demonstration of the interaction between static and dynamic type validation. .. TIP:: To see this example in action, download it from :download:`static_type_checking <../../../examples/typehints/static_type_checking.py>` and run:: $ python static_type_checking.py Example Atom Code ------------------------------------------------------------------------------- .. literalinclude:: ../../../examples/typehints/static_type_checking.py :language: python atom-0.12.1/docs/source/examples/example_doc_generator.py000066400000000000000000000072511506756731600235260ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2018-2024, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Generate the Example Documentation for the Atom Examples Run as part of the documentation build script. """ import os import re import sys from textwrap import dedent def extract_docstring(script_text): """Extract the docstring from an example Enaml script.""" # The docstring is found between the first two '"""' strings. return script_text.split('"""')[1] def clean_docstring(docstring): """Convert a docstring into ReStructuredText format.""" # Find backquoted identifiers, and double the backquotes to match RST. docstring = re.sub( pattern=r"`(?P[A-Za-z_]+)`", # Backquoted identifiers repl=r"``\g``", # Double backquoted identifiers string=docstring, ) return docstring.rstrip() EXAMPLE_DOC_RST_TEMPLATE = dedent( """ .. NOTE: This RST file was generated by `make examples`. Do not edit it directly. See docs/source/examples/example_doc_generator.py .. _ex-{name}: {title} Example =============================================================================== {docstring_rst} .. TIP:: To see this example in action, download it from :download:`{name} <../../../{path}>` and run:: $ python {name}.py Example Atom Code ------------------------------------------------------------------------------- .. literalinclude:: ../../../{path} :language: python """ ) def generate_example_doc(docs_path, script_path): """Generate an RST for an example file. Parameters ---------- docs_path : str Full path to enaml/docs/source/examples script_path : str Full path to the example file """ script_name = os.path.basename(script_path) script_name = script_name[: script_name.find(".")] print("generating doc for %s" % script_name) script_title = script_name.replace("_", " ").title() rst_path = os.path.join(docs_path, "ex_" + script_name + ".rst") relative_script_path = script_path[script_path.find("examples") :].replace( "\\", "/" ) # Add the script to the Python Path old_python_path = sys.path sys.path = [*sys.path, os.path.dirname(script_path)] # Restore Python path. sys.path = old_python_path with open(os.path.join(script_path)) as fid: script_text = fid.read() docstring = clean_docstring(extract_docstring(script_text)) example_doc_rst = EXAMPLE_DOC_RST_TEMPLATE.format( title=script_title, name=script_name, path=relative_script_path, docstring_rst=docstring, ) with open(rst_path, "wb") as rst_output_file: rst_output_file.write(example_doc_rst.lstrip().encode()) def main(): """Generate documentation for all atom examples.""" docs_path = os.path.dirname(__file__) base_path = "../../../examples" base_path = os.path.realpath(os.path.join(docs_path, base_path)) # Find all the files in the examples directory with a .enaml extension # that contain the pragma '<< autodoc-me >>', and generate .rst files for # them. for dirname, dirnames, filenames in os.walk(base_path): files = [os.path.join(dirname, f) for f in filenames if f.endswith(".py")] for fname in files: generate_example_doc(docs_path, fname) if __name__ == "__main__": main() atom-0.12.1/docs/source/examples/index.rst000066400000000000000000000011051506756731600204570ustar00rootroot00000000000000.. _examples: Atom examples ============= Tutorial Examples ------------------------------------------------------------------------------- .. toctree:: :maxdepth: 1 ex_hello_world ex_inventory ex_person ex_employee API Examples ------------------------------------------------------------------------------- .. toctree:: :maxdepth: 1 ex_observe_hints ex_static_type_checking ex_coersion ex_composition ex_containers ex_default_value ex_metadata ex_numeric ex_observe ex_pickling ex_property ex_range atom-0.12.1/docs/source/index.rst000066400000000000000000000016031506756731600166440ustar00rootroot00000000000000.. atom documentation main file, created by sphinx-quickstart on Mon Nov 19 20:12:11 2018. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to atom's documentation! ================================ Atom is a framework for creating memory efficient Python objects with enhanced features such as dynamic initialization, validation, and change notification for object attributes. It provides the default model binding behaviour for the `Enaml `_ UI framework. .. toctree:: :maxdepth: 2 Getting started Advanced Features Examples Developer notes API Documentation Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` atom-0.12.1/docs/source/substitutions.sub000066400000000000000000000126101506756731600204550ustar00rootroot00000000000000.. This file holds some common substitutions (mainly for referencing code). To use it add the directive include:: substitutions.rst at the beginning of the file. .. ============================================================================ .. Atom substitutions .. ============================================================================ .. |Atom| replace:: :py:class:`~atom.atom.Atom` .. |Atom.observe| replace:: :py:class:`~atom.catom.CAtom.observe` .. |Atom.unobserve| replace:: :py:class:`~atom.catom.CAtom.unobserve` .. |Atom.notify| replace:: :py:meth:`~atom.catom.CAtom.freeze` .. |Atom.freeze| replace:: :py:meth:`~atom.catom.CAtom.freeze` .. |Atom.suppress_notifications| replace:: :py:class:`~atom.atom.Atom.suppress_notifications` .. |clone_if_needed| replace:: :py:func:`~atom.meta.atom_meta.clone_if_needed` .. |add_static_observer| replace:: :py:class:`~atom.catom.Member.add_static_observer` .. |remove_static_observer| replace:: :py:class:`~atom.catom.Member.remove_static_observer` .. |Value| replace:: :py:class:`~atom.scalars.Value` .. |Int| replace:: :py:class:`~atom.scalars.Int` .. |Range| replace:: :py:class:`~atom.scalars.Range` .. |Float| replace:: :py:class:`~atom.scalars.Float` .. |FloatRange| replace:: :py:class:`~atom.scalars.FloatRange` .. |Str| replace:: :py:class:`~atom.scalars.Str` .. |Bytes| replace:: :py:class:`~atom.scalars.Bytes` .. |Enum| replace:: :py:class:`~atom.enum.Enum` .. |Typed| replace:: :py:class:`~atom.typed.Typed` .. |ForwardTyped| replace:: :py:class:`~atom.typed.ForwardTyped` .. |Instance| replace:: :py:class:`~atom.instance.Instance` .. |ForwardInstance| replace:: :py:class:`~atom.instance.ForwardInstance` .. |Subclass| replace:: :py:class:`~atom.subclass.Subclass` .. |ForwardSubclass| replace:: :py:class:`~atom.subclass.ForwardSubclass` .. |Tuple| replace:: :py:class:`~atom.tuple.Tuple` .. |List| replace:: :py:class:`~atom.list.List` .. |ContainerList| replace:: :py:class:`~atom.list.ContainerList` .. |Dict| replace:: :py:class:`~atom.dict.Dict` .. |DefaultDict| replace:: :py:class:`~atom.dict.DefaultDict` .. |Delegator| replace:: :py:class:`~atom.delegator.Delegator` .. |Event| replace:: :py:class:`~atom.event.Event` .. |bind| replace:: :py:meth:`~atom.event.Event.bind` .. |unbind| replace:: :py:meth:`~atom.event.Event.unbind` .. |Signal| replace:: :py:class:`~atom.signal.Signal` .. |connect| replace:: :py:meth:`~atom.signal.Signal.connect` .. |disconnect| replace:: :py:meth:`~atom.signal.Signal.disconnect` .. |Coerced| replace:: :py:class:`~atom.coerced.Coerced` .. |Property| replace:: :py:class:`~atom.property.Property` .. |observe| replace:: :py:class:`~atom.atom.observe` .. |set_default| replace:: :py:class:`~atom.atom.set_default` .. |atomref| replace:: :py:class:`~atom.catom.atomref` .. |sortedmap| replace:: :py:class:`~atom.datastructures.sortedmap.sortedmap` .. |GetAttr| replace:: :py:class:`~atom.catom.GetAttr` .. |SetAttr| replace:: :py:class:`~atom.catom.SetAttr` .. |PostGetAttr| replace:: :py:class:`~atom.catom.PostGetAttr` .. |PostSetAttr| replace:: :py:class:`~atom.catom.PostSetAttr` .. |DefaultValue| replace:: :py:class:`~atom.catom.DefaultValue` .. |Validate| replace:: :py:class:`~atom.catom.Validate` .. |PostValidate| replace:: :py:class:`~atom.catom.PostValidate` .. |GetState| replace:: :py:class:`~atom.catom.GetState` .. |Member| replace:: :py:class:`~atom.catom.Member` .. |Member.notify| replace:: :py:meth:`~atom.catom.Member.notify` .. |Member.do_getattr| replace:: :py:meth:`~atom.catom.Member.do_getattr` .. |Member.do_setattr| replace:: :py:meth:`~atom.catom.Member.do_setattr` .. |Member.do_post_getattr| replace:: :py:meth:`~atom.catom.Member.do_post_getattr` .. |Member.do_post_setattr| replace:: :py:meth:`~atom.catom.Member.do_post_setattr` .. |Member.do_default_value| replace:: :py:meth:`~atom.catom.Member.do_default_value` .. |Member.do_validate| replace:: :py:meth:`~atom.catom.Member.do_validate` .. |Member.do_post_validate| replace:: :py:meth:`~atom.catom.Member.do_post_validate` .. |Member.do_full_validate| replace:: :py:meth:`~atom.catom.Member.do_full_validate` .. |Member.getattr_mode| replace:: :py:meth:`~atom.catom.Member.getattr_mode` .. |Member.setattr_mode| replace:: :py:meth:`~atom.catom.Member.setattr_mode` .. |Member.delattr_mode| replace:: :py:meth:`~atom.catom.Member.delattr_mode` .. |Member.post_getattr_mode| replace:: :py:meth:`~atom.catom.Member.post_getattr_mode` .. |Member.post_setattr_mode| replace:: :py:meth:`~atom.catom.Member.post_setattr_mode` .. |Member.default_value_mode| replace:: :py:meth:`~atom.catom.Member.default_value_mode` .. |Member.validate_mode| replace:: :py:meth:`~atom.catom.Member.validate_mode` .. |Member.post_validate_mode| replace:: :py:meth:`~atom.catom.Member.post_validate_mode` .. |Member.set_getattr_mode| replace:: :py:meth:`~atom.catom.Member.set_getattr_mode` .. |Member.set_setattr_mode| replace:: :py:meth:`~atom.catom.Member.set_setattr_mode` .. |Member.set_post_getattr_mode| replace:: :py:meth:`~atom.catom.Member.set_post_getattr_mode` .. |Member.set_post_setattr_mode| replace:: :py:meth:`~atom.catom.Member.set_post_setattr_mode` .. |Member.set_default_value_mode| replace:: :py:meth:`~atom.catom.Member.set_default_value_mode` .. |Member.set_validate_mode| replace:: :py:meth:`~atom.catom.Member.set_validate_mode` .. |Member.set_post_validate_mode| replace:: :py:meth:`~atom.catom.Member.set_post_validate_mode` atom-0.12.1/examples/000077500000000000000000000000001506756731600143715ustar00rootroot00000000000000atom-0.12.1/examples/api/000077500000000000000000000000001506756731600151425ustar00rootroot00000000000000atom-0.12.1/examples/api/coersion.py000066400000000000000000000020521506756731600173340ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Demonstration of the basic use of the Coerced member.""" from atom.api import Atom, Coerced class Demo(Atom): # The coercer could be omitted but being explicit helps static type checkers cint = Coerced(int, coercer=int) cfloat = Coerced(float, coercer=float) cstr = Coerced(str, coercer=str) if __name__ == "__main__": demo = Demo() print("CInt Demo") demo.cint = "1" print(demo.cint) demo.cint = 51.5 print(demo.cint) print("\nCFloat Demo") demo.cfloat = "1.5" print(demo.cfloat) demo.cfloat = 100 print(demo.cfloat) print("\nCStr Demo") demo.cstr = 100 print(demo.cstr) demo.cstr = Demo print(demo.cstr) atom-0.12.1/examples/api/composition.py000066400000000000000000000046011506756731600200600ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Demonstrate the use of Compostion of Atom objects. 1. If the class has not been declared, use a ForwardTyped - Note the use of lambda, because "Person" is not yet defined 2. A Typed object can be instantiated three ways: - Provide args, kwargs, or a factory in the definition - Provide a _default_* static constructor - Provide a pre-created object in the constructor """ from atom.api import Atom, ForwardTyped, Str, Typed class Dog(Atom): name = Str() # note the use of lambda, because Person has not been defined owner = ForwardTyped(lambda: Person) class Person(Atom): name = Str() # uses static constructor # When using a static constructor the member is considered by default to be # optional even though it is often not the desired behavior, and specifying # optional=False preventing the member to be set to None makes sense. fido = Typed(Dog, optional=False) # uses kwargs provided in the definition # When the member is provided a way to build a default value, it assumes it # is not optional by default, i.e. None is not a valid value. fluffy = Typed(Dog, kwargs={"name": "Fluffy"}) # uses an object provided in Person constructor new_dog = Typed(Dog) def _default_fido(self): return Dog(name="Fido", owner=self) if __name__ == "__main__": bob = Person(name="Bob Smith") print("Fido") print("name: {0}".format(bob.fido.name)) assert bob.fido.owner # owner is optional so check it is set print("owner: {0}".format(bob.fido.owner.name)) print("\nFluffy") print("name: {0}".format(bob.fluffy.name)) print("original owner: {0}".format(repr(bob.fluffy.owner))) # none bob.fluffy.owner = bob print("new owner: {0}".format(bob.fluffy.owner.name)) print("\nNew Dog") new_dog = Dog(name="Scruffy", owner=bob) bob.new_dog = new_dog print("name: {0}".format(bob.new_dog.name)) assert bob.new_dog.owner # owner is optional so check it is set print("owner: {0}".format(bob.new_dog.owner.name)) atom-0.12.1/examples/api/containers.py000066400000000000000000000017371506756731600176710ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Demonstration of the member handling containers.""" from atom.api import Atom, ContainerList, Dict, List, Tuple class Data(Atom): dlist = List(default=[1, 2, 3]) dcont_list = ContainerList(default=[1, 2, 3]) dtuple = Tuple(default=(5, 4, 3)) ddict = Dict(default={"foo": 1, "bar": "a"}) def _observe_dcont_list(self, change): print("container list change: {0}".format(change["value"])) if __name__ == "__main__": data = Data() print(data.dlist) print(data.dcont_list) data.dcont_list.append(1) data.dcont_list.pop(0) print(data.dtuple) print(data.ddict) atom-0.12.1/examples/api/default_value.py000066400000000000000000000024001506756731600203300ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Demonstrate all the ways to initialize a value 1. Pass the value directly 2. Assign the default value explicitly 3. Provide the value during initialization of the object 4. Provide factory callable that returns a value 5. Use a _default_* static method """ import sys from atom.api import Atom, Int, Str def get_mother(): return "Maude " + get_last_name() def get_last_name(): """Return a last name based on the system byteorder.""" return sys.byteorder.capitalize() class Person(Atom): """A simple class representing a person object.""" first_name = Str("Bob") age = Int(default=40) address = Str() mother = Str(factory=get_mother) last_name = Str() def _default_last_name(self): return get_last_name() if __name__ == "__main__": bob = Person(address="101 Main") print((bob.first_name, bob.last_name, bob.age)) print(bob.mother) atom-0.12.1/examples/api/metadata.py000066400000000000000000000020461506756731600172760ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2018-2024, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Example demonstrating the use of metadata to filter members.""" import sys from atom.api import Atom, Int, Str def get_last_name(): """Return a last name based on the system byteorder.""" return sys.byteorder.capitalize() class Person(Atom): """A simple class representing a person object.""" first_name = Str("Bob").tag(pref=True) age = Int(default=40).tag(pref=False) last_name = Str() def _default_last_name(self): return get_last_name() if __name__ == "__main__": bob = Person() for name, member in bob.members().items(): if member.metadata and "pref" in member.metadata: print(name, member.metadata["pref"]) atom-0.12.1/examples/api/numeric.py000066400000000000000000000013401506756731600171540ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Demonstration of the member handling numeric values.""" import sys from atom.api import Atom, Bool, Float, Int class Data(Atom): ival = Int(1) lval = Int(sys.maxsize + 1) fval = Float(1.5e6) bval = Bool(False) if __name__ == "__main__": data = Data() print(data.ival) print(data.lval) print(data.fval) print(data.bval) atom-0.12.1/examples/api/observe.py000066400000000000000000000032041506756731600171600ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Demonstration of the use of static and dynamic observers.""" from atom.api import Atom, ChangeDict, Range, Str, Typed, observe class Dog(Atom): name = Str() class Person(Atom): """A simple class representing a person object.""" name = Str() age = Range(low=0) dog = Typed(Dog, ()) def _observe_age(self, change: ChangeDict) -> None: print("Age changed: {0}".format(change["value"])) @observe("name") def any_name_i_want(self, change: ChangeDict) -> None: print("Name changed: {0}".format(change["value"])) @observe("dog.name") def another_random_name(self, change: ChangeDict) -> None: print("Dog name changed: {0}".format(change["value"])) class Employee(Person): title = Str() @observe("title") def on_title_change(self, change: ChangeDict) -> None: super().any_name_i_want(change) def main(): bob = Person(name="Robert Paulson", age=40) bob.name = "Bobby Paulson" bob.age = 50 bob.dog.name = "Scruffy" def watcher_func(change): print("Watcher func change: {0}".format(change["value"])) bob.observe("age", watcher_func) bob.age = 51 bob.unobserve("age", watcher_func) bob.age = 52 # No call to watcher func if __name__ == "__main__": main() atom-0.12.1/examples/api/observe_hints.py000066400000000000000000000027671506756731600204020ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2022-2024, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Demonstration of the use of static and dynamic observers.""" from typing import Optional from atom.api import Atom, ChangeDict, observe class Dog(Atom): name: str class Person(Atom): """A simple class representing a person object.""" name: str age: int dog: Optional[Dog] def _observe_age(self, change: ChangeDict) -> None: print("Age changed: {0}".format(change["value"])) @observe("name") def any_name_i_want(self, change: ChangeDict) -> None: print("Name changed: {0}".format(change["value"])) @observe("dog.name") def another_random_name(self, change: ChangeDict) -> None: print("Dog name changed: {0}".format(change["value"])) def main() -> None: bob = Person(name="Robert Paulson", age=40) bob.name = "Bobby Paulson" bob.age = 50 bob.dog = Dog(name="Scruffy") def watcher_func(change: ChangeDict) -> None: print("Watcher func change: {0}".format(change["value"])) bob.observe("age", watcher_func) bob.age = 51 bob.unobserve("age", watcher_func) bob.age = 52 # No call to watcher func if __name__ == "__main__": main() atom-0.12.1/examples/api/pickling.py000066400000000000000000000021751506756731600173210ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2023-2024, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Demonstration of the basic use of the Coerced member.""" import pickle from atom.api import Atom, GetState, clone_if_needed class PicklePublicOnly(Atom): def __init_subclass__(cls) -> None: super().__init_subclass__() for k, m in cls.members().items(): if k.startswith("_"): m = clone_if_needed(cls, m) m.set_getstate_mode(GetState.Exclude, None) class Demo(PicklePublicOnly): #: Public member declared using type annotation public: int #: Private member declared using type annotation _private: int d = Demo(public=10, _private=20) print(d.public) # 10 print(d._private) # 20 new_d = pickle.loads(pickle.dumps(d)) print(new_d.public) # 10 print(new_d._private) # 0: was not pickled atom-0.12.1/examples/api/property.py000066400000000000000000000021131506756731600173750ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Demonstration of the basics of the Property member.""" from atom.api import Atom, Int, Property, Str class Person(Atom): """A simple class representing a person object.""" first_name = Str() # Static type checker cannot infer from the magic method that the property # is read/write so a type annotation helps. age: "Property[int, int]" = Property() # type: ignore _age = Int(40) def _get_age(self) -> int: return self._age def _set_age(self, age: int) -> None: if age < 100 and age > 0: self._age = age if __name__ == "__main__": bob = Person(first_name="Bob") print(bob.age) bob.age = -10 print(bob.age) bob.age = 20 print(bob.age) atom-0.12.1/examples/api/range.py000066400000000000000000000017011506756731600166070ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Demonstration of the ranges members.""" from atom.api import Atom, FloatRange, Range class Experiment(Atom): coef = FloatRange(-1.0, 1.0, 0.0) gain = Range(0, 100, 10) scale = FloatRange(0.0, 2.0, 1.0, strict=True) if __name__ == "__main__": exp = Experiment() print(exp.coef) exp.coef = 0.5 print(exp.coef) print(exp.gain) exp.gain = 99 print(exp.gain) print(exp.scale) try: exp.scale = 2 # strict=False prevents assigning int/long except TypeError as e: print(e) exp.scale = 2.0 print(exp.scale) atom-0.12.1/examples/tutorial/000077500000000000000000000000001506756731600162345ustar00rootroot00000000000000atom-0.12.1/examples/tutorial/employee.py000066400000000000000000000043051506756731600204270ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Simple example of a class hierarchy built on atom.""" import datetime from atom.api import ( Atom, Bool, ChangeDict, Int, Range, Str, Tuple, Typed, Value, observe, ) class Person(Atom): """A simple class representing a person object.""" last_name = Str() first_name = Str() age = Range(low=0) dob = Value(datetime.date(1970, 1, 1)) debug = Bool(False) @observe("age") def debug_print(self, change: ChangeDict) -> None: """Prints out a debug message whenever the person's age changes.""" if self.debug: templ = "{first} {last} is {age} years old." s = templ.format(first=self.first_name, last=self.last_name, age=self.age) print(s) class Employer(Person): """An employer is a person who runs a company.""" # The name of the company company_name = Str() class Employee(Person): """An employee is person with a boss and a phone number.""" # The employee's boss boss = Typed(Employer) # The employee's phone number as a tuple of 3 ints phone = Tuple(Int()) # This method will be called automatically by atom when the # employee's phone number changes def _observe_phone(self, val: ChangeDict) -> None: if val["type"] == "update": msg = "received new phone number for %s: %s" print(msg % (self.first_name, val["value"])) if __name__ == "__main__": # Create an employee with a boss boss_john = Employer( first_name="John", last_name="Paw", company_name="Packrat's Cats" ) employee_mary = Employee( first_name="Mary", last_name="Sue", boss=boss_john, phone=(555, 555, 5555) ) employee_mary.phone = (100, 100, 100) employee_mary.age = 40 # no debug message employee_mary.debug = True employee_mary.age = 50 atom-0.12.1/examples/tutorial/hello_world.py000066400000000000000000000011711506756731600211200ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Hello world example: how to write an atom class.""" from atom.api import Atom, Str class Hello(Atom): message = Str("Hello") if __name__ == "__main__": hello = Hello() print(hello.message) hello.message = "Goodbye" print(hello.message) atom-0.12.1/examples/tutorial/person.py000066400000000000000000000021531506756731600201150ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Simple class using atom and static observers.""" from atom.api import Atom, Bool, ChangeDict, Range, Str, observe class Person(Atom): """A simple class representing a person object.""" last_name = Str() first_name = Str() age = Range(low=0) debug = Bool(False) @observe("age") def debug_print(self, change: ChangeDict) -> None: """Prints out a debug message whenever the person's age changes.""" if self.debug: templ = "{first} {last} is {age} years old." s = templ.format(first=self.first_name, last=self.last_name, age=self.age) print(s) if __name__ == "__main__": john = Person(first_name="John", last_name="Doe", age=42) john.debug = True john.age = 43 atom-0.12.1/examples/typehints/000077500000000000000000000000001506756731600164205ustar00rootroot00000000000000atom-0.12.1/examples/typehints/inventory.py000066400000000000000000000023761506756731600210370ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2022-2024, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Example on using type hints to create Atom subclasses.""" from atom.api import Atom, ChangeDict, observe class InventoryItem(Atom): """Class for keeping track of an item in inventory. This example demonstrates creating an Atom using python type hints and using `observe` to detect when an item goes on sale. Expected output: `Save $0.99 now on widgets!` """ name: str unit_price: float quantity_on_hand: int = 0 def total_cost(self) -> float: return self.unit_price * self.quantity_on_hand @observe("unit_price") def _observe_unit_price(self, change: ChangeDict) -> None: savings = change.get("oldvalue", 0) - change["value"] if savings > 0: print(f"Save ${savings} now on {self.name}s!") if __name__ == "__main__": w = InventoryItem(name="widget", unit_price=1.99, quantity_on_hand=10) w.unit_price = 1.00 atom-0.12.1/examples/typehints/static_type_checking.py000066400000000000000000000025231506756731600231570ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2022-2024, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Demonstration of the interaction between static and dynamic type validation.""" from typing import List, Optional from atom.api import Atom, Int class MyAtom(Atom): """Simple atom typing example.""" s: str = "Hello" lst: List[int] = [1, 2, 3] # On Python >= 3.9 list[int] can be used num: Optional[float] n = Int() my_atom = MyAtom() assert my_atom.n == 0 # Optional values have a default value of None if no default is specified assert my_atom.num is None # Mutable default values for lists and dicts are OK assert my_atom.lst == [1, 2, 3] # The following statements will fail static type checking and # Atom will raise runtime TypeError exceptions. # (the type ignore comment allow CI to pass) try: my_atom.n = "Not an integer" # type: ignore except TypeError as e: print(f"Invalid value for member 'n' of {my_atom}.\n", e) try: my_atom.s = 5 # type: ignore except TypeError as e: print(f"Invalid value for member 's' of {my_atom}.\n", e) atom-0.12.1/lint_requirements.txt000066400000000000000000000000111506756731600170550ustar00rootroot00000000000000ruff mypyatom-0.12.1/pyproject.toml000066400000000000000000000065571506756731600155040ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- [project] name = "atom" description = "Memory efficient Python objects" readme = "README.rst" requires-python = ">=3.10" license = "BSD-3-Clause" license-files = ["LICENSE"] authors = [{ name = "The Nucleic Development Team", email = "sccolbert@gmail.com" }] maintainers = [{ name = "Matthieu C. Dartiailh", email = "m.dartiailh@gmail.com" }] classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", "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", "Programming Language :: Python :: Implementation :: CPython", ] dependencies = ["typing_extensions;python_version<'3.11'"] dynamic = ["version"] [project.urls] homepage = "https://github.com/nucleic/atom" documentation = "https://atom.readthedocs.io/en/latest/" repository = "https://github.com/nucleic/atom" changelog = "https://github.com/nucleic/atom/blob/main/releasenotes.rst" [build-system] requires = ["setuptools>=77.0", "wheel", "setuptools_scm[toml]>=3.4.3", "cppy>=1.2.0"] build-backend = "setuptools.build_meta" [tool.setuptools] include-package-data = false package-data = { atom = ["py.typed", "*.pyi"] } [tool.setuptools_scm] write_to = "atom/version.py" write_to_template = """ # -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- # This file is auto-generated by setuptools-scm do NOT edit it. # NOTE: version_info has been removed in 0.12.0. # Use packaging.version.parse(__version__) instead which provides a more robust # API. __version__ = "{version}" """ [tool.ruff] src = ["src"] line-length = 88 [tool.ruff.lint] select = ["C", "E", "F", "W", "I", "C90", "RUF"] extend-ignore = ["E501", "RUF012"] [tool.ruff.lint.isort] combine-as-imports = true known-first-party = ["atom"] [tool.ruff.lint.mccabe] max-complexity = 20 [tool.mypy] follow_imports = "normal" strict_optional = true [tool.pytest.ini_options] minversion = "6.0" [tool.coverage] [tool.coverage.run] branch = true source = ["atom"] [tool.coverage.report] # Regexes for lines to exclude from consideration exclude_lines = [ # Have to re-enable the standard pragma "pragma: no cover", # Don't complain if tests don't hit defensive assertion code: "raise NotImplementedError", "pass", # Don't complain about abstract methods, they aren't run: "@(abc\\.)?abstractmethod", # Don't complain about type checking "if TYPE_CHECKING:", # Don't complain about ellipsis in overload "\\.\\.\\.", ] atom-0.12.1/releasenotes.rst000066400000000000000000000306751506756731600160110ustar00rootroot00000000000000Atom Release Notes ================== 0.12.1 - 02/10/2025 ------------------- Identical to 0.12.0, this release is only to fix a release process issue. 0.12.0 - 02/10/2025 ------------------- - remove version_info attribute from version.py - add support for Python 3.14 PR #219 - support for Final in member annotations, which sets the member setattr mode as read-only PR #249 - support for NewType instances in all places in which a type is accepted PR #218 0.11.0 - 27/10/2024 ------------------- - drop support for Python 3.8 and 3.9 - add support for Python 3.13 - add FixedTuple member to validate inhomogeneous tuple with fixed number of elements PR #211 0.10.5 - 04-07-2024 ------------------- - fix a memory when pickling atom objects PR #213 0.10.4 - 23/01/2024 ------------------- - allow unions in TypeVar bound PR #207 0.10.3 - 04/10/2023 ------------------- - fix a an issue when using add_member to override an existing member PR #204 Once again a huge thanks to @frmdstryr for the report and fix 0.10.2 - 02/10/2023 ------------------- - fix a memory leak caused by Dict, Defaultdict and Set members #202 A huge thanks to @frmdstryr for the report and fix 0.10.1 - 11/09/2023 ------------------- - add support for Python 3.12 PR #200 0.10.0 - 05/05/2023 ------------------- - AtomMeta: create the class only once all members have been customized PR #510 This allows to use ``__init_subclass__`` since previously the ``__atom_members__`` was not set when ``__init_subclass__`` was called - illustrate the use of ``__init_subclass__`` to customize pickling PR #510 - refactor the metaclass to make it easier to reason about PR #510 - add a ``DefaultDict`` member which enforce the value to be a dictionary and use a factory for missing keys. The factory is set at the member level and a normal dict can be provided as value. When possible the factory is deduced from the member used for validation. - infer the use of ``DefaultDict`` from a ``collections.defaultdict`` annotation 0.9.1 - 13/03/2023 ------------------ - fix generation of Value member from union containing object/Any PR #198 - fix setdefault method of atomdict to return the actually stored item PR #197 0.9.0 - 21/02/2023 ------------------ - fix the generation of Subclass from type annotations PR #193 - move getstate and setstate implementation to C and allow to customize the condition under which a member value is pickled. PR #182 Customization is used to only pickle member value whose state can be restored (for example Constant is not pickled). Whether a member is pickled can be customized using a method prefixed with ``_getstate_`` on an Atom subclass. - use the Typed member when a type annotation resolve to a single type whose metaclass does not implement a custom ``__instancecheck__`` PR #185 - generate Tuple member for tuple[] annotations. The content of the tuple is typed check only if it is a 1-tuple or variable-length tuple. PR #184 - fix the resolution order of members in the presence of multiple inheritance with a common base class PR #165 #168 Due to the above changes, adding a member after the class definition requires more work than before. As a consequence a new helper function ``add_member`` has been added. 0.8.2 - 18/10/2022 ------------------ - fix handling of Union in annotation based class definition PR #177 0.8.1 - 13/06/2022 ------------------ - add support for Python 3.11 PR #166 0.8.0 - 30/03/2022 ------------------ This release introduced a new way to declare atom object based on type annotations. - emit warnings when a magic method points to an undefined member PR #139 #149 - use isort, black and flake8 to ensure a consistent coding style PR #141 - base the version number on the most recent git tag using setuptools-scm PR #141 - make the setup compatible with PEP 517 and PEP 621 compliant PR #141 #162 Pip should be used for development install in place of directly calling setup.py - fix handling of _SpecialGenericAlias (typing.Sequence, ...) when used inside an Instance member PR #151 - add a ChangeDict TypedDict to help annotate observers PR #133 - add several keyword argument to the AtomMeta metaclass PR #133 - enable_weakrefs: allow to have weak references to an Atom object without having to add the slot by hand. False by default. - use_annotations: generate members from type annotations. Str-like annotations are not supported but allowed when an actual member is provided as default value. True by default. - type_containers: in conjunction with use_annotations determine to what depth to type the content of a container. The default is 1 meaning that list[int] will use List(Int()) but list[list[int]] will use List(List()). - allow specifying which change events are emitted when adding static observers PR #155 0.7.0 - 21/11/2021 ------------------ This release introduces several minor backward incompatibilities which are detailed below. Those are expected to impact only a small minority of users since they make behaviors more in line with users expectations in most cases or can be easily addressed. - allow to use any subscribed type in Typed and Instance. Optional and Union are analyzed to extract the tuple of types to validate. The presence of NoneType in the tuple will make the member optional. For container types (list, dict, set, etc) the content types is not used beyond static type validation. PR #136 #140 Note however that this usage of Optional and Union breaks static analyzer currently, while things deriving from an actual type will work as expected (List[int], list[int], Iterable[int]) - make the factory argument of Typed, Instance and their forwarded version keyword only. PR #123 - add a keyword-only argument to Typed, Instance and their forwarded version: ``optional``. When set to False, this will cause those members to reject None as a valid value. The default value, None, will resolve to True if there is no provided way to build a default value. PR #123 # 131 This is backward incompatible since previously None was always a valid value. - the Instance and Typed variants of the Validate enum have been renamed to OptionalInstance, OptionalTyped and new Instance and Typed variant describing the validation behavior for the member with optional=False have been added. PR #123 - consistently use Instance to wrap types passed to containers. PR #123 For containers, Instance members used for validation are created with optional=False by default. This is backward incompatible since None was always a valid value previously. - add strict argument to FloatRange. PR #124 - allow to specify the type of ReadOnly and Constant. PR #128 The validation is done using the Instance validator. The change for ReadOnly is backward incompatible since the type or tuple of type is the first argument in place of the default value. Specifying the default value by keyword is both forward and backward compatible. - use python stdlib IntEnum instead of the custom one in atom PR #122 - remove the custom atom.IntEnum PR #122 - add and distribute type hints PR #122 #132 This allows static type checkers to resolve the values behind a member. - drop official support for Python 3.6 and add support for Python 3.10 0.6.0 - 02/11/2020 ------------------ - remove deprecated members Long and Unicode PR #108 - add support for Python 3.9 PR #108 0.5.2 - 04/07/2020 ------------------ - make comparison used in C safe (fix bug introduced in 0.5.0) PR #107 0.5.1 - 03/06/2020 ------------------ - fix bug in using atomlist from C PR #102 - clarify Unicode deprecation warning PR #102 0.5.0 - 26/03/2020 ------------------ - drop Python 2 support PR #83 - use cppy to write the c++ extension PR #83 - add c++ implememtation of AtomDict PR #93 - add a Set member PR #93 0.4.3 - 18/02/2019 ------------------ - improve validation error messages for Instance and Subclass PR #91 - improve validation of validation mode for Instance and Subclass PR #91 0.4.2 - 28/01/2019 ------------------ - ensure cached_property are always read-only #84 - improve test coverage #84 - properly implement traverse and clear for atomlist #84 Closes #69 - allow to initialize sortedmap using a dict or an iterable of pairs #84 - fix sortedmap repr #84 - make sortedmap iterable #84 - fix a segfault in SetAttr.ObjectCall_ObjectNameValue #84 - make the clone method of members more homogeneous for container members #84 - add support for Python 3.7 which introduced FAST_CALL protocol for list methods #81 - proper size check when creating a list #79 - drop dependency of future that was taking a large time to import #78 0.4.1 - 28/01/2018 ------------------ - add a fall-back to type name/pointer comparison on Python 3. Otherwise sortedmap can fail for non homogeneously typed keys. #77 0.4.0 - 11/10/2017 ------------------ - Python 3 support. _c063e523dd9_ .. _c063e523dd9: https://github.com/nucleic/atom/commit/c063e523dd90919b3d22eac5d49c7e4d7d595039 0.3.10 - 10/28/2014 ------------------- - Fix a bug in resolution of Forward* members. _92244cf1e75_ .. _92244cf1e75: https://github.com/nucleic/atom/commit/92244cf1e75fb81cdfeb5cc498d0b89d0f7cea66 0.3.9 - 04/28/2014 ------------------ - Move tests out of main source tree. 0.3.8 - 02/20/2014 ------------------ - Move Property behaviors to C++. - Various maintenance related changes. 0.3.7 - 02/11/2014 ------------------ - Add a Subclass member type. a1261b94251_ - Fix a bug with in multiple member assignment. 65cb312e2d7_ .. _a1261b94251: https://github.com/nucleic/atom/commit/a1261b9425196a50fd9b2642e491f7a0cf4f2397 .. _65cb312e2d7: https://github.com/nucleic/atom/commit/65cb312e2d7417a18baaf2816f84b08e483c40bd 0.3.6 - 01/13/2014 ------------------ - Fix a subtle bug with multiple inheritance. 82aa3c99270_ .. _82aa3c99270: https://github.com/nucleic/atom/commit/82aa3c992705fa7d9ce45cba1f9b43f4af862ca0 0.3.5 - 11/25/2013 ------------------ - Added FloatRange member. ef05758c50e_ - Added 'strict' keyword to Str. 8bda51cfee9_ - Added a 'tag' method to Member for setting metadata. 340adbbf5a9_ - Allow unicode string as observer topics. 441ff55ba73_ - Added a 'strict' keyword to Int. 2ca8b542e8b_ - Added a slew of examples. - Minor bug fixes and cleanup. Special thanks to Steven Silvester (@blink1073) for all of his contributions to this release. .. _ef05758c50e: https://github.com/nucleic/atom/commit/ef05758c50e256074501081dd17d151fd5f906a9 .. _8bda51cfee9: https://github.com/nucleic/atom/commit/8bda51cfee995b32e678dd2cd7bc0b3801e3ad97 .. _340adbbf5a9: https://github.com/nucleic/atom/commit/340adbbf5a9df8913303ab587f45d172254fd862 .. _441ff55ba73: https://github.com/nucleic/atom/commit/441ff55ba739c428b0f6473ed277df961a154761 .. _2ca8b542e8b: https://github.com/nucleic/atom/commit/2ca8b542e8bda067ea1708548cd36281d2941b62 0.3.4 - 10/10/2013 ------------------ - Add more flexibility for unobserving an object. f4ac152ffdf_ .. _f4ac152ffdf: https://github.com/nucleic/atom/commit/f4ac152ffdf11a86b30e61d07caa1f718ff77dee 0.3.3 - 09/20/2013 ------------------ - Add support for static callable observers. bc630777965_ - Add support for single dotted extended observers. f6a33ee4d57_ - Make atomref a singleton per Atom instance. b793dec1336_ .. _bc630777965: https://github.com/nucleic/atom/commit/bc63077796559b81a7565a20fe4d3299d5b5b6b0 .. _f6a33ee4d57: https://github.com/nucleic/atom/commit/f6a33ee4d5797d7b5659ef5007e84941bf9de54a .. _b793dec1336: https://github.com/nucleic/atom/commit/b793dec133608c26ac277d4e959b039371e9569e 0.3.2 - 07/20/2013 ------------------ - Use Atom instance methods as observers without requiring weakrefs. 31df89b7e8_ .. _31df89b7e8: https://github.com/nucleic/atom/commit/31df89b7e8aa64319e83ad6c8b5012bdeec43a09 0.3.1 - 07/03/2013 ------------------ - Bugfix for typed ContainerList. e4f96706ff_ - Make atomlist subclass friendly. 21396e8d1e_ - Add an integer enum class. d2e80e3231_ .. _e4f96706ff: https://github.com/nucleic/atom/commit/e4f96706ff166e107d90376cb88205a51f8db174 .. _21396e8d1e: https://github.com/nucleic/atom/commit/21396e8d1e489556287e12dd9b47434d1589264f .. _d2e80e3231: https://github.com/nucleic/atom/commit/d2e80e323190b698296a1f21a3837e21f40cbd33 0.3.0 - 05/17/2013 ------------------ - First version with release notes. - Add the ability to monitor the lifetime of an Atom object without weakrefs. 7596aa1b48_ - Move the implemenations for List and ContainerList into C++. 7596aa1b48_ - Add support for pickling Atom objects. 7596aa1b48_ .. _7596aa1b48: https://github.com/nucleic/atom/commit/7596aa1b4884f67ab8266c340e9e5d24c0d15f3b atom-0.12.1/setup.py000066400000000000000000000037271506756731600142760ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from setuptools import Extension, setup try: from cppy import CppyBuildExt except ImportError as e: raise RuntimeError( "Missing setup required dependencies: cppy. " "Installing through pip as recommended ensure one never hits this issue." ) from e ext_modules = [ Extension( "atom.catom", [ "atom/src/atomlist.cpp", "atom/src/atomdict.cpp", "atom/src/atomset.cpp", "atom/src/atomref.cpp", "atom/src/catom.cpp", "atom/src/catommodule.cpp", "atom/src/defaultvaluebehavior.cpp", "atom/src/delattrbehavior.cpp", "atom/src/enumtypes.cpp", "atom/src/eventbinder.cpp", "atom/src/getattrbehavior.cpp", "atom/src/getstatebehavior.cpp", "atom/src/member.cpp", "atom/src/memberchange.cpp", "atom/src/methodwrapper.cpp", "atom/src/observerpool.cpp", "atom/src/postgetattrbehavior.cpp", "atom/src/postsetattrbehavior.cpp", "atom/src/postvalidatebehavior.cpp", "atom/src/propertyhelper.cpp", "atom/src/setattrbehavior.cpp", "atom/src/signalconnector.cpp", "atom/src/validatebehavior.cpp", ], include_dirs=["src"], language="c++", ), Extension( "atom.datastructures.sortedmap", ["atom/src/sortedmap.cpp"], include_dirs=["src"], language="c++", ), ] setup( use_scm_version=True, ext_modules=ext_modules, cmdclass={"build_ext": CppyBuildExt}, ) atom-0.12.1/test_requirements.txt000066400000000000000000000000761506756731600171010ustar00rootroot00000000000000pytest pytest-cov pytest-mypy-plugins pytest-benchmark psutil atom-0.12.1/tests/000077500000000000000000000000001506756731600137155ustar00rootroot00000000000000atom-0.12.1/tests/datastructure/000077500000000000000000000000001506756731600166075ustar00rootroot00000000000000atom-0.12.1/tests/datastructure/test_sortedmap.py000066400000000000000000000113121506756731600222140ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Test the sortedmap that acts like an ordered dictionary.""" import gc import pytest from atom.api import Atom, Value, atomref from atom.datastructures.api import sortedmap @pytest.fixture def smap(): """Sortedmap used for testing.""" smap = sortedmap() smap["a"] = 1 smap["b"] = 2 smap["c"] = 3 return smap def test_sortedmap_init(): """Test initializing a sortedmap.""" smap = sortedmap({}) assert smap.items() == [] smap = sortedmap([(1, 2)]) assert smap.items() == [(1, 2)] smap = sortedmap({1: 2}) assert smap.items() == [(1, 2)] with pytest.raises(TypeError): sortedmap(1) with pytest.raises(TypeError) as excinfo: sortedmap([1]) assert "pairs" in excinfo.exconly() def test_traverse(): """Test traversing on deletion.""" class Holder(Atom): smap = Value() h = Holder() smap = sortedmap() # Create a reference cycle h.smap = smap smap[1] = h # Create a weakref to check that the objects involved in teh cycle are # collected ref = atomref(h) del smap, h gc.collect() assert not ref() def test_contains(smap): """Test contains test.""" assert "a" in smap assert 1 not in smap def test_indexing(smap): """Test using indexing for get and set operations.""" assert smap["a"] == 1 smap["a"] = 2 assert smap["a"] == 2 with pytest.raises(KeyError): smap[1] with pytest.raises(KeyError): sortedmap()[1] def test_get(smap): """Test the get method of sortedmap.""" assert smap.get("a") == 1 assert smap.get("d") is None assert smap.get("e", 4) == 4 # Test bad parameters for get with pytest.raises(TypeError): smap.get() with pytest.raises(TypeError): smap.get("r", None, None) def test_pop(smap): """Test the pop method of sortedmap.""" assert smap.pop("b") == 2 assert "b" not in smap assert smap.pop("b", 1) == 1 assert smap.keys() == ["a", "c"] assert smap.pop("d", 1) == 1 with pytest.raises(KeyError): smap.pop("b") # Test bad parameters for pop with pytest.raises(TypeError): smap.pop() with pytest.raises(TypeError): smap.pop(None, None, None) def test_keys_values_items(smap): """Test the keys, values and items.""" assert smap.keys() == ["a", "b", "c"] assert smap.values() == [1, 2, 3] assert smap.items() == [("a", 1), ("b", 2), ("c", 3)] def test_iter(smap): """Test iterating sortedmap.""" keys = smap.keys() for i, k in enumerate(smap): assert keys[i] == k def test_ordering_with_inhomogeneous(smap): """Test the ordering of the map.""" smap["d"] = 4 assert list(smap.keys()) == ["a", "b", "c", "d"] smap["0"] = 4 assert list(smap.keys()) == ["0", "a", "b", "c", "d"] smap[1] = 4 assert list(smap.keys()) == [1, "0", "a", "b", "c", "d"] # Test ordering None, which is smaller than anything s = sortedmap() s[1] = 1 s[None] = 1 assert list(s.keys()) == [None, 1] s = sortedmap() s[None] = 1 s[1] = 1 assert list(s.keys()) == [None, 1] # Test ordering class that cannot be ordered through the usual mean class T: pass t1 = T() t2 = T() oT = T class T: pass u = T() s = sortedmap() s[t1] = 1 s[t2] = 1 assert list(s.keys()) == [t1, t2] if id(t1) < id(t2) else [t2, t1] s[u] = 1 assert next(iter(s.keys())) is u if id(T) < id(oT) else list(s.keys())[-1] is u def test_deleting_keys(smap): """Test deleting items.""" del smap["c"] assert smap.keys() == ["a", "b"] with pytest.raises(KeyError): del smap[1] with pytest.raises(KeyError): del sortedmap()[1] def test_repr(smap): """Test getting the repr of the map.""" assert "sortedmap" in repr(smap) new_smap = eval(repr(smap)) assert new_smap.items() == smap.items() # assert eval(repr(smap)) == smap def test_copying(smap): """Test copying a sortedmap.""" csmap = smap.copy() assert csmap is not smap assert csmap.keys() == smap.keys() assert csmap.values() == smap.values() assert csmap.items() == smap.items() def test_sizeof(smap): """Test comuting the size.""" smap.__sizeof__() def test_clear(smap): """Test clearing a map.""" smap.clear() assert not smap atom-0.12.1/tests/test_atom.py000066400000000000000000000160541506756731600162740ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Test the working of the Atom class and metaclass The handling of specially named method is not tested here as it is exercised in the test dedicated to the associated behaviors. The methods related to member observation are tested in test_observe.py """ import gc import pickle from textwrap import dedent import pytest from atom.api import ( Atom, Int, MissingMemberWarning, Str, Value, add_member, atomref, clone_if_needed, observe, set_default, ) def test_init(): """Test init.""" class A(Atom): val = Int() a = A(val=2) assert a.val == 2 with pytest.raises(TypeError): A(None) # Simply check it does not crash a.__sizeof__() def test_set_default(): """Test changing the default value of a member.""" class Default1(Atom): i = Int() i2 = Int() sd = set_default(1) class Default2(Default1): i = sd i2 = sd # By setting the same default twice we should get a clone assert Default1.i is not Default2.i assert Default1().i == 0 assert Default2().i == 1 with pytest.raises(TypeError): class Check(Atom): a = set_default(1) def test_multi_inheritance(): """Test that multiple inheritance does not break the memory layout.""" class Multi1(Atom): i1 = Int() i2 = Int() class Multi2(Atom): i3 = Int() i4 = Int() # This ensures the conflict will occur (dict lack of ordering can cause # unexpected mismatch) assert Multi1.i1.index == Multi2.i3.index or Multi1.i1.index == Multi2.i4.index assert Multi1.i2.index == Multi2.i3.index or Multi1.i2.index == Multi2.i4.index class Multi(Multi1, Multi2): i4 = Int(12) # Test that conflicts do not mess up overridden members assert Multi().i4 == 12 members = Multi().members() for m in members.values(): for m2 in members.values(): if m is m2: continue assert m.index != m2.index class Mixin(Atom): i5 = Int() i6 = Int() i7 = Int() class MultiNext(Multi, Mixin): i1 = Int() class MultiNext2(MultiNext): i1 = Int() assert sorted(m.index for m in MultiNext2.__atom_members__.values()) == list( range(7) ) def test_member_mro_in_multiple_inheritance(): """Test that we respect the MRO for members.""" class A(Atom): a = Str("a") class B(Atom): b = Str("b") class AB(A, B): pass class A2(A): a = Str("a2") class C(AB, A2): pass # C mro AB -> A2 -> A -> B -> Atom # a is not defined or altered on AB so we expect to get A2 behavior assert C().a == "a2" # When AB alters a we expect to see it class BB(A, B): a = set_default("b") class D(BB, A2): pass assert D().a == "b" def test_add_member(): class A(Atom): pass add_member(A, "a", Int()) class B(A): pass assert "a" in B().members() def test_add_member_overridden_member(): class A(Atom): a = Int() b = Int() def _observe_b(self, change): pass class B(A): c = Int() new = Str() add_member(B, "b", new) r = B(a=1, b="abc") assert r.a == 1 assert r.b == "abc" assert B.a.index == 0 assert B.b.index == 1 assert B.c.index == 2 assert B.b.has_observer("_observe_b") def test_cloning_members(): """Test cloning a member when appropriate. Here we test assigning the same member to two names. Other cases in which cloning is required such as modifying a mode are tested in the tests """ class CloneTest(Atom): a = b = Int() assert CloneTest.a is not CloneTest.b def test_listing_members(): """Test listing the members from an Atom instance.""" class MembersTest(Atom): a = b = c = d = e = Int() assert sorted(MembersTest().members().keys()) == ["a", "b", "c", "d", "e"] def test_getting_members(): """Test accessing members directly.""" class A(Atom): val = Int() assert A().get_member("val") is A.val assert A().get_member("") is None with pytest.raises(TypeError): A().get_member(1) class PicklingTest(Atom): __slots__ = ("d",) a = b = c = Int() # See also test_get_set_state def test_pickling(): """Test pickling an Atom instance.""" pt = PicklingTest() pt.a = 2 pt.b = 3 pt.c = 4 pt.d = 5 pick = pickle.dumps(pt) loaded = pickle.loads(pick) assert isinstance(loaded, PicklingTest) assert loaded.a == 2 assert loaded.b == 3 assert loaded.c == 4 assert loaded.d == 5 def test_freezing(): """Test freezing an Atom instance.""" class FreezingTest(Atom): a = Int() ft = FreezingTest() ft.a = 25 ft.freeze() assert ft.a == 25 with pytest.raises(AttributeError): ft.a = 1 with pytest.raises(AttributeError): del ft.a def test_traverse_atom(): """Test that we can break reference cycles involving Atom object.""" class MyAtom(Atom): val = Value() a = MyAtom() l1 = [] a.val = l1 a.val.append(a) ref = atomref(a) del a, l1 gc.collect() assert not ref() @pytest.mark.parametrize( "method_name", [ "_default_x", "_validate_x", "_post_validate_x", "_post_getattr_", "_post_setattr_", "_getstate_", "_observe_", ], ) def test_warn_on_missing(method_name): src = dedent( f""" from atom.api import Atom class A(Atom): def {method_name}(self): pass """ ) with pytest.warns(MissingMemberWarning): exec(src) def test_warn_on_missing_observe(): src = dedent( """ class A(Atom): @observe("x") def f(self, change): pass """ ) with pytest.warns(MissingMemberWarning): exec(src, globals(), {"Atom": Atom, "observe": observe}) def test_enable_weakref(): class WA(Atom, enable_weakrefs=True): pass assert "__weakref__" in WA.__slots__ def test_init_subclass(): members = {} class A(Atom): def __init_subclass__(cls) -> None: super().__init_subclass__() nonlocal members members = cls.members() class B(A): a = Int() assert members == {"a": B.a} def test_clone_if_needed(): class A(Atom): def __init_subclass__(cls) -> None: for m in cls.members().values(): clone_if_needed(cls, m) class B(A): a = Int() i = Int() class C(B): b = i assert C.a is not B.a assert C.b is i atom-0.12.1/tests/test_atom_from_annotations.py000066400000000000000000000235721506756731600217370ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # ------------------------------------------------------------------------------ """Test defining an atom class using typing annotations.""" import logging from collections import defaultdict from typing import ( Any, Callable as TCallable, ClassVar, DefaultDict as TDefaultDict, Dict as TDict, Final, Iterable, List as TList, Literal, Optional, Set as TSet, Tuple as TTuple, Type, TypeVar, Union, get_args, ) import pytest from atom.api import ( Atom, Bool, Bytes, Callable, DefaultDict, Dict, Enum, FixedTuple, Float, Instance, Int, List, Member, Set, SetAttr, Str, Subclass, Tuple, Typed, Value, ) from atom.atom import set_default def test_ignore_annotations(): class A(Atom, use_annotations=False): a: int assert not hasattr(A, "a") def test_reject_str_annotations(): with pytest.raises(TypeError): class A(Atom, use_annotations=True): a: "int" def test_ignore_class_var(): class A(Atom, use_annotations=True): a: ClassVar[int] = 1 assert not isinstance(A.a, Member) def test_ignore_annotated_member(): class A(Atom, use_annotations=True): a: List[int] = List(default=[1, 2, 3]) assert A().a == [1, 2, 3] def test_ignore_str_annotated_member(): class A(Atom, use_annotations=True): b: "List[int]" = List(default=[1, 2, 3]) assert A().b == [1, 2, 3] def test_ignore_annotated_set_default(): class A(Atom, use_annotations=True): a = Value() class B(A, use_annotations=True): a: Value[int] = set_default(1) assert B().a == 1 def test_ignore_str_annotated_set_default(): class A(Atom, use_annotations=True): a = Value() class B(A, use_annotations=True): a: "Value[int]" = set_default(1) assert B().a == 1 def test_reject_bare_member_annotated_member(): with pytest.raises(ValueError) as e: class A(Atom, use_annotations=True): a: List assert "field 'a' of 'A'" in e.exconly() assert e.value.__cause__ def test_reject_non_member_annotated_member(): with pytest.raises(TypeError): class A(Atom, use_annotations=True): a: TList[int] = List(int, default=[1, 2, 3]) def test_reject_non_member_annotated_set_default(): class A(Atom, use_annotations=True): a = Value() with pytest.raises(TypeError): class B(A, use_annotations=True): a: int = set_default(1) @pytest.mark.parametrize("final", [True, False]) @pytest.mark.parametrize( "annotation, member", [ (bool, Bool), (int, Int), (float, Float), (str, Str), (bytes, Bytes), (Any, Value), (object, Value), (TCallable, Callable), (TCallable[[int], int], Callable), (logging.Logger, Typed), (Iterable, Instance), (Type[int], Subclass), (Type[TypeVar("T")], Subclass), (Literal[1, 2, "a"], Enum), ], ) def test_annotation_use(final, annotation, member): if final: ann = Final[annotation] else: ann = annotation class A(Atom, use_annotations=True): a: ann assert isinstance(A.a, member) if member is Typed: assert A.a.validate_mode[1] == annotation elif member is Instance: assert A.a.validate_mode[1] == (annotation.__origin__,) elif member is Subclass: if isinstance(a_args := get_args(annotation)[0], TypeVar): assert A.a.validate_mode[1] is object else: assert A.a.validate_mode[1] == a_args elif member is Enum: assert A.a.validate_mode[1] == get_args(annotation) else: assert A.a.default_value_mode == member().default_value_mode if final: assert A.a.setattr_mode[0] is SetAttr.ReadOnly @pytest.mark.parametrize("final", [True, False]) @pytest.mark.parametrize( "annotation, member, validate_mode", [ (Atom, Typed, Atom), (Union[int, str], Instance, (int, str)), (int | str, Instance, (int, str)), ], ) def test_union_in_annotation(final, annotation, member, validate_mode): if final: annotation = Final[annotation] class A(Atom, use_annotations=True): a: annotation assert isinstance(A.a, member) assert A.a.validate_mode[1] == validate_mode if final: assert A.a.setattr_mode[0] is SetAttr.ReadOnly @pytest.mark.parametrize("final", [True, False]) @pytest.mark.parametrize( "annotation, member, depth", [ (TList[int], List(), 0), (TList[int], List(Int()), 1), (TList[Literal[1, 2, 3]], List(Enum(1, 2, 3)), 1), (TList[TList[int]], List(List()), 1), (TList[TList[int]], List(List(Int())), -1), (TSet[int], Set(), 0), (TSet[int], Set(Int()), 1), (TDict[int, int], Dict(), 0), (TDict[int, int], Dict(Int(), Int()), 1), (TDefaultDict[int, int], DefaultDict(Int(), Int()), 1), (TTuple[int], Tuple(), 0), (TTuple[int], FixedTuple(Int()), 1), (TTuple[int, ...], Tuple(Int()), 1), (TTuple[int, object], FixedTuple(Int(), Value()), 1), (TTuple[int, Any], FixedTuple(Int(), Value()), 1), (TTuple[int, float], FixedTuple(Int(), Float()), 1), (TTuple[tuple, int], FixedTuple(Tuple(), Int()), 1), ( TTuple[TTuple[int, int], int], FixedTuple(FixedTuple(Int(), Int()), Int()), -1, ), (list[int], List(), 0), (list[int], List(Int()), 1), (list[list[int]], List(List()), 1), (list[list[int]], List(List(Int())), -1), (set[int], Set(), 0), (set[int], Set(Int()), 1), (dict[int, int], Dict(), 0), (dict[int, int], Dict(Int(), Int()), 1), (defaultdict[int, int], DefaultDict(Int(), Int()), 1), (tuple[int], Tuple(), 0), (tuple[int], FixedTuple(Int()), 1), (tuple[int, ...], Tuple(Int()), 1), (tuple[int, float], FixedTuple(Int(), Float()), 1), (tuple[tuple, int], FixedTuple(Tuple(), Int()), 1), ( tuple[tuple[int, int], int], FixedTuple(FixedTuple(Int(), Int()), Int()), -1, ), ], ) def test_annotated_containers_no_default(final, annotation, member, depth): if final: ann = Final[annotation] else: ann = annotation class A(Atom, use_annotations=True, type_containers=depth): a: ann assert isinstance(A.a, type(member)) if depth == 0: if isinstance(member, Dict): assert A.a.validate_mode[1] == (None, None) elif isinstance(member, FixedTuple): assert A.a.items == () else: assert A.a.item is None else: if isinstance(member, Dict): for (k, v), (mk, mv) in zip( A.a.validate_mode[1:], member.validate_mode[1:] ): assert type(k) is type(mk) assert type(v) is type(mv) elif isinstance(member, DefaultDict): for (k, v, f), (mk, mv, mf) in zip( A.a.validate_mode[1:], member.validate_mode[1:] ): assert type(k) is type(mk) assert type(v) is type(mv) assert f(A()) == mf(A()) elif isinstance(member, FixedTuple): for v, mv in zip(A.a.validate_mode[1], member.validate_mode[1]): assert type(v) is type(mv) else: assert type(A.a.item) is type(member.item) if isinstance(member.item, List): assert type(A.a.item.item) is type(member.item.item) if final: assert A.a.setattr_mode[0] is SetAttr.ReadOnly @pytest.mark.parametrize("final", [True, False]) @pytest.mark.parametrize( "annotation, member, default", [ (bool, Bool, True), (int, Int, 1), (float, Float, 1.0), (str, Str, "a"), (bytes, Bytes, b"a"), (Any, Value, 1), (Union[Any, None], Value, None), (object, Value, 2), (TCallable, Callable, lambda x: x), (TTuple[int], FixedTuple, (1,)), (TList, List, [1]), (TSet, Set, {1}), (TDict, Dict, {1: 2}), (TDefaultDict, DefaultDict, defaultdict(int, {1: 2})), (Optional[Iterable], Instance, None), (Type[int], Subclass, int), (tuple[int], FixedTuple, (1,)), (list, List, [1]), (set, Set, {1}), (dict, Dict, {1: 2}), (defaultdict, DefaultDict, defaultdict(int, {1: 2})), (Literal[1, 2, "a"], Enum, 2), ], ) def test_annotations_with_default(final, annotation, member, default): if final: ann = Final[annotation] else: ann = annotation class A(Atom, use_annotations=True): a: ann = default assert isinstance(A.a, member) if member is Subclass: assert A.a.default_value_mode == member(int, default=default).default_value_mode elif member is Enum: assert A.a.default_value_mode[1] == default elif member is not Instance: assert A.a.default_value_mode == member(default=default).default_value_mode if final: assert A.a.setattr_mode[0] is SetAttr.ReadOnly def test_annotations_no_default_for_instance(): with pytest.raises(ValueError): class A(Atom, use_annotations=True): a: Iterable = [] with pytest.raises(ValueError): class B(Atom, use_annotations=True): a: Optional[Iterable] = [] def test_annotations_invalid_default_for_literal(): with pytest.raises(ValueError): class A(Atom, use_annotations=True): a: Literal["a", "b"] = "c" atom-0.12.1/tests/test_atomdefaultdict.py000066400000000000000000000174361506756731600205120ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2018-2024, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Test the typed dictionary.""" from collections import defaultdict import pytest from atom.api import Atom, DefaultDict, Instance, Int, List, atomlist, defaultatomdict @pytest.fixture def default_atom_dict(): """Atom with different Dict members.""" class DictAtom(Atom): untyped = DefaultDict(missing=int) keytyped = DefaultDict(Int(), missing=int) valuetyped = DefaultDict(value=Int()) valuetyped_missing = DefaultDict(value=Int(1), missing=int) fullytyped = DefaultDict(Int(), Int()) fullytyped_missing = DefaultDict(Int(), Int(1), missing=int) untyped_default = DefaultDict(default=defaultdict(int, {1: 1})) untyped_default_missing = DefaultDict(default={1: 1}, missing=int) keytyped_default = DefaultDict(Int(), default=defaultdict(int, {1: 1})) valuetyped_default = DefaultDict(value=Int(), default={1: 1}) valuetyped_default_missing = DefaultDict( value=Int(1), default={1: 1}, missing=int ) fullytyped_default = DefaultDict(Int(), Int(), default={1: 1}) fullytyped_default_missing = DefaultDict( Int(), Int(1), default={1: 1}, missing=int ) return DictAtom() MEMBERS = [ "untyped", "keytyped", "valuetyped", "valuetyped_missing", "fullytyped", "fullytyped_missing", "untyped_default", "keytyped_default", "valuetyped_default", "valuetyped_default_missing", "fullytyped_default", "fullytyped_default_missing", ] @pytest.mark.parametrize("member", MEMBERS) def test_instance(default_atom_dict, member): """Test the repr.""" assert isinstance(getattr(default_atom_dict, member), defaultatomdict) @pytest.mark.parametrize("member", MEMBERS) def test_repr(default_atom_dict, member): """Test the repr.""" m = getattr(default_atom_dict.__class__, member) d = m.default_value_mode[1] if not d or not isinstance(d, defaultdict): d = defaultdict(m.validate_mode[1][2], {i: i**2 for i in range(10)}) setattr(default_atom_dict, member, d) assert repr(getattr(default_atom_dict, member)) == repr(d) @pytest.mark.parametrize("member", MEMBERS) def test_len(default_atom_dict, member): """Test the len.""" d = getattr(default_atom_dict.__class__, member).default_value_mode[1] if not d: d = {i: i**2 for i in range(10)} setattr(default_atom_dict, member, d) assert len(getattr(default_atom_dict, member)) == len(d) @pytest.mark.parametrize("member", MEMBERS) def test_contains(default_atom_dict, member): """Test __contains__.""" d = {i: i**2 for i in range(10)} setattr(default_atom_dict, member, d) assert 5 in getattr(default_atom_dict, member) del getattr(default_atom_dict, member)[5] assert 5 not in getattr(default_atom_dict, member) @pytest.mark.parametrize("member", MEMBERS) def test_keys(default_atom_dict, member): """Test the keys.""" d = getattr(default_atom_dict.__class__, member).default_value_mode[1] if not d: d = {i: i**2 for i in range(10)} setattr(default_atom_dict, member, d) assert getattr(default_atom_dict, member).keys() == d.keys() @pytest.mark.parametrize("member", MEMBERS) def test_copy(default_atom_dict, member): """Test copy.""" d = getattr(default_atom_dict.__class__, member).default_value_mode[1] if not d: d = {i: i**2 for i in range(10)} setattr(default_atom_dict, member, d) assert getattr(default_atom_dict, member).copy() == d def test_bad_missing_callable(): with pytest.raises(ValueError) as exc: class DA(Atom): broken_d = DefaultDict(missing=lambda: int("")) assert "The missing argument expect a callable taking no argument" in exc.exconly() def test_setitem(default_atom_dict): """Test setting items.""" default_atom_dict.untyped[""] = 1 assert default_atom_dict.untyped[""] == 1 default_atom_dict.keytyped[1] = "" assert default_atom_dict.keytyped[1] == "" with pytest.raises(TypeError): default_atom_dict.keytyped[""] = 1 default_atom_dict.valuetyped[1] = 1 assert default_atom_dict.valuetyped[1] == 1 with pytest.raises(TypeError): default_atom_dict.valuetyped[""] = "" default_atom_dict.fullytyped[1] = 1 assert default_atom_dict.fullytyped[1] == 1 with pytest.raises(TypeError): default_atom_dict.fullytyped[""] = 1 with pytest.raises(TypeError): default_atom_dict.fullytyped[1] = "" def test_setdefault(default_atom_dict): """Test using setdefault.""" assert default_atom_dict.untyped.setdefault("", 1) == 1 assert default_atom_dict.untyped.setdefault("", 2) == 1 assert default_atom_dict.untyped[""] == 1 assert default_atom_dict.keytyped.setdefault(1, "") == "" assert default_atom_dict.keytyped[1] == "" with pytest.raises(TypeError): default_atom_dict.keytyped.setdefault("", 1) assert default_atom_dict.valuetyped.setdefault(1, 1) == 1 assert default_atom_dict.valuetyped.setdefault(1, "") == 1 assert default_atom_dict.valuetyped[1] == 1 with pytest.raises(TypeError): default_atom_dict.valuetyped.setdefault(2, "") assert default_atom_dict.fullytyped.setdefault(1, 1) == 1 assert default_atom_dict.fullytyped.setdefault(1, "") == 1 assert default_atom_dict.fullytyped[1] == 1 with pytest.raises(TypeError): default_atom_dict.fullytyped.setdefault("", 1) with pytest.raises(TypeError): default_atom_dict.fullytyped.setdefault(2, "") def test_setdefault_coercion(): class A(Atom): d = DefaultDict(int, List(int)) a = A() content = a.d.setdefault(1, []) assert isinstance(content, atomlist) assert content is a.d[1] def test_update(default_atom_dict): """Test update a dict.""" default_atom_dict.untyped.update({"": 1}) assert default_atom_dict.untyped[""] == 1 default_atom_dict.untyped.update([("1", 1)]) assert default_atom_dict.untyped["1"] == 1 default_atom_dict.keytyped.update({1: 1}) assert default_atom_dict.keytyped[1] == 1 default_atom_dict.keytyped.update([(2, 1)]) assert default_atom_dict.keytyped[1] == 1 with pytest.raises(TypeError): default_atom_dict.keytyped.update({"": 1}) default_atom_dict.valuetyped.update({1: 1}) assert default_atom_dict.valuetyped[1] == 1 default_atom_dict.valuetyped.update([(2, 1)]) assert default_atom_dict.valuetyped[1] == 1 with pytest.raises(TypeError): default_atom_dict.valuetyped.update({"": ""}) default_atom_dict.fullytyped.update({1: 1}) assert default_atom_dict.fullytyped[1] == 1 default_atom_dict.fullytyped.update([(2, 1)]) assert default_atom_dict.fullytyped[1] == 1 with pytest.raises(TypeError): default_atom_dict.fullytyped.update({"": 1}) with pytest.raises(TypeError): default_atom_dict.fullytyped.update({"": ""}) @pytest.mark.parametrize("member_name", MEMBERS) def test_missing(default_atom_dict, member_name): assert getattr(default_atom_dict, member_name)[-1] == 0 def test_missing_with_trivial_instance(): class A(Atom): d = DefaultDict(int, int) assert isinstance(A.d.validate_mode[1][1], Instance) a = A() assert a.d[1] == 0 with pytest.raises(RuntimeError): A().d[1] def test_coerced_missing(): class A(Atom): d = DefaultDict(int, List(int)) a = A() content = a.d[1] assert isinstance(content, atomlist) assert content is a.d[1] atom-0.12.1/tests/test_atomdict.py000066400000000000000000000130731506756731600171360ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2018-2024, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Test the typed dictionary.""" import pytest from atom.api import Atom, Dict, Int, List, atomdict, atomlist @pytest.fixture def atom_dict(): """Atom with different Dict members.""" class DictAtom(Atom): untyped = Dict() keytyped = Dict(Int()) valuetyped = Dict(value=Int()) fullytyped = Dict(Int(), Int()) untyped_default = Dict(default={1: 1}) keytyped_default = Dict(Int(), default={1: 1}) valuetyped_default = Dict(value=Int(), default={1: 1}) fullytyped_default = Dict(Int(), Int(), default={1: 1}) return DictAtom() MEMBERS = [ "untyped", "keytyped", "valuetyped", "fullytyped", "untyped_default", "keytyped_default", "valuetyped_default", "fullytyped_default", ] @pytest.mark.parametrize("member", MEMBERS) def test_instance(atom_dict, member): """Test the repr.""" assert isinstance(getattr(atom_dict, member), atomdict) @pytest.mark.parametrize("member", MEMBERS) def test_repr(atom_dict, member): """Test the repr.""" d = getattr(atom_dict.__class__, member).default_value_mode[1] if not d: d = {i: i**2 for i in range(10)} setattr(atom_dict, member, d) assert repr(getattr(atom_dict, member)) == repr(d) @pytest.mark.parametrize("member", MEMBERS) def test_len(atom_dict, member): """Test the len.""" d = getattr(atom_dict.__class__, member).default_value_mode[1] if not d: d = {i: i**2 for i in range(10)} setattr(atom_dict, member, d) assert len(getattr(atom_dict, member)) == len(d) @pytest.mark.parametrize("member", MEMBERS) def test_contains(atom_dict, member): """Test __contains__.""" d = {i: i**2 for i in range(10)} setattr(atom_dict, member, d) assert 5 in getattr(atom_dict, member) del getattr(atom_dict, member)[5] assert 5 not in getattr(atom_dict, member) @pytest.mark.parametrize("member", MEMBERS) def test_keys(atom_dict, member): """Test the keys.""" d = getattr(atom_dict.__class__, member).default_value_mode[1] if not d: d = {i: i**2 for i in range(10)} setattr(atom_dict, member, d) assert getattr(atom_dict, member).keys() == d.keys() @pytest.mark.parametrize("member", MEMBERS) def test_copy(atom_dict, member): """Test copy.""" d = getattr(atom_dict.__class__, member).default_value_mode[1] if not d: d = {i: i**2 for i in range(10)} setattr(atom_dict, member, d) assert getattr(atom_dict, member).copy() == d def test_setitem(atom_dict): """Test setting items.""" atom_dict.untyped[""] = 1 assert atom_dict.untyped[""] == 1 atom_dict.keytyped[1] = "" assert atom_dict.keytyped[1] == "" with pytest.raises(TypeError): atom_dict.keytyped[""] = 1 atom_dict.valuetyped[1] = 1 assert atom_dict.valuetyped[1] == 1 with pytest.raises(TypeError): atom_dict.valuetyped[""] = "" atom_dict.fullytyped[1] = 1 assert atom_dict.fullytyped[1] == 1 with pytest.raises(TypeError): atom_dict.fullytyped[""] = 1 with pytest.raises(TypeError): atom_dict.fullytyped[1] = "" def test_setdefault(atom_dict): """Test using setdefault.""" assert atom_dict.untyped.setdefault("", 1) == 1 assert atom_dict.untyped.setdefault("", 2) == 1 assert atom_dict.untyped[""] == 1 assert atom_dict.keytyped.setdefault(1, "") == "" assert atom_dict.keytyped[1] == "" with pytest.raises(TypeError): atom_dict.keytyped.setdefault("", 1) assert atom_dict.valuetyped.setdefault(1, 1) == 1 assert atom_dict.valuetyped.setdefault(1, "") == 1 assert atom_dict.valuetyped[1] == 1 with pytest.raises(TypeError): atom_dict.valuetyped.setdefault(2, "") assert atom_dict.fullytyped.setdefault(1, 1) == 1 assert atom_dict.fullytyped.setdefault(1, "") == 1 assert atom_dict.fullytyped[1] == 1 with pytest.raises(TypeError): atom_dict.fullytyped.setdefault("", 1) with pytest.raises(TypeError): atom_dict.fullytyped.setdefault(2, "") def test_setdefault_coercion(): class A(Atom): d = Dict(int, List(int)) a = A() content = a.d.setdefault(1, []) assert isinstance(content, atomlist) assert content is a.d[1] def test_update(atom_dict): """Test update a dict.""" atom_dict.untyped.update({"": 1}) assert atom_dict.untyped[""] == 1 atom_dict.untyped.update([("1", 1)]) assert atom_dict.untyped["1"] == 1 atom_dict.keytyped.update({1: 1}) assert atom_dict.keytyped[1] == 1 atom_dict.keytyped.update([(2, 1)]) assert atom_dict.keytyped[1] == 1 with pytest.raises(TypeError): atom_dict.keytyped.update({"": 1}) atom_dict.valuetyped.update({1: 1}) assert atom_dict.valuetyped[1] == 1 atom_dict.valuetyped.update([(2, 1)]) assert atom_dict.valuetyped[1] == 1 with pytest.raises(TypeError): atom_dict.valuetyped.update({"": ""}) atom_dict.fullytyped.update({1: 1}) assert atom_dict.fullytyped[1] == 1 atom_dict.fullytyped.update([(2, 1)]) assert atom_dict.fullytyped[1] == 1 with pytest.raises(TypeError): atom_dict.fullytyped.update({"": 1}) with pytest.raises(TypeError): atom_dict.fullytyped.update({"": ""}) atom-0.12.1/tests/test_atomlist.py000066400000000000000000000535261506756731600171750ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- import gc from collections import Counter from pickle import dumps, loads import pytest from atom.api import Atom, ContainerList, Int, List, Value, atomclist, atomlist, atomref class StandardModel(Atom): """A model class for testing atomlist behavior.""" #: A standard list with no type checking. untyped = List() #: A standard list of integers. typed = List(Int()) class CyclicStandardModel(StandardModel): """A model class to test the handling of reference cycles.""" typed = List(StandardModel) # type: ignore class ContainerModel(Atom): """A model class for testing atomclist behavior.""" #: A container list with no type checking. untyped = ContainerList() #: A container list of integers. typed = ContainerList(Int()) #: Change dictionary for the last notification change = Value() #: Change dictionary for the last notification from static observer on #: untyped static_untyped_change = Value() #: Change dictionary for the last notification from static observer on #: typed static_typed_change = Value() def _observe_untyped(self, change): self.static_untyped_change = change def _observe_typed(self, change): self.static_typed_change = change def _changed(self, change): self.change = change def get_static_change(self, name): return getattr(self, "static_" + name + "_change") class CyclicContainerModel(ContainerModel): """A model class to test the handling of reference cycles.""" typed = ContainerList(ContainerModel) # type: ignore class Indexer(object): """Stupid object behaving like an index but not being an int.""" def __init__(self, index): self.index = index def __index__(self): return self.index @pytest.mark.parametrize("list_subclass", [atomlist, atomclist]) def test_new_method_of_list_subclasses(list_subclass): """Test directly creating list subclass.""" assert list_subclass(range(10)) == list(range(10)) @pytest.mark.parametrize("model", [CyclicStandardModel, CyclicContainerModel]) @pytest.mark.parametrize("kind", ["untyped", "typed"]) def test_list_traversal(model, kind): """Test traversing atom lists. We also test breaking reference cycles between the list and its inner attributes. """ m = model() l1 = getattr(m, kind) l1.append(m) referents = [] if model is CyclicContainerModel: referents.append(getattr(model, kind)) if kind == "typed": referents.append(getattr(model, kind).item) referents.append(m) referents.append(atomclist if model is CyclicContainerModel else atomlist) assert Counter(gc.get_referents(l1)) == Counter(referents) ref = atomref(m) del m, l1, referents gc.collect() if ref(): print(gc.get_referents(ref())) assert not ref() class ListTestBase(object): """A base class which provides base list tests.""" #: Set this to one of the models defined above in setUp. model = None # -------------------------------------------------------------------------- # Untyped Tests # -------------------------------------------------------------------------- def test_untyped_any_data(self): data = [object(), object, type, 12.0, "wfoo", 9j, 10, {}, [], ()] self.model.untyped = data assert self.model.untyped == data def test_untyped_convert_to_list(self): self.model.untyped = list(range(10)) assert list(self.model.untyped) == list(range(10)) def test_untyped_iterate(self): self.model.untyped = list(range(10)) data = list(self.model.untyped) assert data == list(range(10)) def test_untyped_copy_on_assign(self): data = list(range(10)) self.model.untyped = data assert self.model.untyped == data assert self.model.untyped is not data def test_untyped_append(self): self.model.untyped.append(1) assert self.model.untyped == [1] def test_untyped_extend(self): self.model.untyped.extend(list(range(10))) assert self.model.untyped == list(range(10)) def test_untyped_insert(self): self.model.untyped = list(range(10)) self.model.untyped.insert(0, 19) assert self.model.untyped == [19, *list(range(10))] def test_untyped_remove(self): self.model.untyped = list(range(10)) self.model.untyped.remove(5) data = list(range(10)) data.remove(5) assert self.model.untyped == data def test_untyped_pop(self): self.model.untyped = list(range(10)) self.model.untyped.pop() assert self.model.untyped == list(range(9)) self.model.untyped.pop(0) assert self.model.untyped == list(range(1, 9)) self.model.untyped.pop(-1) assert self.model.untyped == list(range(1, 8)) def test_untyped_index(self): self.model.untyped = list(range(10)) index = self.model.untyped.index(5) assert index == 5 def test_untyped_count(self): self.model.untyped = [1] * 10 count = self.model.untyped.count(1) assert count == 10 def test_untyped_reverse(self): self.model.untyped = list(range(10)) self.model.untyped.reverse() assert self.model.untyped == list(reversed(range(10))) def test_untyped_sort(self): self.model.untyped = [8, 3, 2, 5, 9] self.model.untyped.sort() assert self.model.untyped == [2, 3, 5, 8, 9] self.model.untyped.sort(reverse=True) assert self.model.untyped == [9, 8, 5, 3, 2] def test_untyped_get_item(self): self.model.untyped = list(range(10)) assert self.model.untyped[3] == 3 assert self.model.untyped[Indexer(3)] == 3 assert self.model.untyped[Indexer(-1)] == 9 def test_untyped_get_slice(self): self.model.untyped = list(range(10)) assert self.model.untyped[3:8] == list(range(3, 8)) def test_untyped_get_slice_step(self): self.model.untyped = list(range(10)) assert self.model.untyped[3::2] == list(range(3, 10, 2)) def test_untyped_set_item(self): self.model.untyped = list(range(10)) self.model.untyped[5] = 42 assert self.model.untyped[5] == 42 self.model.untyped[Indexer(5)] = 43 assert self.model.untyped[5] == 43 self.model.untyped[Indexer(-1)] = 41 assert self.model.untyped[Indexer(-1)] == 41 def test_untyped_set_slice(self): self.model.untyped = list(range(5)) self.model.untyped[3:5] = [42, 42] assert self.model.untyped == [0, 1, 2, 42, 42] def test_untyped_set_slice_step(self): self.model.untyped = list(range(5)) self.model.untyped[::2] = [42, 42, 42] assert self.model.untyped == [42, 1, 42, 3, 42] def test_untyped_del_item(self): self.model.untyped = list(range(5)) del self.model.untyped[3] assert self.model.untyped == [0, 1, 2, 4] def test_untyped_del_slice(self): self.model.untyped = list(range(5)) del self.model.untyped[3:] assert self.model.untyped == list(range(3)) def test_untyped_del_slice_step(self): self.model.untyped = list(range(10)) del self.model.untyped[::2] assert self.model.untyped == list(range(1, 10, 2)) def test_untyped_concat(self): self.model.untyped = list(range(10)) self.model.untyped += list(range(5)) assert self.model.untyped == list(range(10)) + list(range(5)) def test_untyped_repeat(self): self.model.untyped = list(range(10)) self.model.untyped *= 3 assert self.model.untyped == list(range(10)) * 3 # -------------------------------------------------------------------------- # Typed Tests # -------------------------------------------------------------------------- def test_typed_convert_to_list(self): self.model.typed = list(range(10)) assert list(self.model.typed) == list(range(10)) def test_typed_iterate(self): self.model.typed = list(range(10)) data = list(self.model.typed) assert data == list(range(10)) def test_typed_copy_on_assign(self): data = list(range(10)) self.model.typed = data assert self.model.typed == data assert self.model.typed is not data def test_typed_append(self): self.model.typed.append(1) assert self.model.typed == [1] def test_typed_extend(self): self.model.typed.extend(list(range(10))) assert self.model.typed == list(range(10)) def test_typed_insert(self): self.model.typed = list(range(10)) self.model.typed.insert(0, 19) assert self.model.typed == [19, *list(range(10))] def test_typed_remove(self): self.model.typed = list(range(10)) self.model.typed.remove(5) data = list(range(10)) data.remove(5) assert self.model.typed == data def test_typed_pop(self): self.model.typed = list(range(10)) self.model.typed.pop() assert self.model.typed == list(range(9)) self.model.typed.pop(0) assert self.model.typed == list(range(1, 9)) self.model.typed.pop(-1) assert self.model.typed == list(range(1, 8)) def test_typed_index(self): self.model.typed = list(range(10)) index = self.model.typed.index(5) assert index == 5 def test_typed_count(self): self.model.typed = [1] * 10 count = self.model.typed.count(1) assert count == 10 def test_typed_reverse(self): self.model.typed = list(range(10)) self.model.typed.reverse() assert self.model.typed == list(reversed(range(10))) def test_typed_sort(self): self.model.typed = [8, 3, 2, 5, 9] self.model.typed.sort() assert self.model.typed == [2, 3, 5, 8, 9] self.model.typed.sort(reverse=True) assert self.model.typed == [9, 8, 5, 3, 2] def test_typed_get_item(self): self.model.typed = list(range(10)) assert self.model.typed[3] == 3 assert self.model.typed[Indexer(3)] == 3 assert self.model.typed[Indexer(-1)] == 9 def test_typed_get_slice(self): self.model.typed = list(range(10)) assert self.model.typed[3:8] == list(range(3, 8)) def test_typed_get_slice_step(self): self.model.typed = list(range(10)) assert self.model.typed[3::2] == list(range(3, 10, 2)) def test_typed_set_item(self): self.model.typed = list(range(10)) self.model.typed[5] = 42 assert self.model.typed[5] == 42 self.model.typed[Indexer(5)] = 43 assert self.model.typed[5] == 43 self.model.typed[Indexer(-1)] = 41 assert self.model.typed[Indexer(-1)] == 41 def test_typed_set_slice(self): self.model.typed = list(range(5)) self.model.typed[3:5] = [42, 42] assert self.model.typed == [0, 1, 2, 42, 42] def test_typed_set_slice_step(self): self.model.typed = list(range(5)) self.model.typed[::2] = [42, 42, 42] assert self.model.typed == [42, 1, 42, 3, 42] def test_typed_del_item(self): self.model.typed = list(range(5)) del self.model.typed[3] assert self.model.typed == [0, 1, 2, 4] def test_typed_del_slice(self): self.model.typed = list(range(5)) del self.model.typed[3:] assert self.model.typed == list(range(3)) def test_typed_del_slice_step(self): self.model.typed = list(range(10)) del self.model.typed[::2] assert self.model.typed == list(range(1, 10, 2)) def test_typed_concat(self): self.model.typed = list(range(10)) self.model.typed += list(range(5)) assert self.model.typed == list(range(10)) + list(range(5)) def test_typed_repeat(self): self.model.typed = list(range(10)) self.model.typed *= 3 assert self.model.typed == list(range(10)) * 3 class TestStandardList(ListTestBase): """A test class for the List member.""" def setup_method(self): self.model = StandardModel() def teardown_method(self): self.model = None def test_list_types(self): assert type(self.model.untyped) is atomlist assert type(self.model.typed) is atomlist def test_pickle(self): data = list(range(10)) self.model.untyped = data self.model.typed = data assert data == loads(dumps(self.model.untyped, 0)) assert data == loads(dumps(self.model.untyped, 1)) assert data == loads(dumps(self.model.untyped, 2)) assert data == loads(dumps(self.model.typed, 0)) assert data == loads(dumps(self.model.typed, 1)) assert data == loads(dumps(self.model.typed, 2)) def test_typed_bad_append(self): with pytest.raises(TypeError): self.model.typed.append(1.0) def test_typed_bad_extend(self): with pytest.raises(TypeError): self.model.typed.extend([1, 2, 3, "four"]) def test_typed_bad_insert(self): self.model.typed = list(range(10)) with pytest.raises(TypeError): self.model.typed.insert(0, object()) def test_typed_bad_set_item(self): self.model.typed = list(range(10)) with pytest.raises(TypeError): self.model.typed[5] = 42j def test_typed_bad_set_slice(self): self.model.typed = list(range(5)) with pytest.raises(TypeError): self.model.typed[3:5] = ["None", "None"] def test_typed_bad_set_slice_step(self): self.model.typed = list(range(5)) with pytest.raises(TypeError): self.model.typed[::2] = [56.7, 56.7, 56.7] def test_typed_bad_concat(self): self.model.typed = list(range(10)) with pytest.raises(TypeError): self.model.typed += [12, 14, "bad"] class TestContainerList(TestStandardList): """A test class for the ContainerList.""" def setup_method(self): self.model = ContainerModel() def teardown_method(self): self.model = None def test_list_types(self): assert type(self.model.untyped) is atomclist assert type(self.model.typed) is atomclist @pytest.fixture def container_model(): """Create the typed model and setup the observers.""" model = ContainerModel() model.untyped = list(range(10)) model.typed = list(range(10)) model.observe("untyped", model._changed) model.observe("typed", model._changed) yield model model.unobserve("untyped", model._changed) model.unobserve("typed", model._changed) def verify_base_change(model, name): for change in (model.change, model.get_static_change(name)): assert change["type"] == "container" assert change["name"] == name assert change["object"] == model assert change["value"] == getattr(model, name) @pytest.mark.parametrize("kind", ("untyped", "typed")) def test_container_append(container_model, kind): mlist = getattr(container_model, kind) mlist.append(1) verify_base_change(container_model, kind) for change in (container_model.change, container_model.get_static_change(kind)): assert change["operation"] == "append" assert change["item"] == 1 @pytest.mark.parametrize("kind", ("untyped", "typed")) def test_container_insert(container_model, kind): mlist = getattr(container_model, kind) mlist.insert(0, 42) verify_base_change(container_model, kind) for change in (container_model.change, container_model.get_static_change(kind)): assert change["operation"] == "insert" assert change["index"] == 0 assert change["item"] == 42 @pytest.mark.parametrize("kind", ("untyped", "typed")) def test_container_extend(container_model, kind): mlist = getattr(container_model, kind) mlist.extend(list(range(3))) verify_base_change(container_model, kind) for change in (container_model.change, container_model.get_static_change(kind)): assert change["operation"] == "extend" assert change["items"] == list(range(3)) @pytest.mark.parametrize("kind", ("untyped", "typed")) def test_container_remove(container_model, kind): mlist = getattr(container_model, kind) mlist.remove(5) verify_base_change(container_model, kind) for change in (container_model.change, container_model.get_static_change(kind)): assert change["operation"] == "remove" assert change["item"] == 5 @pytest.mark.parametrize("kind", ("untyped", "typed")) def test_container_pop(container_model, kind): mlist = getattr(container_model, kind) mlist.pop(0) verify_base_change(container_model, kind) for change in (container_model.change, container_model.get_static_change(kind)): assert change["operation"] == "pop" assert change["index"] == 0 assert change["item"] == 0 @pytest.mark.parametrize("kind", ("untyped", "typed")) def test_container_reverse(container_model, kind): mlist = getattr(container_model, kind) mlist.reverse() verify_base_change(container_model, kind) for change in (container_model.change, container_model.get_static_change(kind)): assert change["operation"] == "reverse" @pytest.mark.parametrize("kind", ("untyped", "typed")) def test_container_sort(container_model, kind): mlist = getattr(container_model, kind) mlist.sort() verify_base_change(container_model, kind) for change in (container_model.change, container_model.get_static_change(kind)): assert change["operation"] == "sort" assert change["key"] is None assert change["reverse"] is False @pytest.mark.parametrize("kind", ("untyped", "typed")) def test_container_key_sort(container_model, kind): mlist = getattr(container_model, kind) def key(i): return i mlist.sort(key=key, reverse=True) verify_base_change(container_model, kind) for change in (container_model.change, container_model.get_static_change(kind)): assert change["operation"] == "sort" assert change["key"] == key assert change["reverse"] is True @pytest.mark.parametrize("kind", ("untyped", "typed")) def test_container_set_item(container_model, kind): mlist = getattr(container_model, kind) mlist[0] = 42 verify_base_change(container_model, kind) for change in (container_model.change, container_model.get_static_change(kind)): assert change["operation"] == "__setitem__" assert change["index"] == 0 assert change["olditem"] == 0 assert change["newitem"] == 42 @pytest.mark.parametrize("kind", ("untyped", "typed")) def test_container_set_slice(container_model, kind): mlist = getattr(container_model, kind) mlist[3:5] = [1, 2, 3] verify_base_change(container_model, kind) for change in (container_model.change, container_model.get_static_change(kind)): assert change["operation"] == "__setitem__" assert change["index"] == slice(3, 5, None) assert change["olditem"] == [3, 4] assert change["newitem"] == [1, 2, 3] @pytest.mark.parametrize("kind", ("untyped", "typed")) def test_container_set_slice_step(container_model, kind): mlist = getattr(container_model, kind) mlist[::2] = [1, 2, 3, 4, 5] verify_base_change(container_model, kind) for change in (container_model.change, container_model.get_static_change(kind)): assert change["operation"] == "__setitem__" assert change["index"] == slice(None, None, 2) assert change["olditem"] == [0, 2, 4, 6, 8] assert change["newitem"] == [1, 2, 3, 4, 5] @pytest.mark.parametrize("kind", ("untyped", "typed")) def test_container_del_item(container_model, kind): mlist = getattr(container_model, kind) del mlist[0] verify_base_change(container_model, kind) for change in (container_model.change, container_model.get_static_change(kind)): assert change["operation"] == "__delitem__" assert change["index"] == 0 assert change["item"] == 0 @pytest.mark.parametrize("kind", ("untyped", "typed")) def test_container_del_slice(container_model, kind): mlist = getattr(container_model, kind) del mlist[0:5] verify_base_change(container_model, kind) for change in (container_model.change, container_model.get_static_change(kind)): assert change["operation"] == "__delitem__" assert change["index"] == slice(0, 5, None) assert change["item"] == list(range(5)) @pytest.mark.parametrize("kind", ("untyped", "typed")) def test_container_del_slice_step(container_model, kind): mlist = getattr(container_model, kind) del mlist[::2] verify_base_change(container_model, kind) for change in (container_model.change, container_model.get_static_change(kind)): assert change["operation"] == "__delitem__" assert change["index"] == slice(None, None, 2) assert change["item"] == list(range(10))[::2] @pytest.mark.parametrize("kind", ("untyped", "typed")) def test_container_concat(container_model, kind): mlist = getattr(container_model, kind) mlist += [1, 2, 3] verify_base_change(container_model, kind) for change in (container_model.change, container_model.get_static_change(kind)): assert change["operation"] == "__iadd__" assert change["items"] == [1, 2, 3] @pytest.mark.parametrize("kind", ("untyped", "typed")) def test_container_repeat(container_model, kind): mlist = getattr(container_model, kind) mlist *= 2 verify_base_change(container_model, kind) for change in (container_model.change, container_model.get_static_change(kind)): assert change["operation"] == "__imul__" assert change["count"] == 2 atom-0.12.1/tests/test_atomref.py000066400000000000000000000016521506756731600167670ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2018-2024, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- import gc import pytest from atom.api import Atom, atomref def test_live_atomref(): """Test a live atomref.""" atom = Atom() ref = atomref(atom) assert ref is atomref(atom) assert ref and ref() is atom assert "AtomRef" in repr(ref) ref.__sizeof__() with pytest.raises(TypeError): atomref(object()) def test_dead_atomref(): """Test a dead atomref.""" atom = Atom() ref = atomref(atom) del atom gc.collect() assert not ref and ref() is None assert "AtomRef" in repr(ref) ref.__sizeof__() atom-0.12.1/tests/test_atomset.py000066400000000000000000000100011506756731600167720ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2019-2024, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Test the typed dictionary.""" from operator import iand, ior, isub, ixor import pytest from atom.api import Atom, Int, Set, atomset @pytest.fixture def atom_set(): """Atom with different Set members.""" class SetAtom(Atom): untyped = Set() typed = Set(Int()) untyped_default = Set(default={1}) typed_default = Set(Int(), default={1}) return SetAtom() MEMBERS = ["untyped", "typed", "untyped_default", "typed_default"] @pytest.mark.parametrize("member", MEMBERS) def test_instance(atom_set, member): """Test the type of the returned .""" assert isinstance(getattr(atom_set, member), atomset) @pytest.mark.parametrize("member", MEMBERS) def test_repr(atom_set, member): """Test the repr.""" s = getattr(atom_set.__class__, member).default_value_mode[1] if not s: s = set(range(10)) setattr(atom_set, member, s) assert repr(s) in repr(getattr(atom_set, member)) @pytest.mark.parametrize("member", MEMBERS) def test_len(atom_set, member): """Test the len.""" s = getattr(atom_set.__class__, member).default_value_mode[1] if not s: s = set(range(10)) setattr(atom_set, member, s) assert len(getattr(atom_set, member)) == len(s) @pytest.mark.parametrize("member", MEMBERS) def test_contains(atom_set, member): """Test __contains__.""" s = set(range(10)) setattr(atom_set, member, s) assert 5 in getattr(atom_set, member) @pytest.mark.parametrize("member", MEMBERS) def test_add(atom_set, member): """Test adding an element to a set.""" s = getattr(atom_set.__class__, member).default_value_mode[1] or set() a_set = getattr(atom_set, member) s.add(2) a_set.add(2) assert a_set == s if member.startswith("typed"): with pytest.raises(TypeError): a_set.add("") @pytest.mark.parametrize( "member, op, valid, result, invalid", [ ("untyped", "update", {1, 3}, {1, 2, 3}, None), ("typed", "update", {1, 3}, {1, 2, 3}, {""}), ("untyped", "difference_update", {1, 3}, {2}, None), ("typed", "difference_update", {1, 3}, {2}, {""}), ("untyped", "intersection_update", {1, 3}, {1}, None), ("typed", "intersection_update", {1, 3}, {1}, {""}), ("untyped", "symmetric_difference_update", {1, 3}, {2, 3}, None), ("typed", "symmetric_difference_update", {1, 3}, {2, 3}, {""}), ], ) def test_update_methods(atom_set, member, op, valid, result, invalid): """Test the different update method of a set.""" setattr(atom_set, member, {1, 2}) getattr(getattr(atom_set, member), op)(valid) assert getattr(atom_set, member) == result if invalid is not None: with pytest.raises(TypeError): getattr(getattr(atom_set, member), op)(invalid) @pytest.mark.parametrize( "member, op, valid, result, invalid", [ ("untyped", ior, {1, 3}, {1, 2, 3}, None), ("typed", ior, {1, 3}, {1, 2, 3}, {""}), ("untyped", isub, {1, 3}, {2}, None), ("typed", isub, {1, 3}, {2}, {""}), ("untyped", iand, {1, 3}, {1}, None), ("typed", iand, {1, 3}, {1}, {""}), ("untyped", ixor, {1, 3}, {2, 3}, None), ("typed", ixor, {1, 3}, {2, 3}, {""}), ], ) def test_operations(atom_set, member, op, valid, result, invalid): """Test the different update method of a set.""" a = {1, 2} op(a, valid) setattr(atom_set, member, {1, 2}) print(a, getattr(atom_set, member), valid) op(getattr(atom_set, member), valid) assert getattr(atom_set, member) == result if invalid is not None: with pytest.raises(TypeError): op(getattr(atom_set, member), invalid) atom-0.12.1/tests/test_default_values.py000066400000000000000000000223341506756731600203350ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Tests for member validation handlers: no_op_handler: unused as far as I can tell static_handler list_handler dict_handler delegate_handler: not tested here call_object_handler: used for factory function or Typed/Instance with args call_object_object_handler: advanced used case not used internally call_object_object_name_handler: advanced used case not used internally object_method_handler object_method_name_handler: advanced used case not used internally member_method_object_handler """ import pytest from atom.api import ( Atom, Coerced, DefaultValue, Dict, FloatRange, ForwardInstance, ForwardSubclass, ForwardTyped, Instance, List, Member, Range, Set, Subclass, Typed, Value, ) def test_no_op_handler(): """Test the NoOp handler.""" class A(Atom): v = Member() assert A.v.default_value_mode[0] == DefaultValue.NoOp assert A().v is None @pytest.mark.parametrize( "member, expected", [ (Value(1), 1), (Range(0), 0), (Range(high=0), 0), (Range(0, value=1), 1), (FloatRange(0.0), 0.0), (FloatRange(high=0.0), 0.0), (FloatRange(0.0, value=1.0), 1.0), (Subclass(float), float), (ForwardSubclass(lambda: float), float), ], ) def test_static_handler(member, expected): """Test a static handler.""" class StaticTest(Atom): v = member mode = ( DefaultValue.MemberMethod_Object if isinstance(member, ForwardSubclass) else DefaultValue.Static ) assert StaticTest.v.default_value_mode[0] == mode assert StaticTest().v == expected assert StaticTest.v.default_value_mode[0] == DefaultValue.Static @pytest.mark.parametrize( "member, expect_error", [ (Typed(int), False), (Typed(int, (), optional=False), False), (Typed(int, factory=lambda: 1, optional=False), False), (Instance(int), False), (Instance(int, (), optional=False), False), (Instance(int, factory=lambda: 1, optional=False), False), (Instance(int, optional=False), True), (ForwardTyped(lambda: int), False), (ForwardTyped(lambda: int, (), optional=False), False), (ForwardTyped(lambda: int, factory=lambda: 1, optional=False), False), (ForwardTyped(lambda: int, optional=False), True), (ForwardInstance(lambda: int), False), (ForwardInstance(lambda: int, (), optional=False), False), (ForwardInstance(lambda: int, factory=lambda: 1, optional=False), False), (ForwardInstance(lambda: int, optional=False), True), ], ) def test_non_optional_handler(member, expect_error): """Test a static handler.""" class NonOptionalTest(Atom): v = member if expect_error: assert NonOptionalTest.v.default_value_mode[0] == DefaultValue.NonOptional with pytest.raises(ValueError) as e: NonOptionalTest().v assert "is not optional but no default value" in str(e) else: NonOptionalTest().v if not expect_error: assert NonOptionalTest.v.default_value_mode[0] != DefaultValue.NonOptional def test_list_handler(): """Test that the list handler properly copies the default value.""" class ListTest(Atom): no_default = List() default = List(default=["a"]) assert ListTest.no_default.default_value_mode[0] == DefaultValue.List assert ListTest().no_default == [] assert ListTest.default.default_value_mode[0] == DefaultValue.List default_value = ListTest.default.default_value_mode[1] assert ListTest().default == default_value assert ListTest().default is not default_value def test_dict_handler(): """Test that the dict handler properly copies the default value.""" class DictTest(Atom): no_default = Dict() default = Dict(default={"a": 1}) assert DictTest.no_default.default_value_mode[0] == DefaultValue.Dict assert DictTest().no_default == {} assert DictTest.default.default_value_mode[0] == DefaultValue.Dict default_value = DictTest.default.default_value_mode[1] assert DictTest().default == default_value assert DictTest().default is not default_value def test_set_handler(): """Test that the set handler properly copies the default value.""" class SetTest(Atom): no_default = Set() default = Set(default={"a"}) assert SetTest.no_default.default_value_mode[0] == DefaultValue.Set assert SetTest().no_default == set() assert SetTest.default.default_value_mode[0] == DefaultValue.Set default_value = SetTest.default.default_value_mode[1] assert SetTest().default == default_value assert SetTest().default is not default_value @pytest.mark.parametrize( "member, expected, mode", [ (Typed(int, ("101",), {"base": 2}), 5, DefaultValue.CallObject), (Typed(int, factory=lambda: 5), 5, DefaultValue.CallObject), ( ForwardTyped(lambda: int, ("101",), {"base": 2}), 5, DefaultValue.MemberMethod_Object, ), (ForwardTyped(lambda: int, factory=lambda: (5)), 5, DefaultValue.CallObject), (Instance(int, ("101",), {"base": 2}), 5, DefaultValue.CallObject), (Instance(int, factory=lambda: 5), 5, DefaultValue.CallObject), ( ForwardInstance(lambda: int, ("101",), {"base": 2}), 5, DefaultValue.MemberMethod_Object, ), ( ForwardInstance(lambda: int, factory=lambda: 5), 5, DefaultValue.CallObject, ), (Value(factory=lambda: 5), 5, DefaultValue.CallObject), (Coerced((int, type(None)), coercer=int), None, DefaultValue.CallObject), (Coerced(int, ()), 0, DefaultValue.CallObject), (Coerced(int, factory=lambda: 5), 5, DefaultValue.CallObject), ], ) def test_callobject_handler(member, expected, mode): """Test calling factory handler.""" class CallTest(Atom): m = member assert CallTest.m.default_value_mode[0] == mode assert CallTest().m == expected # Called twice to call the resolved version of the default for forward members assert CallTest().m == expected assert CallTest.m.default_value_mode[0] == DefaultValue.CallObject def test_callobject_object_handler(): """Test the CallObject_Object mode.""" mode = DefaultValue.CallObject_Object member = Value() member.set_default_value_mode(mode, lambda obj: id(obj)) class A(Atom): m = member a1 = A() a2 = A() assert id(a1) == a1.m assert id(a2) == a2.m assert a1.m != a2.m def test_callobject_object_name_handler(): """Test the CallObject_ObjectName mode.""" mode = DefaultValue.CallObject_ObjectName member = Value() member.set_default_value_mode(mode, lambda obj, name: (id(obj), name)) class A(Atom): m = member a1 = A() a2 = A() assert id(a1), "m" == a1.m assert id(a2), "m" == a2.m assert a1.m != a2.m def test_object_method_member_handler(): """Test calling a method on the Atom object.""" class DefaultMethodTest(Atom): v = Value() def _default_v(self): return 5 assert DefaultMethodTest.v.default_value_mode[0] == DefaultValue.ObjectMethod assert DefaultMethodTest().v == 5 def test_member_method_object_handler(): """Test subclassing a Member.""" SENTINEL = object() class DefaultValueMember(Value): def __init__(self): super(DefaultValueMember, self).__init__() mode = DefaultValue.MemberMethod_Object self.set_default_value_mode(mode, "default_value") def default_value(self, atom): return SENTINEL class MemberTest(Atom): m = DefaultValueMember() assert MemberTest.m.default_value_mode[0] == DefaultValue.MemberMethod_Object assert MemberTest().m is SENTINEL assert MemberTest.m.do_default_value(MemberTest()) is SENTINEL def test_object_method_name_handler(): """Test object""" mode = DefaultValue.ObjectMethod_Name member = Value() member.set_default_value_mode(mode, "custom_default") class DefaultMethodTest(Atom): v = member def custom_default(self, name): return 5, name assert DefaultMethodTest().v == (5, "v") @pytest.mark.parametrize( "mode, default", [ (DefaultValue.List, 1), (DefaultValue.Dict, 1), (DefaultValue.Delegate, 1), (DefaultValue.CallObject, 1), (DefaultValue.CallObject_Object, 1), (DefaultValue.CallObject_ObjectName, 1), (DefaultValue.ObjectMethod, 1), (DefaultValue.ObjectMethod_Name, 1), (DefaultValue.MemberMethod_Object, 1), ], ) def test_validating_default_value(mode, default): """Test that we validate the proposed default value when setting the mode.""" with pytest.raises(TypeError): Member().set_default_value_mode(mode, default) atom-0.12.1/tests/test_del_behaviors.py000066400000000000000000000042221506756731600201340ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Test the del behaviors no_op_handler: not sure it is used slot_handler: behavior leading to calling the default factory on next get constant_handler read_only_handler event_handler signal_handler delegate_handler: not tested here (see test_delegate.py) property_handler: not tested here (see test_property.py) """ import pytest from atom.api import Atom, Constant, Event, Int, Member, ReadOnly, Signal from atom.catom import DelAttr def test_del_noop(): """Test the noop handler.""" member = Member() member.set_delattr_mode(DelAttr.NoOp, None) class A(Atom): m = member assert A.m.delattr_mode[0] == DelAttr.NoOp a = A() a.m = 1 del a.m assert a.m == 1 assert A.m.do_delattr(a) is None assert a.m == 1 @pytest.mark.parametrize( "member, mode", [ (Event(), DelAttr.Event), (Signal(), DelAttr.Signal), (ReadOnly(), DelAttr.ReadOnly), (Constant(1), DelAttr.Constant), ], ) def test_undeletable(member, mode): """Test that unsettable members do raise the proper error.""" class Undeletable(Atom): m = member assert Undeletable.m.delattr_mode[0] == mode u = Undeletable() with pytest.raises(TypeError): del u.m with pytest.raises(TypeError): Undeletable.m.do_delattr(u) def test_del_slot(): """Test deleting a member using the slot handler.""" class DelSlot(Atom): i = Int(10) a = DelSlot() assert a.i == 10 # Using del statement a.i = 0 del a.i assert a.i == 10 # Using the member do_delattr a.i = 0 DelSlot.i.do_delattr(a) assert a.i == 10 # Test deleting an improperly indexed slot DelSlot.i.set_index(DelSlot.i.index + 1) with pytest.raises(AttributeError): DelSlot.i.do_delattr(a) atom-0.12.1/tests/test_delegator.py000066400000000000000000000146531506756731600173050ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Test the delegator member. We need to test all behaviors: - get - post_get - set - post_set - post_validate - default_value - del """ import pytest from atom.api import ( Atom, DefaultValue, Delegator, GetAttr, Int, PostGetAttr, PostSetAttr, PostValidate, SetAttr, Validate, ) from atom.catom import DelAttr class TrackedInt(Int): """Member used to check that a Delegate does forward all calls.""" def __init__(self, set_custom_modes=False): super(TrackedInt, self).__init__() self.called = [] if set_custom_modes: mode = GetAttr.MemberMethod_Object self.set_getattr_mode(mode, "get") mode = PostGetAttr.MemberMethod_ObjectValue self.set_post_getattr_mode(mode, "post_get") mode = SetAttr.MemberMethod_ObjectValue self.set_setattr_mode(mode, "set") mode = PostSetAttr.MemberMethod_ObjectOldNew self.set_post_setattr_mode(mode, "post_set") mode = Validate.MemberMethod_ObjectOldNew self.set_validate_mode(mode, "validate") mode = PostValidate.MemberMethod_ObjectOldNew self.set_post_validate_mode(mode, "post_validate") mode = DefaultValue.MemberMethod_Object self.set_default_value_mode(mode, "default_value") def get(self, obj): self.called.append("get") return self.get_slot(obj) def post_get(self, obj, value): self.called.append("post_get") return value def set(self, obj, value): self.called.append("set") self.set_slot(obj, value) def post_set(self, obj, old, new): self.called.append("post_set") def validate(self, obj, old, new): self.called.append("validate") if not isinstance(new, int): raise TypeError() return new def post_validate(self, obj, old, new): self.called.append("post_validate") return new def default_value(self, obj): self.called.append("default_value") return 0 def clone(self): # Re-implemented here to make sure that mode setting willl result # from the action of cloning the delegator return TrackedInt(False) def test_delegator_behaviors(): """Test that a Delegator does properly forward the behaviors.""" class DelegateTest(Atom): d = Delegator(TrackedInt(True)) # Test that behaviors are properly delegated dt = DelegateTest() assert dt.d == 0 assert DelegateTest.d.delegate.called == [ "default_value", "validate", "post_validate", "post_get", ] DelegateTest.d.delegate.called = [] dt.d = 1 assert DelegateTest.d.delegate.called == ["validate", "post_validate", "post_set"] assert dt.d == 1 # Make sure they use the same slot assert DelegateTest.d.delegate.do_getattr(dt) == 1 # Test also delegating get and set DelegateTest.d.delegate.called = [] DelegateTest.d.set_getattr_mode(GetAttr.Delegate, DelegateTest.d.delegate) DelegateTest.d.set_setattr_mode(SetAttr.Delegate, DelegateTest.d.delegate) DelegateTest.d.set_delattr_mode(DelAttr.Delegate, DelegateTest.d.delegate) assert dt.d == 1 assert DelegateTest.d.delegate.called == ["get", "validate", "post_validate"] dt.d = 2 assert DelegateTest.d.delegate.called == [ "get", "validate", "post_validate", "validate", "post_validate", "set", ] assert dt.d == 2 # Test delegating del (This will cause an error because the validator will # get None) del dt.d with pytest.raises(TypeError): assert dt.d == 0 @pytest.mark.parametrize( "mode, func", [ (GetAttr, "set_getattr_mode"), (SetAttr, "set_setattr_mode"), (DelAttr, "set_delattr_mode"), (PostGetAttr, "set_post_getattr_mode"), (PostSetAttr, "set_post_setattr_mode"), (Validate, "set_validate_mode"), (PostValidate, "set_post_validate_mode"), ], ) def test_delegator_mode_args_validation(mode, func): """Test that a delegator properly validate the arguments when setting mode.""" with pytest.raises(TypeError) as excinfo: getattr(Delegator(Int()), func)(getattr(mode, "Delegate"), None) assert "Member" in excinfo.exconly() def test_delegator_methods(): """Test manipulating a delegator. Mode setting methods are tested in cloning test """ class DelegateTest(Atom): d = Delegator(TrackedInt(True)) assert DelegateTest.d.name == "d" assert DelegateTest.d.delegate.name == "d" DelegateTest.d.set_name("e") assert DelegateTest.d.name == "e" assert DelegateTest.d.delegate.name == "e" assert DelegateTest.d.index == DelegateTest.d.delegate.index new_index = DelegateTest.d.index + 1 DelegateTest.d.set_index(new_index) assert DelegateTest.d.index == new_index assert DelegateTest.d.delegate.index == new_index def observer(s, c): return None assert not DelegateTest.d.delegate.static_observers() DelegateTest.d.add_static_observer(observer) assert DelegateTest.d.delegate.static_observers() DelegateTest.d.remove_static_observer(observer) assert not DelegateTest.d.delegate.static_observers() def test_delegator_cloning(): """Test cloning a delegator member. Test that the delegate is also cloned and its mode properly configured. """ class DelegateTest(Atom): tracked_int = TrackedInt(True) d = Delegator(tracked_int) # Checked that a cloned TrackedInt does not have any special mode set ti_clone = DelegateTest.tracked_int.clone() for m in ( "validate_mode", "post_getattr_mode", "post_validate_mode", "post_setattr_mode", ): assert getattr(ti_clone, m)[1] is None d_clone = DelegateTest.d.clone() for m in ( "validate_mode", "post_getattr_mode", "post_validate_mode", "post_setattr_mode", ): assert getattr(d_clone.delegate, m)[1] is not None atom-0.12.1/tests/test_enum.py000066400000000000000000000021071506756731600162720ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Test the enum member.""" import pytest from atom.api import Enum def test_enum(): """Test manipulating an Enum member.""" e = Enum("a", "b") assert e.items == ("a", "b") assert e.default_value_mode[1] == "a" e_def = e("b") assert e_def is not e assert e_def.default_value_mode[1] == "b" with pytest.raises(TypeError): e("c") e_add = e.added("c", "d") assert e_add is not e assert e_add.items == ("a", "b", "c", "d") e_rem = e.removed("a") assert e_rem is not e assert e_rem.items == ("b",) assert e_rem.default_value_mode[1] == "b" with pytest.raises(ValueError): e.removed("a", "b") with pytest.raises(ValueError): Enum() atom-0.12.1/tests/test_eventbinder.py000066400000000000000000000040411506756731600176320ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2018-2024, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Test the notification mechanisms.""" import gc import operator import sys from collections import Counter import pytest from atom.api import Atom, Event, Int def test_eventbinder_lifecycle(): """Test creating and destroying an event binder. We create enough event binder to exceed the freelist length and fully deallocate some. """ class EventAtom(Atom): e = Event() event_binders = [EventAtom.e for i in range(512)] for i, e in enumerate(event_binders): event_binders[i] = None del e gc.collect() atom = EventAtom() eb = atom.e referents = [EventAtom.e, atom, type(eb)] assert Counter(gc.get_referents(eb)) == Counter(referents) def test_eventbinder_call(): """Test calling an event binder and handling bad arguments.""" class EventAtom(Atom): counter = Int() e = Event() a = EventAtom() def update_counter(change): change["object"].counter += change["value"] a.observe("e", update_counter) a.e(2) assert a.counter == 2 with pytest.raises(TypeError) as excinfo: a.e(k=1) assert "keyword arguments" in excinfo.exconly() with pytest.raises(TypeError) as excinfo: a.e(1, 2, 3) assert "at most 1 argument" in excinfo.exconly() def test_eventbinder_cmp(): """Test comparing event binders.""" class EventAtom(Atom): e1 = Event() e2 = Event() a = EventAtom() assert a.e1 == a.e1 assert not a.e1 == a.e2 assert not a.e1 == 1 if sys.version_info >= (3,): for op in ("lt", "le", "gt", "ge"): with pytest.raises(TypeError): getattr(operator, op)(a.e1, 1) atom-0.12.1/tests/test_examples.py000066400000000000000000000015141506756731600171450ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2020-2024, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Test the examples.""" import os import runpy import pytest example_folder = os.path.join(os.path.dirname(__file__), "..", "examples") examples = [] for dirpath, dirnames, filenames in os.walk(example_folder): examples += [os.path.join(dirpath, f) for f in filenames] @pytest.mark.parametrize("path", examples) def test_example(path): if "pickling" in path: pytest.skip("Example requires to be executed as a top level module") runpy.run_path(path) atom-0.12.1/tests/test_get_behaviors.py000066400000000000000000000111601506756731600201460ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Test the get behaviors no_op_handler: : tested here slot_handler: standard one tested through other tests (post_get, ...) event_handler: not tested here (see test_observe.py and test_event_binder.py) signal_handler: not tested here (see test_observe.py) delegate_handler: not tested here (see test_delegate.py) property_handler: not tested here (see test_property.py) cached_property_handler: not tested here (see test_property.py) call_object_object_handler: tested here call_object_object_name_handler: tested here object_method_handler: tested here object_method_name_handler: tested here member_method_object_handler: method defined on a Member subclass """ import pytest from atom.api import Atom, GetAttr, Int, Value def test_using_no_op_handler(): """Test using the no_op handler.""" v = Value() v.set_getattr_mode(GetAttr.NoOp, None) class CustomGetAtom(Atom): val = v a = CustomGetAtom() assert a.val is None a.val = 1 assert a.val is None def test_using_call_object_object_mode(): """Test using call_object_object mode.""" def getter(object): object.count += 1 return object.count m = Int() m.set_getattr_mode(GetAttr.CallObject_Object, getter) class CustomGetAtom(Atom): val = m count = Int() a = CustomGetAtom() assert a.val == 1 assert a.val == 2 with pytest.raises(TypeError): m.set_getattr_mode(GetAttr.CallObject_Object, 1) def test_using_call_object_object_name_mode(): """Test using call_object_object_name mode.""" def getter(object, name): object.count += 1 return object.count, name m = Value() m.set_getattr_mode(GetAttr.CallObject_ObjectName, getter) class CustomGetAtom(Atom): val = m count = Int() a = CustomGetAtom() assert a.val == (1, "val") assert a.val == (2, "val") with pytest.raises(TypeError): m.set_getattr_mode(GetAttr.CallObject_ObjectName, 1) def test_using_object_method_mode(): """Test using object_method mode.""" m = Int() m.set_getattr_mode(GetAttr.ObjectMethod, "getter") class CustomGetAtom(Atom): val = m count = Int() def getter(self): self.count += 1 return self.count a = CustomGetAtom() assert a.val == 1 assert a.val == 2 with pytest.raises(TypeError): m.set_getattr_mode(GetAttr.ObjectMethod, 1) def test_using_object_method_name_mode(): """Test using object_method mode.""" m = Value() m.set_getattr_mode(GetAttr.ObjectMethod_Name, "getter") class CustomGetAtom(Atom): val = m count = Int() def getter(self, name): self.count += 1 return (self.count, name) a = CustomGetAtom() assert a.val == (1, "val") assert a.val == (2, "val") with pytest.raises(TypeError): m.set_getattr_mode(GetAttr.ObjectMethod_Name, 1) def test_subclassing_member(): """Test defining get in a Member subclass""" class ModuloInt(Int): def __init__(self): super(ModuloInt, self).__init__() mode = GetAttr.MemberMethod_Object self.set_getattr_mode(mode, "get") def get(self, obj): return self.get_slot(obj) % 2 class GetTest(Atom): mi = ModuloInt() pvt = GetTest() mi = pvt.get_member("mi") assert mi.getattr_mode[0] == GetAttr.MemberMethod_Object pvt.mi = 2 assert pvt.mi == 0 pvt.mi = 3 assert pvt.mi == 1 pvt.mi = 2 assert mi.do_getattr(pvt) == 0 pvt.mi = 3 assert mi.do_getattr(pvt) == 1 with pytest.raises(TypeError): Int().set_getattr_mode(GetAttr.MemberMethod_Object, 1) def test_handling_wrong_index(): """Test handling a wrong index in the slot handler.""" class SlotAtom(Atom): v = Value() sa = SlotAtom() assert SlotAtom.v.do_getattr(sa) is None SlotAtom.v.set_index(SlotAtom.v.index + 1) with pytest.raises(AttributeError): SlotAtom.v.do_getattr(sa) def test_using_invalid_args(): """Test using the invalid arguments.""" v = Value() with pytest.raises(TypeError): v.set_getattr_mode(None) with pytest.raises(TypeError): v.set_getattr_mode(7, None) atom-0.12.1/tests/test_get_set_state.py000066400000000000000000000165511506756731600201700ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2022-2024, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- from datetime import datetime import pytest from atom.api import Atom, Bool, Constant, Float, GetState, Int, List, Range, Str, Typed try: import pytest_benchmark # noqa: F401 BENCHMARK_INSTALLED = True except ImportError: BENCHMARK_INSTALLED = False class AtomBase(Atom): def __getstate_py__(self): state = {} state.update(getattr(self, "__dict__", {})) for name in self.__class__.__slotnames__: state[name] = getattr(self, name) for key in self.members(): state[key] = getattr(self, key) return state def __setstate_py__(self, state): for key, value in state.items(): setattr(self, key, value) def test_getstate_member_slots_error(): class Test(Atom): __slots__ = ("a", "b") Test.__slotnames__ = None v = Test() with pytest.raises(SystemError): v.__getstate__() def test_getstate_member_error(): class Test(Atom): created = Typed(datetime, optional=False) Test.created.set_getstate_mode(GetState.Include, None) v = Test() with pytest.raises(ValueError): v.__getstate__() def test_getstate(): class Test(Atom): x = Int(3) y = Int(2) t = Test() assert t.__getstate__() == {"x": 3, "y": 2} def test_getstate_constant(): class Test(Atom): x = Int(3) y = Int(2) z = Constant(4) t = Test() assert "z" not in t.__getstate__() def test_getstate_frozen(): class Test(Atom): x = Int(3) y = Int(2) t = Test() t.freeze() assert t.__getstate__() == {"x": 3, "y": 2, "--frozen": None} def test_setstate_frozen(): class Test(Atom): x = Int(3) y = Int(2) t = Test() t.__setstate__({"x": 3, "y": 2, "--frozen": None}) with pytest.raises(AttributeError): t.x = 5 # Setting again does not work t.__setstate__({"--frozen": 0}) with pytest.raises(AttributeError): t.x = 5 # Check that it can be modified if frozen flag is missing t = Test() t.__setstate__({"x": 3, "y": 2}) t.x = 5 def test_setstate_non_str_key(): class Test(Atom): x = Int(3) y = Int(2) t = Test() with pytest.raises(TypeError): t.__setstate__({0: "yes"}) class Foo: def __eq__(self, other): raise ValueError("Do not compare me") with pytest.raises(TypeError): t.__setstate__({Foo(): "yes"}) @pytest.mark.skipif(not BENCHMARK_INSTALLED, reason="benchmark is not installed") @pytest.mark.benchmark(group="getstate") @pytest.mark.parametrize("fn", ("c", "py")) def test_bench_getstate(benchmark, fn): class Test(AtomBase): first_name = Str("First") last_name = Str("Last") age = Range(low=0) debug = Bool(False) items = List(default=[1, 2, 3]) expected = { "first_name": "First", "last_name": "Last", "age": 0, "debug": False, "items": [1, 2, 3], } p = Test() if fn == "py": func = p.__getstate_py__ else: func = p.__getstate__ def task(): assert func() == expected benchmark(task) @pytest.mark.skipif(not BENCHMARK_INSTALLED, reason="benchmark is not installed") @pytest.mark.benchmark(group="loopback") @pytest.mark.parametrize("fn", ("c", "py")) def test_bench_loopback(benchmark, fn): class Test(AtomBase): first_name = Str("First") last_name = Str("Last") age = Range(low=0) debug = Bool(False) items = List(default=[1, 2, 3]) expected = { "first_name": "First", "last_name": "Last", "age": 0, "debug": False, "items": [1, 2, 3], } if fn == "py": def task(): t = Test() t.__setstate_py__(expected) assert t.__getstate_py__() == expected else: def task(): t = Test() t.__setstate__(expected) assert t.__getstate__() == expected benchmark(task) @pytest.mark.skipif(not BENCHMARK_INSTALLED, reason="benchmark is not installed") @pytest.mark.benchmark(group="getstate-dict") @pytest.mark.parametrize("fn", ("c", "py")) def test_bench_getstate_dict(benchmark, fn): class Foo: def __init__(self): self.count = 1 class Test(AtomBase, Foo): title = Str("Title") enabled = Bool(True) category = Str("Main") tags = List(default=["foo", "bar"]) expected = { "count": 1, "title": "Title", "enabled": True, "category": "Main", "tags": ["foo", "bar"], } p = Test() p.count = 1 if fn == "py": func = p.__getstate_py__ else: func = p.__getstate__ def task(): assert func() == expected benchmark(task) @pytest.mark.skipif(not BENCHMARK_INSTALLED, reason="benchmark is not installed") @pytest.mark.benchmark(group="getstate-slots") @pytest.mark.parametrize("fn", ("c", "py")) def test_bench_getstate_slots(benchmark, fn): class Test(AtomBase): __slots__ = ("bar", "foo") name = Str("Name") enabled = Bool(True) rating = Float() created = Typed(datetime) tags = List(default=["foo", "bar"]) now = datetime.now() expected = { "foo": 1, "bar": True, "name": "Name", "enabled": True, "rating": 0.0, "created": now, "tags": ["foo", "bar"], } p = Test() p.foo = 1 p.bar = True p.created = now if fn == "py": func = p.__getstate_py__ else: func = p.__getstate__ def task(): assert func() == expected benchmark(task) @pytest.mark.skipif(not BENCHMARK_INSTALLED, reason="benchmark is not installed") @pytest.mark.benchmark(group="state") @pytest.mark.parametrize("fn", ("c", "py")) def test_bench_setstate(benchmark, fn): class Test(AtomBase): x = Int() y = Int() if fn == "py": def task(): t = Test() t.__setstate_py__({"x": 1, "y": 2}) assert t.x == 1 assert t.y == 2 else: def task(): t = Test() t.__setstate__({"x": 1, "y": 2}) assert t.x == 1 assert t.y == 2 benchmark(task) def test_setstate(): class Test(Atom): x = Int() y = Int() t = Test() t.__setstate__({"x": 1, "y": 2}) assert t.x == 1 assert t.y == 2 def test_setstate_errors(caplog): class Test(AtomBase): x = Int() y = Int() t = Test() with pytest.raises(TypeError): t.__setstate__() # Incorrect number of args with pytest.raises(TypeError): t.__setstate__({}, False) # Incorrect number of args with pytest.raises(TypeError): t.__setstate__(None) # Not a mapping (no items() method) with pytest.raises(TypeError): t.__setstate__(["z"]) # Not a mapping (has no items() method) with pytest.raises(AttributeError): t.__setstate__({"z": 3}) # Invalid attribute atom-0.12.1/tests/test_getstate_behaviors.py000066400000000000000000000101331506756731600212060ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2023-2024, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Test the getstate behaviors include_handler: tested here exclude_handler: tested here include_non_default_handler: tested here property_handler: tested in test_property member_method_object_handler: tested here object_method_name_handler: tested here """ import pickle import pytest from atom.api import ( Atom, Bool, Constant, ForwardInstance, ForwardTyped, GetState, Instance, ReadOnly, Typed, Value, ) @pytest.mark.parametrize( "member, mode", [ (Value(), GetState.Include), (ReadOnly(), GetState.IncludeNonDefault), (Constant(1), GetState.Exclude), (Typed(int, optional=True), GetState.Include), (Typed(int, optional=False), GetState.IncludeNonDefault), (ForwardTyped(lambda: int, optional=True), GetState.Include), (ForwardTyped(lambda: int, optional=False), GetState.IncludeNonDefault), (Instance(int, optional=True), GetState.Include), (Instance(int, optional=False), GetState.IncludeNonDefault), (ForwardInstance(lambda: int, optional=True), GetState.Include), (ForwardInstance(lambda: int, optional=False), GetState.IncludeNonDefault), ], ) def test_member_getstate_mode(member, mode): assert member.getstate_mode[0] == mode class A(Atom): val = Value() def test_using_include_handler(): """Test using the include handler.""" A.val.set_getstate_mode(GetState.Include, None) a = A() assert A.val.do_should_getstate(a) is True assert b"val" in pickle.dumps(a, 0) def test_using_exclude_handler(): """Test using the include handler.""" A.val.set_getstate_mode(GetState.Exclude, None) a = A() assert A.val.do_should_getstate(a) is False assert b"val" not in pickle.dumps(a, 0) def test_using_include_non_default_handler(): """Test using the include handler.""" class A(Atom): val = Value() val.set_getstate_mode(GetState.IncludeNonDefault, None) assert A.val.do_should_getstate(A()) is False assert A.val.do_should_getstate(A(val=1)) is True def test_using_object_method_name(): """Test using object_method mode.""" class A(Atom): val = Value() val2 = Value() seen = Value() def _getstate_val(self, name: str) -> bool: self.seen = name return name == "val" def _getstate_val2(self, name: str) -> bool: self.seen = name return name == "val" a = A() assert A.val.do_should_getstate(a) is True assert a.seen == "val" assert A.val2.do_should_getstate(a) is False assert a.seen == "val2" with pytest.raises(TypeError): Value().set_getstate_mode(GetState.ObjectMethod_Name, 1) def test_subclassing_member(): """Test defining get in a Member subclass""" class CustomV(Value): def __init__(self): super(Value, self).__init__() self.set_getstate_mode(GetState.MemberMethod_Object, "getstate") def getstate(self, obj): return obj.pickle class A(Atom): v = CustomV() pickle = Bool() pvt = A() assert A.v.do_should_getstate(pvt) is False pvt.pickle = True assert A.v.do_should_getstate(pvt) is True with pytest.raises(TypeError): Value().set_getstate_mode(GetState.MemberMethod_Object, 1) def test_handling_exception_in_getstate(): """Test handling an exception while pickling.""" class CustomV(Value): def __init__(self): super(Value, self).__init__() self.set_getstate_mode(GetState.MemberMethod_Object, "getstate") def getstate(self, obj): raise RuntimeError class A(Atom): v = CustomV() with pytest.raises(RuntimeError): pickle.dumps(A()) atom-0.12.1/tests/test_mem.py000066400000000000000000000066431506756731600161150ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2023-2024, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- import gc import os import pickle import sys import time import tracemalloc from multiprocessing import Process import pytest from atom.api import Atom, DefaultDict, Dict, Int, List, Set, atomref try: import psutil PSUTIL_UNAVAILABLE = False except ImportError: PSUTIL_UNAVAILABLE = True TIMEOUT = 6 class DictObj(Atom): data = Dict(default={"a": 0}) class DefaultDictObj(Atom): data = DefaultDict(value=Int(), default={1: 1}) class ListObj(Atom): data = List(default=[1, 2, 3]) class SetObj(Atom): data = Set(default={1, 2, 3}) class RefObj(Atom): data = Int() MEM_TESTS = { "dict": DictObj, "defaultdict": DefaultDictObj, "list": ListObj, "set": SetObj, "atomref": RefObj, } PICKLE_MEM_TESTS = { "dict": DictObj, "defaultdict": DefaultDictObj, "list": ListObj, "set": SetObj, } def memtest(cls): # Create object in a loop # Memory usage should settle out and not change while True: obj = cls() obj.data # Force creation del obj gc.collect() def atomreftest(cls): obj = cls() obj.data while True: ref = atomref(obj) del ref gc.collect() @pytest.mark.skipif( "CI" in os.environ and sys.platform.startswith("darwin"), reason="Flaky on MacOS CI runners", ) @pytest.mark.skipif(PSUTIL_UNAVAILABLE, reason="psutil is not installed") @pytest.mark.parametrize("label", MEM_TESTS.keys()) def test_mem_usage(label): TestClass = MEM_TESTS[label] if "atomref" in label: target = atomreftest else: target = memtest p = Process(target=target, args=(TestClass,)) p.start() try: stats = psutil.Process(p.pid) time.sleep(TIMEOUT * 1 / 4) first_info = stats.memory_info() time.sleep(TIMEOUT * 3 / 4) last_info = stats.memory_info() # Allow slight memory decrease over time to make tests more resilient if first_info != last_info: assert first_info.rss >= last_info.rss >= 0, ( "Memory leaked:\n {}\n {}".format(first_info, last_info) ) assert first_info.vms >= last_info.vms >= 0, ( "Memory leaked:\n {}\n {}".format(first_info, last_info) ) finally: p.kill() p.join() @pytest.mark.parametrize("label", PICKLE_MEM_TESTS.keys()) def test_pickle_mem_usage(label): TestClass = PICKLE_MEM_TESTS[label] obj = TestClass() for _ in range(100): pickle.loads(pickle.dumps(obj)) gc.collect() tracemalloc.start() for i in range(10000): pck = pickle.dumps(obj) pickle.loads(pck) del pck gc.collect() for stat in ( tracemalloc.take_snapshot() .filter_traces( [ tracemalloc.Filter(True, "*/atom/*"), tracemalloc.Filter(False, "*/tests/*"), ] ) .statistics("lineno") ): # not sure why I sometimes see a 2 here but the original buggy version # reported values > 50 assert stat.count < 5 atom-0.12.1/tests/test_member.py000066400000000000000000000244341506756731600166040ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Test the Member class. Attributes: - name - metadata - index # Tested in behavior related tests - (post_)getattr/(post_)setattr/delattr/default_value/(post_)validate_mode Methods: - set_name (Delegator see test_delegator.py, Dict, Event, List) - set_index (Delegator see test_delegator.py, Dict, Event, List) - get_slot - set_slot - del_slot - clone (Member, Delegator, Instance, List, Subclass, Typed) - tag # Tested in test_observe.py - has_observers - has_observer - copy_static_observers - static_observers (Delegator see test_delegator.py) - add_static_oberserver (Delegator see test_delegator.py) - remove_static_observer (Delegator see test_delegator.py) - notify # Tested in behavior related test - do_(post_)getattr/(post_)setattr/delattr/default_value/(post_)validate - do_full_validate - set_getattr/setattr/delattr/default_value/validate_mode - set_post_getattr/setattr/validate_mode """ import pytest from atom.api import ( Atom, DefaultValue, Dict, Event, FixedTuple, ForwardInstance, ForwardSubclass, ForwardTyped, GetAttr, GetState, Int, List, PostGetAttr, PostSetAttr, PostValidate, Set, SetAttr, Tuple, Validate, Value, observe, ) from atom.catom import DelAttr def test_name_managing_name(): """Test getting/setting the name of a Member.""" class NameTest(Atom): v = Value() t = Tuple(Int()) li = List(Int()) d = Dict(Int(), Int()) e = Event(Int()) assert NameTest.v.name == "v" NameTest.v.set_name("v2") assert NameTest.v.name == "v2" assert NameTest.t.name == "t" assert NameTest.t.item.name == "t|item" assert NameTest.li.name == "li" assert NameTest.li.item.name == "li|item" assert NameTest.d.name == "d" key, value = NameTest.d.validate_mode[1] assert key.name == "d|key" assert value.name == "d|value" assert NameTest.e.name == "e" assert NameTest.e.validate_mode[1].name == "e" with pytest.raises(TypeError) as excinfo: NameTest.v.set_name(1) assert "str" in excinfo.exconly() def test_managing_slot_index(): """Test getting and setting the index of a Member.""" class IndexTest(Atom): v1 = Value() v2 = Value() t = Tuple(Int()) li = List(Int()) d = Dict(Int(), Int()) e = Event(Int()) it = IndexTest() it.v1 = 1 it.v2 = 2 id1 = IndexTest.v1.index id2 = IndexTest.v2.index IndexTest.v1.set_index(id2) IndexTest.v2.set_index(id1) assert it.v1 == 2 assert it.v2 == 1 assert IndexTest.t.item.index == IndexTest.t.index IndexTest.t.set_index(99) assert IndexTest.t.item.index == IndexTest.t.index assert IndexTest.li.item.index == IndexTest.li.index IndexTest.li.set_index(99) assert IndexTest.li.item.index == IndexTest.li.index key, value = IndexTest.d.validate_mode[1] assert key.index == IndexTest.d.index assert value.index == IndexTest.d.index IndexTest.d.set_index(99) assert key.index == IndexTest.d.index assert value.index == IndexTest.d.index assert IndexTest.e.validate_mode[1].index == IndexTest.e.index with pytest.raises(TypeError) as excinfo: IndexTest.v1.set_index("") assert "int" in excinfo.exconly() def test_metadata_handling(): """Test writing and accessing the metadata of a Member.""" assert Value().metadata is None class MetadataTest(Atom): m = Value().tag(pref=True) mt = MetadataTest() m = mt.get_member("m") assert m.metadata == {"pref": True} m.metadata = {"a": 1, "b": 2} assert m.metadata == {"a": 1, "b": 2} m.metadata = None assert m.metadata is None with pytest.raises(TypeError) as excinfo: m.metadata = 1 assert "dict or None" in excinfo.exconly() with pytest.raises(TypeError) as excinfo: m.tag(1) assert "tag()" in excinfo.exconly() with pytest.raises(TypeError) as excinfo: m.tag() assert "tag()" in excinfo.exconly() def test_direct_slot_access(): """Test accessing a slot directly.""" class SlotTest(Atom): v = Value() st = SlotTest() assert SlotTest.v.get_slot(st) is None SlotTest.v.set_slot(st, 1) assert SlotTest.v.get_slot(st) == 1 SlotTest.v.del_slot(st) assert SlotTest.v.get_slot(st) is None # Test type validation with pytest.raises(TypeError) as excinfo: SlotTest.v.get_slot(None) assert "CAtom" in excinfo.exconly() with pytest.raises(TypeError) as excinfo: SlotTest.v.set_slot() assert "2 arguments" in excinfo.exconly() with pytest.raises(TypeError) as excinfo: SlotTest.v.set_slot(None, 1) assert "CAtom" in excinfo.exconly() with pytest.raises(TypeError) as excinfo: SlotTest.v.del_slot(None) assert "CAtom" in excinfo.exconly() # Test index validation SlotTest.v.set_index(SlotTest.v.index + 1) with pytest.raises(AttributeError): SlotTest.v.get_slot(st) with pytest.raises(AttributeError): SlotTest.v.set_slot(st, 1) with pytest.raises(AttributeError): SlotTest.v.del_slot(st) def test_class_validation(): """Test validating the type of class in the descriptor.""" class FalseAtom(object): v = Value() fa = FalseAtom() with pytest.raises(TypeError) as excinfo: fa.v assert "CAtom" in excinfo.exconly() with pytest.raises(TypeError) as excinfo: fa.v = 1 assert "CAtom" in excinfo.exconly() @pytest.mark.parametrize( "method, arg_number", [ ("do_getattr", 1), ("do_setattr", 2), ("do_delattr", 1), ("do_post_getattr", 2), ("do_post_setattr", 3), ("do_default_value", 1), ("do_validate", 3), ("do_post_validate", 3), ("do_full_validate", 3), ("do_should_getstate", 1), ], ) def test_handling_arg_issue_in_do_methods(method, arg_number): """Test handling bad args in do methods.""" m = Value() if arg_number > 1: with pytest.raises(TypeError) as excinfo: getattr(m, method)() assert str(arg_number) in excinfo.exconly() with pytest.raises(TypeError) as excinfo: getattr(m, method)(*([None] * arg_number)) def test_member_cloning(): """Test cloning members.""" class Spy(object): def __call__(self, *args): pass spy = Spy() class CloneTest(Atom): v = Value().tag(test=True) @observe("v") def react(self, change): pass assert CloneTest.v.static_observers() CloneTest.v.set_getattr_mode(GetAttr.CallObject_Object, spy) CloneTest.v.set_setattr_mode(SetAttr.CallObject_ObjectValue, spy) CloneTest.v.set_delattr_mode(DelAttr.Signal, None) CloneTest.v.set_default_value_mode(DefaultValue.CallObject_Object, spy) CloneTest.v.set_validate_mode(Validate.Int, None) CloneTest.v.set_post_getattr_mode(PostGetAttr.ObjectMethod_NameValue, "a") CloneTest.v.set_post_setattr_mode(PostSetAttr.ObjectMethod_NameOldNew, "b") CloneTest.v.set_post_validate_mode(PostValidate.ObjectMethod_NameOldNew, "c") CloneTest.v.set_getstate_mode(GetState.ObjectMethod_Name, "s") cv = CloneTest.v.clone() for attr in ( "name", "index", "metadata", "getattr_mode", "setattr_mode", "delattr_mode", "default_value_mode", "validate_mode", "post_getattr_mode", "post_setattr_mode", "post_validate_mode", "getstate_mode", ): assert getattr(cv, attr) == getattr(CloneTest.v, attr) assert cv.static_observers() == CloneTest.v.static_observers() @pytest.mark.parametrize( "untyped, typed", [ (List(), List(int)), (Tuple(), Tuple(int)), (Dict(), Dict(int, int)), (Set(), Set(int)), ], ) def test_cloning_containers_member(untyped, typed): """Check that cloning a list does clone the validation item is present.""" if not isinstance(untyped, Dict): assert untyped.clone().item is None typed.set_index(5) cl2 = typed.clone() assert cl2.index == typed.index validators = [typed.item] if hasattr(typed, "item") else typed.validate_mode[1] c_validators = [cl2.item] if hasattr(typed, "item") else cl2.validate_mode[1] for v, cv in zip(validators, c_validators): assert cv is not v assert isinstance(cv, type(v)) def test_cloning_fixed_tuple(): typed = FixedTuple(int) typed.set_index(5) cl2 = typed.clone() assert cl2.index == typed.index for v, cv in zip(typed.items, cl2.items): assert cv is not v assert isinstance(cv, type(v)) # XXX should the kwargs be copied rather than simply re-assigned @pytest.mark.parametrize( "member, cloned_attributes", [ (ForwardSubclass(lambda: object), ["resolve"]), (ForwardTyped(lambda: object, (1,), {"a": 1}), ["resolve", "args", "kwargs"]), ( ForwardInstance(lambda: object, (1,), {"a": 1}), ["resolve", "args", "kwargs"], ), ], ) def test_cloning_forward(member, cloned_attributes): """Test that subclasses of Member are properly cloned.""" member.set_index(5) clone = member.clone() assert clone.index == member.index for attr in cloned_attributes: assert getattr(clone, attr) is getattr(member, attr) @pytest.mark.parametrize( "ForwardedMember, optional", ( (ForwardInstance, True), (ForwardTyped, False), ), ) def test_cloned_forward_validator(ForwardedMember, optional): """Test a cloned forwarded member""" class AbtractItem(Atom): view = ForwardedMember(lambda: View, optional=optional) class Item(AbtractItem): def _default_view(self): return View() class View(Atom): pass Item().view # Test validate assert Item.view.optional == optional atom-0.12.1/tests/test_observe.py000066400000000000000000000570501506756731600170020ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Test the notification mechanisms.""" import pytest from atom.api import ( Atom, ChangeType, ContainerList, Event, Int, List, Signal, Value, observe, ) class NonComparableObject: def __eq__(self, other): raise ValueError() # --- Static observer manipulations @pytest.fixture def static_atom(): """Class to test static observers.""" class Extended(Atom): val = Int() obs_decorator = observe("val2", "ext.val") class ObserverTest(Atom): cls = Extended ext = Value() val2 = Int(0) changes = List() @obs_decorator def react(self, change): self.changes.append(change["name"]) manual_obs = obs_decorator(react.func) return ObserverTest() def test_static_observer(static_atom): """Test static observers.""" ot = static_atom # Test checking for static observers assert ot.get_member("val2").has_observers() assert ot.get_member("val2").has_observers(ChangeType.UPDATE) assert ot.get_member("val2").has_observer("manual_obs") assert ot.get_member("val2").has_observer("react") with pytest.raises(TypeError) as excinfo: assert ot.get_member("val2").has_observer(1) assert "str or callable" in excinfo.exconly() # Test notifications on value setting ot.val2 = 1 assert "val2" in ot.changes def test_manual_static_observers(static_atom): """Test manually managing static observers.""" # Force the use of safe comparison (error cleaning and fallback) class Observer: def __eq__(self, other): raise ValueError() def __call__(self, change): change["object"].changes.append(change["name"]) react = Observer() # We have 2 static observers hence 2 removals member = static_atom.get_member("val2") member.remove_static_observer("react") member.remove_static_observer("manual_obs") assert not member.has_observers() static_atom.val2 += 1 assert not static_atom.changes member.add_static_observer(react) assert member.has_observer(react) static_atom.val2 += 1 assert static_atom.changes member.remove_static_observer(react) assert not member.has_observers() member.add_static_observer(react, ChangeType.UPDATE) assert not member.has_observers(ChangeType.CREATE) assert member.has_observers(ChangeType.UPDATE) assert member.has_observer(react, ChangeType.UPDATE) assert not member.has_observer(react, ChangeType.DELETE) with pytest.raises(TypeError) as excinfo: member.add_static_observer(1) assert "str or callable" in excinfo.exconly() with pytest.raises(TypeError) as excinfo: member.add_static_observer(react, "foobar") assert "int" in excinfo.exconly() with pytest.raises(TypeError) as excinfo: member.remove_static_observer(1) assert "str or callable" in excinfo.exconly() # Check errors with pytest.raises(TypeError) as excinfo: member.has_observer() assert "expects a callable" in excinfo.exconly() with pytest.raises(TypeError) as excinfo: member.has_observer(react, 1, True) assert "expects a callable" in excinfo.exconly() with pytest.raises(TypeError) as excinfo: member.has_observer(react, "bool") assert "int" in excinfo.exconly() @pytest.mark.parametrize( "change_type, expected_types", [ (ChangeType.ANY, ["create", "update", "delete"]), (ChangeType.CREATE, ["create"]), (ChangeType.UPDATE, ["update"]), (ChangeType.DELETE, ["delete"]), (ChangeType.UPDATE | ChangeType.DELETE, ["update", "delete"]), (0, []), (100000, []), ], ) def test_static_observers_change_types(change_type, expected_types): """Test manually managing static observers.""" # Force the use of safe comparison (error cleaning and fallback) class Widget(Atom): val = Value() changes = [] def react(change): changes.append(change) Widget.val.add_static_observer(react, change_type) w = Widget() w.val w.val = 1 del w.val assert len(changes) == len(expected_types) for change, exp in zip(changes, expected_types): assert change["type"] == exp def test_modifying_static_observers_in_callback(): """Test modifying the static observers in an observer.""" class ChangingAtom(Atom): val = Int() counter1 = Int() counter2 = Int() @observe("val") def react1(self, change): self.counter1 += 1 m = self.get_member("val") m.remove_static_observer("react1") m.add_static_observer("react2") def react2(self, change): self.counter2 += 1 m = self.get_member("val") m.remove_static_observer("react2") m.add_static_observer("react1") ca = ChangingAtom() assert ChangingAtom.val.has_observer("react1") assert not ChangingAtom.val.has_observer("react2") ca.val = 1 assert ca.counter1 == 1 # Ensure the modification take place after notification dispatch is # complete assert ca.counter2 == 0 assert ChangingAtom.val.has_observer("react2") assert not ChangingAtom.val.has_observer("react1") ca.val += 1 assert ca.counter2 == 1 # Ensure the modification take place after notification dispatch is # complete assert ca.counter1 == 1 assert ChangingAtom.val.has_observer("react1") assert not ChangingAtom.val.has_observer("react2") # Test handling exception in the guard map that ensure that the # modifications to the observers occur after the notification dispatch def raising_observer(change): raise ValueError() ChangingAtom.val.add_static_observer(raising_observer) with pytest.raises(ValueError): ca.val += 1 assert ca.counter1 == 2 # Ensure the modification take place after notification dispatch is # complete assert ca.counter2 == 1 assert ChangingAtom.val.has_observer("react2") def test_copy_static_observers(static_atom): """Test cloning the static observers of a member.""" member = static_atom.get_member("val2") v = Value() v.copy_static_observers(member) assert v.has_observers() assert v.has_observer("manual_obs") assert v.has_observer("react") # This is ano-op and take an early exit seen in coverage. v.copy_static_observers(v) with pytest.raises(TypeError) as excinfo: v.copy_static_observers(1) assert "Member" in excinfo.exconly() def test_extended_static_observers(static_atom): """Test using extended static observers.""" ot = static_atom ext1 = static_atom.cls() ext2 = static_atom.cls() # Test installing the extended observer ot.ext = ext1 assert ext1.has_observer("val", ot.react) assert not ext2.has_observer("val", ot.react) ot.ext = ext2 assert ext2.has_observer("val", ot.react) assert not ext1.has_observer("val", ot.react) # Test notifications on value setting ot.val2 = 1 assert "val2" in ot.changes ext1.val = 1 assert "val" not in ot.changes ext2.val = 1 assert "val" in ot.changes # Test removing the extended observer upon deletion del ot.ext assert not ext2.has_observer("val", ot.react) with pytest.raises(TypeError): ot.ext = 12 def test_observe_decorators(): """Test checking observe decorator handling.""" def react(self, change): pass handler = observe(("val",)) handler(react) handler_clone = handler.clone() assert handler is not handler_clone assert handler.pairs == handler_clone.pairs assert handler.func is handler_clone.func with pytest.raises(TypeError): observe(12) with pytest.raises(TypeError): observe(["a.b.c"]) # --- Dynamic observer manipulations class Observer(object): """Observer used for testing dynamic observers.""" def __init__(self): self.count = 0 def react(self, change): self.count += 1 # Force the use of safe equality comparison (ie clean error and fallback # on pointer comparison) def __eq__(self, other): raise ValueError() class DynamicAtom(Atom): """Atom used to test dynamic observers.""" val = Int() val2 = Int() val3 = Int() def test_single_observe(): """Test observing a single member from a single instance.""" dt1 = DynamicAtom() dt2 = DynamicAtom() observer = Observer() dt1.observe("val", observer.react) # Test creation in the absence of static observers dt1.val assert observer.count == 1 dt2.val = 1 assert observer.count == 1 del dt1.val assert observer.count == 2 def test_multiple_observe(): """Test observing multiple members from a single instance.""" dt1 = DynamicAtom() observer = Observer() dt1.observe(("val", "val2"), observer.react) dt1.val = 1 assert observer.count == 1 dt1.val2 = 1 assert observer.count == 2 def test_observe_change_types(): """Test observing multiple members from a single instance.""" dt1 = DynamicAtom() changes = [] def on_change(change): changes.append(change) dt1.observe("val", on_change, ChangeType.UPDATE | ChangeType.DELETE) dt1.val = 1 assert len(changes) == 0 dt1.val = 2 assert len(changes) == 1 assert changes[0]["type"] == "update" del dt1.val assert len(changes) == 2 assert changes[1]["type"] == "delete" def test_wrong_args_observe(): """Test handling of wrong arguments to observe.""" dt1 = DynamicAtom() with pytest.raises(TypeError) as excinfo: dt1.observe("val") assert "2 or 3 arguments" in excinfo.exconly() with pytest.raises(TypeError) as excinfo: dt1.observe("val", lambda change: change, ChangeType.ANY, "bar") assert "2 or 3 arguments" in excinfo.exconly() with pytest.raises(TypeError) as excinfo: dt1.observe(1, lambda change: change) assert "iterable" in excinfo.exconly() with pytest.raises(TypeError) as excinfo: dt1.observe((1, 1), lambda change: change) assert "str" in excinfo.exconly() with pytest.raises(TypeError) as excinfo: dt1.observe("val", 1) assert "callable" in excinfo.exconly() with pytest.raises(TypeError) as excinfo: dt1.observe("val", lambda change: change, "foo") assert "int" in excinfo.exconly() @pytest.fixture() def observed_atom(): """Observed atom used to test unobserve.""" a = DynamicAtom() ob1 = Observer() ob2 = Observer() a.observe(("val", "val2", "val3"), ob1.react) a.observe(("val", "val2", "val3"), ob2.react) return a, ob1, ob2 def test_unobserving_no_args(observed_atom): """Test removing all observers at once.""" a, _, _ = observed_atom a.unobserve() for m in a.members(): assert not a.has_observers(m) def test_unobserving_a_single_member(observed_atom): """Test removing the observers from a single member.""" a, _, _ = observed_atom assert a.has_observers("val") assert a.has_observers("val2") a.unobserve("val") assert not a.has_observers("val") assert a.has_observers("val2") def test_unobserving_multiple_members(observed_atom): """Test removing observers from multiple members.""" a, _, _ = observed_atom assert a.has_observers("val") assert a.has_observers("val2") a.unobserve(("val", "val2")) assert not a.has_observers("val") assert not a.has_observers("val2") assert a.has_observers("val3") def test_removing_specific_observer(observed_atom): """Test removing a specific observer from a member.""" a, ob1, ob2 = observed_atom assert a.has_observer("val", ob1.react) assert a.has_observer("val", ob2.react) a.unobserve("val", ob2.react) assert a.has_observer("val", ob1.react) assert not a.has_observer("val", ob2.react) def test_removing_specific_observer2(observed_atom): """Test removing a specific observer from multiple members.""" a, ob1, ob2 = observed_atom for m in ("val", "val2", "val3"): assert a.has_observer(m, ob1.react) assert a.has_observer(m, ob2.react) a.unobserve(("val", "val2"), ob2.react) for m in ("val", "val2"): assert a.has_observer(m, ob1.react) assert not a.has_observer("val", ob2.react) assert a.has_observer("val3", ob1.react) assert a.has_observer("val3", ob2.react) def test_wrong_args_unobserve(observed_atom): """Test handling of bad arguments to unobserve.""" a, _, _ = observed_atom # Too many args with pytest.raises(TypeError) as excinfo: a.unobserve("val", lambda change: change, 1) assert "2 arguments" in excinfo.exconly() # Non-iterable first arg with pytest.raises(TypeError) as excinfo: a.unobserve(1) assert "iterable" in excinfo.exconly() # Non-iterable first arg with callable with pytest.raises(TypeError) as excinfo: a.unobserve(1, lambda change: change) assert "iterable" in excinfo.exconly() # Non iterable fo string first arg with pytest.raises(TypeError) as excinfo: a.unobserve((1, 1)) assert "str" in excinfo.exconly() # Non iterable fo string first arg with callable with pytest.raises(TypeError) as excinfo: a.unobserve((1, 1), lambda change: change) assert "str" in excinfo.exconly() # Non callable second arg with pytest.raises(TypeError) as excinfo: a.unobserve("vsl", 1) assert "callable" in excinfo.exconly() def test_wrong_arg_to_has_observer(observed_atom): """Test non string arg to has_observer.""" a, _, _ = observed_atom with pytest.raises(TypeError) as excinfo: a.has_observer(1, lambda x: x, 1) assert "2 arguments" in excinfo.exconly() with pytest.raises(TypeError) as excinfo: a.has_observer(1, lambda x: x) assert "str" in excinfo.exconly() with pytest.raises(TypeError) as excinfo: a.has_observer("val", 1) assert "callable" in excinfo.exconly() def test_binding_event_signals(): """Test directly binding events and signals.""" class EventSignalTest(Atom): e = Event() s = Signal() counter = Int() def event_handler(change): change["object"].counter += 1 def signal_handler(obj): obj.counter += 2 est = EventSignalTest() est.e.bind(event_handler) est.s.connect(signal_handler) est.e = 1 est.s(est) assert est.counter == 3 est.e(1) est.s.emit(est) assert est.counter == 6 est.e.unbind(event_handler) est.s.disconnect(signal_handler) def test_modifying_dynamic_observers_in_callback(): """Test modifying the static observers in an observer.""" class InvalidObserver(object): """Silly callable which always evaluate to false.""" def __init__(self, active): self.active = active def __bool__(self): return self.active __nonzero__ = __bool__ def __call__(self, change): pass class ChangingAtom(Atom): val = Int() counter1 = Int() counter2 = Int() observer = Value() def react1(self, change): self.counter1 += 1 self.observer.active = False self.unobserve("val", self.react1) self.observe("val", self.react2) def react2(self, change): self.counter2 += 1 self.unobserve("val") self.observe("val", self.react1) ca = ChangingAtom() ca.observer = invalid_obs = InvalidObserver(True) ca.observe("val", invalid_obs) ca.observe("val", ca.react1) assert ca.has_observer("val", ca.react1) assert not ca.has_observer("val", ca.react2) ca.val = 1 assert ca.counter1 == 1 # Ensure the modification take place after notification dispatch is # complete assert ca.counter2 == 0 assert ca.has_observer("val", ca.react2) assert not ca.has_observer("val", ca.react1) assert not ca.has_observer("val", invalid_obs) ca.val += 1 assert ca.counter2 == 1 # Ensure the modification take place after notification dispatch is # complete assert ca.counter1 == 1 assert ca.has_observer("val", ca.react1) assert not ca.has_observer("val", ca.react2) # Test handling exception in the guard map that ensure that the # modifications to the observers occur after the notification dispatch def raising_observer(change): raise ValueError() ca.observe("val", raising_observer) with pytest.raises(ValueError): ca.val += 1 assert ca.counter1 == 2 # Ensure the modification take place after notification dispatch is # complete assert ca.counter2 == 1 assert ca.has_observer("val", ca.react2) # --- Notifications generation and handling @pytest.fixture def sd_observed_atom(): """Atom object with both static and dynamic observers.""" class Observer(object): def __init__(self): self.count = 0 def react(self, change): print(change) self.count += 1 class NotifTest(Atom): val = Value() count = Int() observer = Value() def __init__(self): super(NotifTest, self).__init__() self.observer = Observer() self.observe("val", self.observer.react) def _observe_val(self, change): print(change) self.count += 1 return NotifTest() def test_notification_on_creation_deletion(sd_observed_atom): """Test that observers are properly called on creation.""" # Create value based on default sd_observed_atom.val assert sd_observed_atom.count == 1 assert sd_observed_atom.observer.count == 1 del sd_observed_atom.val assert sd_observed_atom.count == 2 assert sd_observed_atom.observer.count == 2 sd_observed_atom.val assert sd_observed_atom.count == 3 assert sd_observed_atom.observer.count == 3 def test_notification_on_setting(sd_observed_atom): """Test that notifiers are called when setting a value.""" sd_observed_atom.val = 1 assert sd_observed_atom.count == 1 assert sd_observed_atom.observer.count == 1 # And also test update of values sd_observed_atom.val = 2 assert sd_observed_atom.count == 2 assert sd_observed_atom.observer.count == 2 def test_notification_on_setting_non_comparable_value(sd_observed_atom): """Test that notifiers are called when setting a value.""" o1 = NonComparableObject() sd_observed_atom.val = 0 assert sd_observed_atom.count == 1 assert sd_observed_atom.observer.count == 1 # And also test update of values sd_observed_atom.val = 1 assert sd_observed_atom.count == 2 assert sd_observed_atom.observer.count == 2 # No notification on equal assignment sd_observed_atom.val = 1 assert sd_observed_atom.count == 2 assert sd_observed_atom.observer.count == 2 # Check notification on invalid comparison sd_observed_atom.val = o1 assert sd_observed_atom.count == 3 assert sd_observed_atom.observer.count == 3 # Check no notification on equal value assignment sd_observed_atom.val = o1 assert sd_observed_atom.count == 3 assert sd_observed_atom.observer.count == 3 def test_enabling_disabling_notifications(sd_observed_atom): """Test enabling/disabling notification on an atom.""" assert sd_observed_atom.notifications_enabled() sd_observed_atom.val = 1 assert sd_observed_atom.count == 1 assert sd_observed_atom.observer.count == 1 sd_observed_atom.set_notifications_enabled(False) sd_observed_atom.val = 0 assert sd_observed_atom.count == 1 assert sd_observed_atom.observer.count == 1 with pytest.raises(TypeError): sd_observed_atom.set_notifications_enabled("") sd_observed_atom.__sizeof__() # check that observers do not cause issues def test_manually_notifying(sd_observed_atom): """Test manual notifications""" nt = sd_observed_atom ob = nt.observer # Check both static and dynamic notifiers are called nt.val = 1 assert ob.count == 1 assert nt.count == 1 # Check only dynamic notifiers are called nt.notify("val", {"name": "val"}) assert ob.count == 2 assert nt.count == 1 # Check that only static notifiers are called type(nt).val.notify(nt, {}) assert ob.count == 2 assert nt.count == 2 # Check that notification suppression does work ob.count = 0 nt.count = 0 with nt.suppress_notifications(): nt.val += 1 assert not nt.count and not ob.count # Check bad argument with pytest.raises(TypeError) as excinfo: nt.notify() assert "1 argument" in excinfo.exconly() with pytest.raises(TypeError) as excinfo: nt.notify(1) assert "str" in excinfo.exconly() with pytest.raises(TypeError) as excinfo: type(nt).val.notify(1) assert "CAtom" in excinfo.exconly() def test_event_notification(sd_observed_atom): """Check that Event do call both static and dynamic observers.""" class EventAtom(type(sd_observed_atom)): val = Event() def _observe_val(self, change): self.count += 1 ea = EventAtom() ea.val = 1 assert ea.count == 1 assert ea.observer.count == 1 def test_signal_notification(sd_observed_atom): """Check that Signal do call both static and dynamic observers.""" class SignalAtom(type(sd_observed_atom)): val = Signal() def _observe_val(self, change): self.count += 1 sa = SignalAtom() sa.val(1) assert sa.count == 1 assert sa.observer.count == 1 def test_static_observer_container_change_type(): """Test observing a single member from a single instance.""" class Widget(Atom): items = ContainerList() changes = [] def react(change): changes.append(change) Widget.items.add_static_observer(react, ChangeType.CREATE | ChangeType.UPDATE) w = Widget() w.items = [] assert len(changes) == 1 assert changes[0]["type"] == "create" changes.clear() w.items.append(1) assert len(changes) == 0 # Container ignored Widget.items.add_static_observer(react, ChangeType.CONTAINER) w.items.append(1) assert len(changes) == 1 assert changes[0]["type"] == "container" changes.clear() w.items = [] assert len(changes) == 0 Widget.items.add_static_observer(react, ChangeType.UPDATE | ChangeType.CONTAINER) w.items = [1, 2] assert len(changes) == 1 assert changes[0]["type"] == "update" def test_observe_decorator_change_type(): """Test observing a single member from a single instance.""" changes = [] change_types = ChangeType.ANY & ~(ChangeType.CREATE) class Widget(Atom): items = ContainerList() @observe("items", change_types=change_types) def _on_items_change(self, change): changes.append(change) w = Widget() w.items assert len(changes) == 0 w.items.append(1) assert len(changes) == 1 assert changes[0]["type"] == "container" changes.clear() del w.items assert len(changes) == 1 assert changes[0]["type"] == "delete" changes.clear() w.items # Create default assert len(changes) == 0 w.items = [2] # Update assert len(changes) == 1 assert changes[0]["type"] == "update" atom-0.12.1/tests/test_post_behaviors.py000066400000000000000000000133021506756731600203540ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Test post_validate/get/set behaviors All of them have the following handlers: no_op_handler: Do nothing delegate_handler: not tested here (see test_delegate.py) object_method_old_new_handler: method defined on the Atom object object_method_name_old_new_handler: method defined on the Atom object taking the member name as first arg member_method_object_old_new_handler: Method defined on a Member subclass """ import pytest from atom.api import Int, PostGetAttr, PostSetAttr, PostValidate GET_MEMBER_METHOD_SRC = """ from atom.api import Atom class TrackedInt(Int): def __init__(self): super(TrackedInt, self).__init__() mode = PostGetAttr.{mode} self.set_post_getattr_mode(mode, 'post_getattr') def post_getattr(self, obj, value): obj.counter += 1 class PostAtom(Atom): mi = TrackedInt() counter = Int() """ GET_OBJECT_METHOD_SRC = """ from atom.api import Atom class PostAtom(Atom): mi = Int() counter = Int() def _post_getattr_mi(self, value): self.counter += 1 """ GET_OBJECT_METHOD_NAME_SRC = """ from atom.api import Atom class PostAtom(Atom): mi = Int() mi.set_post_getattr_mode(getattr(PostGetAttr, 'ObjectMethod_NameValue'), 'post_getattr_mi') counter = Int() def post_getattr_mi(self, name, value): self.counter += 1 """ @pytest.mark.parametrize( "mode", ( "NoOp", "ObjectMethod_Value", "ObjectMethod_NameValue", "MemberMethod_ObjectValue", ), ) def test_post_getattr(mode): """Test the post_getattr_behaviors.""" namespace = {} namespace.update(globals()) src = ( GET_MEMBER_METHOD_SRC if mode in ("MemberMethod_ObjectValue", "NoOp") else GET_OBJECT_METHOD_SRC if mode == "ObjectMethod_Value" else GET_OBJECT_METHOD_NAME_SRC ) src = src.format(mode=mode) print(src) exec(src, namespace) PostAtom = namespace["PostAtom"] # Test subclassed member pot = PostAtom() mi = pot.get_member("mi") assert mi.post_getattr_mode[0] == getattr(PostGetAttr, mode) pot.mi assert pot.counter == (1 if mode != "NoOp" else 0) # Test do_post_*** method func = getattr(mi, "do_post_getattr") func(pot, 2) assert pot.counter == (2 if mode != "NoOp" else 0) MEMBER_METHOD_SRC = """ from atom.api import Atom class TrackedInt(Int): def __init__(self): super(TrackedInt, self).__init__() mode = enum.{mode} self.set_post_{operation}_mode(mode, 'post_{operation}') def post_{operation}(self, obj, old, new): obj.counter += 1 class PostAtom(Atom): mi = TrackedInt() counter = Int() """ OBJECT_METHOD_SRC = """ from atom.api import Atom class PostAtom(Atom): mi = Int() counter = Int() def _post_{operation}_mi(self, old, new): self.counter += 1 """ OBJECT_METHOD_NAME_SRC = """ from atom.api import Atom class PostAtom(Atom): mi = Int() mi.set_post_{operation}_mode(getattr(enum, 'ObjectMethod_NameOldNew'), 'post_{operation}_mi') counter = Int() def post_{operation}_mi(self, name, old, new): self.counter += 1 """ @pytest.mark.parametrize( "operation, enum", [("setattr", PostSetAttr), ("validate", PostValidate)] ) @pytest.mark.parametrize( "mode", ( "NoOp", "ObjectMethod_OldNew", "ObjectMethod_NameOldNew", "MemberMethod_ObjectOldNew", ), ) def test_post_setattr_validate(operation, enum, mode): """Test the post_setattr/validate behaviors.""" namespace = {"enum": enum} namespace.update(globals()) src = ( MEMBER_METHOD_SRC if mode in ("MemberMethod_ObjectOldNew", "NoOp") else OBJECT_METHOD_SRC if mode == "ObjectMethod_OldNew" else OBJECT_METHOD_NAME_SRC ) src = src.format(operation=operation, mode=mode) print(src) exec(src, namespace) PostAtom = namespace["PostAtom"] # Test subclassed member pot = PostAtom() mi = pot.get_member("mi") assert getattr(mi, "post_{}_mode".format(operation))[0] == getattr(enum, mode) pot.mi = 2 assert pot.counter == (1 if mode != "NoOp" else 0) # Test do_post_*** method func = getattr(mi, "do_post_{}".format(operation)) func(pot, 2, 3) assert pot.counter == (2 if mode != "NoOp" else 0) @pytest.mark.parametrize( "operation, mode, msg", [ ("getattr", PostGetAttr.ObjectMethod_Value, "str"), ("getattr", PostGetAttr.ObjectMethod_NameValue, "str"), ("getattr", PostGetAttr.MemberMethod_ObjectValue, "str"), ("setattr", PostSetAttr.ObjectMethod_OldNew, "str"), ("setattr", PostSetAttr.ObjectMethod_NameOldNew, "str"), ("setattr", PostSetAttr.MemberMethod_ObjectOldNew, "str"), ("validate", PostValidate.ObjectMethod_OldNew, "str"), ("validate", PostValidate.ObjectMethod_NameOldNew, "str"), ("validate", PostValidate.MemberMethod_ObjectOldNew, "str"), ], ) def test_wrong_argument_in_mode_setting(operation, mode, msg): """Test handling wrong argument types when setting mode.""" m = Int() with pytest.raises(TypeError) as excinfo: getattr(m, "set_post_{}_mode".format(operation))(mode, 1) assert msg in excinfo.exconly() atom-0.12.1/tests/test_property.py000066400000000000000000000126071506756731600172200ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Test the property and cached property member""" import pytest from atom.api import ( Atom, GetAttr, Int, Property, SetAttr, Value, cached_property, observe, ) from atom.catom import DelAttr, reset_property def test_property1(): """Test defining a property using the arguments.""" def get_function(obj): return obj.i def set_function(obj, value): obj.i = value def del_function(obj): del obj.i class PropertyTest(Atom): p = Property(get_function, set_function, del_function) i = Int() assert not PropertyTest.p.cached assert PropertyTest.p.fget is get_function assert PropertyTest.p.fset is set_function assert PropertyTest.p.fdel is del_function pt = PropertyTest() assert pt.p == 0 pt.p = 10 assert pt.i == 10 assert pt.p == 10 del pt.p assert pt.p == 0 def test_property2(): """Test defining a property using the decorators.""" class PropertyTest(Atom): p = Property() i = Int() @p.getter def get_function(obj): return obj.i @p.setter def set_function(obj, value): obj.i = value @p.deleter def del_function(obj): del obj.i pt = PropertyTest() assert pt.p == 0 pt.p = 10 assert pt.i == 10 assert pt.p == 10 del pt.p assert pt.p == 0 def test_property3(): """Test defining a property mangled method names.""" class PropertyTest(Atom): p = Property() i = Int() def _get_p(self): return self.i def _set_p(self, value): self.i = value def _del_p(self): del self.i pt = PropertyTest() assert pt.p == 0 pt.p = 10 assert pt.i == 10 assert pt.p == 10 del pt.p assert pt.p == 0 def test_property4(): """Test handling missing function(fget, fset, fdel)""" class PropertyTest(Atom): p = Property() pt = PropertyTest() with pytest.raises(AttributeError): pt.p with pytest.raises(AttributeError): pt.p = 1 with pytest.raises(AttributeError): del pt.p def test_cached_property(): """Test using a cached property.""" class PropertyTest(Atom): i = Int() @cached_property def prop(self): self.i += 1 return self.i assert PropertyTest.prop.cached pt = PropertyTest() assert pt.prop == 1 assert pt.prop == 1 pt.get_member("prop").reset(pt) assert pt.prop == 2 def test_enforce_read_only_cached_property(): """Check a cached property has to be read-only.""" def get(self): pass def set(self, value): pass with pytest.raises(ValueError): Property(get, set, cached=True) with pytest.raises(ValueError): p = Property(cached=True) p.setter(set) def test_observed_property(): """Test observing a property.""" class NonComparableObject: def __eq__(self, other): raise ValueError() def __add__(self, other): return other + 5 class PropertyTest(Atom): i = Value(0) counter = Int() prop = Property() @prop.getter def _prop(self): self.i += 1 return self.i @observe("prop") def observe_cp(self, change): self.counter += 1 pt = PropertyTest() assert pt.prop == 1 assert pt.prop == 2 pt.i = NonComparableObject() pt.observe("prop", pt.observe_cp) pt.get_member("prop").reset(pt) assert pt.counter == 2 assert pt.prop == 7 def test_wrong_reset_arguments(): """Test the handling of wrong arguments in reset.""" prop = Property() with pytest.raises(TypeError) as excinfo: reset_property() assert "2 arguments" in excinfo.exconly() with pytest.raises(TypeError) as excinfo: reset_property(None, None) assert "Member" in excinfo.exconly() with pytest.raises(TypeError) as excinfo: prop.reset(None) assert "CAtom" in excinfo.exconly() with pytest.raises(SystemError) as excinfo: prop.reset(Atom()) assert "invalid member index" in excinfo.exconly() @pytest.mark.parametrize( "mode, func", [ (GetAttr, "set_getattr_mode"), (SetAttr, "set_setattr_mode"), (DelAttr, "set_delattr_mode"), ], ) def test_property_mode_args_validation(mode, func): """Test that a delegator properly validate the arguments when setting mode.""" with pytest.raises(TypeError) as excinfo: getattr(Property(), func)(getattr(mode, "Property"), 1) assert "callable or None" in excinfo.exconly() def test_property_getstate_mode(): def get_function(obj): return obj.i def set_function(obj, value): obj.i = value def del_function(obj): del obj.i p = Property(get_function) assert p.do_should_getstate(Atom()) is False p2 = Property(get_function, set_function) assert p2.do_should_getstate(Atom()) is True atom-0.12.1/tests/test_set_behaviors.py000066400000000000000000000127401506756731600201670ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Test the set behaviors no_op_handler: do nothing on setting slot_handler: standard one tested through other tests (post_set, ...) constant_handler: prevent to set a value read_only_handler: allow a single set event_handler: not tested here (see test_observe.py) signal_handler: siganls are not settable delegate_handler: not tested here (see test_delegate.py) property_handler: not tested here (see test_property.py) call_object_object_value_handler: use a custom function call_object_object_name_value_handler: use a custom function object_method_value_handler: use an object method object_method_name_value_handler: use an object method member_method_object_value_handler: method defined on a Member subclass """ import pytest from atom.api import Atom, Constant, Int, ReadOnly, SetAttr, Signal @pytest.mark.parametrize( "member, mode", [(Signal(), "Signal"), (Constant(1), "Constant")] ) def test_unsettable(member, mode): """Test that unsettable members do raise the proper error.""" class Unsettable(Atom): m = member u = Unsettable() assert u.get_member("m").setattr_mode[0] == getattr(SetAttr, mode) with pytest.raises(TypeError) as excinfo: u.m = 1 assert mode.lower() in excinfo.exconly() @pytest.mark.parametrize("member, mode", [(Int(), "Slot"), (ReadOnly(), "ReadOnly")]) def test_wrong_index_value(member, mode): """Test handling wrong index This should never happen save if the user manipulate the index. """ class Unsettable(Atom): m = member Unsettable.m.set_index(100) u = Unsettable() assert u.get_member("m").setattr_mode[0] == getattr(SetAttr, mode) with pytest.raises(AttributeError) as excinfo: u.m = 1 assert "'m'" in excinfo.exconly() def test_read_only_behavior(): """Test the behavior of read only member.""" class ReadOnlyTest(Atom): r = ReadOnly() rt = ReadOnlyTest() rt.r = 1 assert rt.r == 1 with pytest.raises(TypeError) as excinfo: rt.r = 2 assert "read only" in excinfo.exconly() def test_no_op(): """Test the no-op behavior.""" class Unsettable(Atom): m = Constant("1") m.set_setattr_mode(SetAttr.NoOp, None) u = Unsettable() u.m = None assert u.m == "1" def co_ov_factory(): """Factory for the CallObject_ObjectValue behavior.""" def custom_set(obj, value): obj.get_member("mi").set_slot(obj, value % 2) class SetTest(Atom): mi = Int() mi.set_setattr_mode(SetAttr.CallObject_ObjectValue, custom_set) return SetTest def co_onv_factory(): """Factory for the CallObject_ObjectNameValue behavior.""" def custom_set(obj, name, value): obj.get_member(name).set_slot(obj, value % 2) class SetTest(Atom): mi = Int() mi.set_setattr_mode(SetAttr.CallObject_ObjectNameValue, custom_set) return SetTest def om_v_factory(): """Factory for the ObjectMethod_Value behavior.""" class SetTest(Atom): mi = Int() mi.set_setattr_mode(SetAttr.ObjectMethod_Value, "custom_set") def custom_set(self, value): self.get_member("mi").set_slot(self, value % 2) return SetTest def om_nv_factory(): """Factory for the ObjecMethod_NameValue behavior.""" class SetTest(Atom): mi = Int() mi.set_setattr_mode(SetAttr.ObjectMethod_NameValue, "custom_set") def custom_set(self, name, value): self.get_member(name).set_slot(self, value % 2) return SetTest def mm_ov_factory(): """Factory for the MemberMethod_ObjectValue behavior.""" class ModuloInt(Int): def __init__(self): super(ModuloInt, self).__init__() mode = SetAttr.MemberMethod_ObjectValue self.set_setattr_mode(mode, "set") def set(self, obj, value): self.set_slot(obj, value % 2) class SetTest(Atom): mi = ModuloInt() return SetTest @pytest.mark.parametrize( "mode, factory", [ ("CallObject_ObjectValue", co_ov_factory), ("CallObject_ObjectNameValue", co_onv_factory), ("ObjectMethod_Value", om_v_factory), ("ObjectMethod_NameValue", om_nv_factory), ("MemberMethod_ObjectValue", mm_ov_factory), ], ) def test_member_set_behaviors(mode, factory): """Test defining set in a Member subclass""" pvt = factory()() mi = pvt.get_member("mi") assert mi.setattr_mode[0] == getattr(SetAttr, mode) pvt.mi = 2 assert pvt.mi == 0 pvt.mi = 3 assert pvt.mi == 1 mi.do_setattr(pvt, 2) assert pvt.mi == 0 mi.do_setattr(pvt, 3) assert pvt.mi == 1 @pytest.mark.parametrize( "mode, msg", [ ("CallObject_ObjectValue", "callable"), ("CallObject_ObjectNameValue", "callable"), ("ObjectMethod_Value", "str"), ("ObjectMethod_NameValue", "str"), ("MemberMethod_ObjectValue", "str"), ], ) def test_member_set_behaviors_wrong_args(mode, msg): """Test handling bad arguments to set_setattr_mode""" m = Int() with pytest.raises(TypeError) as excinfo: m.set_setattr_mode(getattr(SetAttr, mode), 1) assert msg in excinfo.exconly() atom-0.12.1/tests/test_signalconnector.py000066400000000000000000000030771506756731600205250ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2018-2024, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Test the signal connectors.""" import gc import operator import sys from collections import Counter import pytest from atom.api import Atom, Signal def test_signalconnector_lifecycle(): """Test creating and destroying an event binder. We create enough event binder to exceed the freelist length and fully deallocate some. """ class SignalAtom(Atom): s = Signal() signal_connectors = [SignalAtom.s for i in range(512)] for i, e in enumerate(signal_connectors): signal_connectors[i] = None del e gc.collect() atom = SignalAtom() sc = atom.s # Under Python 3.9+ heap allocated type instance keep a reference to the # type referents = [SignalAtom.s, atom, type(sc)] assert Counter(gc.get_referents(sc)) == Counter(referents) def test_signalconnector_cmp(): """Test comparing event binders.""" class EventAtom(Atom): s1 = Signal() s2 = Signal() a = EventAtom() assert a.s1 == a.s1 assert not a.s1 == a.s2 assert not a.s1 == 1 if sys.version_info >= (3,): for op in ("lt", "le", "gt", "ge"): with pytest.raises(TypeError): getattr(operator, op)(a.s1, 1) atom-0.12.1/tests/test_typing_utils.py000066400000000000000000000044541506756731600200670ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Test typing utilities.""" from collections.abc import Iterable from typing import Dict, List, Literal, NewType, Optional, Set, Tuple, TypeVar, Union import pytest from atom.typing_utils import extract_types, is_optional T = TypeVar("T") U = TypeVar("U", bound=int) UU = TypeVar("UU", bound=Union[int, str]) V = TypeVar("V", int, float) W = TypeVar("W", contravariant=True) NT = NewType("NT", int) NNT = NewType("NNT", NT) @pytest.mark.parametrize( "ty, outputs", [ (Tuple[int], (tuple,)), (List[int], (list,)), (Dict[str, int], (dict,)), (Set[int], (set,)), (Optional[int], (int, type(None))), (Union[int, str], (int, str)), (Union[int, Optional[str]], (int, str, type(None))), (Union[int, Union[str, bytes]], (int, str, bytes)), (list[int], (list,)), (dict[str, int], (dict,)), (set[int], (set,)), (Iterable[int], (Iterable,)), (int | str, (int, str)), (NT, (int,)), (NNT, (int,)), ], ) def test_extract_types(ty, outputs): assert extract_types(ty) == outputs def test_extract_types_for_type_vars(): assert extract_types(T) == (object,) assert extract_types(U) == (int,) assert extract_types(UU) == (int, str) with pytest.raises(ValueError) as e: extract_types(V) assert "Constraints" in e.exconly() with pytest.raises(ValueError) as e: extract_types(W) assert "contravariant" in e.exconly() @pytest.mark.parametrize( "ty, outputs", [ (Optional[int], (True, (int,))), (Union[int, str], (False, (int, str))), (Union[int, Optional[str]], (True, (int, str))), ], ) def test_is_optional(ty, outputs): assert is_optional(extract_types(ty)) == outputs def test_reject_str_annotations(): with pytest.raises(TypeError): extract_types("int") def test_reject_literal(): with pytest.raises(TypeError): extract_types(Literal[1]) atom-0.12.1/tests/test_validators.py000066400000000000000000000344301506756731600175020ustar00rootroot00000000000000# ------------------------------------------------------------------------------------------------------ # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # ------------------------------------------------------------------------------------------------------ """Tests for member validation handlers: no_op_handler bool_handler int_handler int_promote_handler long_handler long_promote_handler float_handler float_promote_handler str_handler str_promote_handler unicode_handler unicode_promote_handler tuple_handler fixed_tuple_handler list_handler container_list_handler set_handler dict_handler instance_handler typed_handler subclass_handler enum_handler callable_handler float_range_handler range_handler coerced_handler delegate_handler: not tested here object_method_old_new_handler: used when defining validate on Atom subclass object_method_name_old_new_handler: unused as far as I can tell member_method_object_old_new_handler: used in ForwardType/Instance/Subclass """ import sys from typing import List as TList, Optional, Sequence, Set as TSet, Union import pytest from atom.api import ( Atom, Bool, Bytes, Callable, CAtom, Coerced, Constant, ContainerList, Delegator, Dict, Enum, Event, FixedTuple, Float, FloatRange, ForwardInstance, ForwardSubclass, ForwardTyped, Instance, Int, List, Range, ReadOnly, Set, Str, Subclass, Tuple, Typed, Validate, Value, ) def test_no_op_validation(): """Test the no-op handler.""" a = Atom() m = Value() m.set_validate_mode(Validate.NoOp, None) for value in (1, 1.0, "", [], {}): assert m.do_validate(a, None, value) == value def c(x: object) -> int: return int(str(x), 2) @pytest.mark.parametrize( "member, set_values, values, raising_values", [ (Value(), ["a", 1, None], ["a", 1, None], []), (ReadOnly(int), [1], [1], [1.0]), (Bool(), [True, False], [True, False], "r"), (Int(strict=True), [1], [1], [1.0]), (Int(strict=False), [1, 1.0, (1)], 3 * [1], ["a"]), (Range(0, 2), [0, 2], [0, 2], [-1, 3, ""]), (Range(2, 0), [0, 2], [0, 2], [-1, 3]), (Range(0), [0, 3], [0, 3], [-1]), (Range(high=2), [-1, 2], [-1, 2], [3]), ( Range(sys.maxsize, sys.maxsize + 2), [sys.maxsize, sys.maxsize + 2], [sys.maxsize, sys.maxsize + 2], [sys.maxsize - 1, sys.maxsize + 3], ), (Float(), [1, (1), 1.1], [1.0, 1.0, 1.1], [""]), (Float(strict=True), [1.1], [1.1], [1]), (FloatRange(0.0, 0.5), [0.0, 0.5], [0.0, 0.5], [-0.1, 0.6]), (FloatRange(0.5, 0.0), [0.0, 0.5], [0.0, 0.5], [-0.1, 0.6]), (FloatRange(0.0), [0.0, 0.6], [0.0, 0.6], [-0.1, ""]), (FloatRange(high=0.5), [-0.3, 0.5], [-0.3, 0.5], [0.6]), (FloatRange(1.0, 10.0, strict=True), [1.0, 3.7], [1.0, 3.7], [2, 4, 0, -11]), (FloatRange(low=0, strict=False), [1, 25], [1.0, 25.0], [-1, -10.0]), (Bytes(strict=False), [b"a", "a"], [b"a"] * 2, [1]), (Bytes(), [b"a"], [b"a"], ["a"]), (Str(strict=False), [b"a", "a"], ["a"] * 2, [1]), (Str(), ["a"], ["a"], [b"a"]), (Enum(1, 2, "a"), [1, 2, "a"], [1, 2, "a"], [3]), (Callable(), [int, None], [int, None], [1]), # 3.9 subs and 3.10 union tests in test_typing_utils are sufficient (Coerced(set), [{1}, [1], (1,)], [{1}] * 3, [1]), (Coerced(int, coercer=c), ["101"], [5], []), (Coerced((int, float), coercer=c), ["101"], [5], []), (Coerced(int, coercer=lambda x: []), [], [], [""]), # type: ignore (Coerced(TSet[int]), [{1}, [1], (1,)], [{1}] * 3, [1]), (Tuple(), [(1,)], [(1,)], [[1]]), (Tuple(Int()), [(1,)], [(1,)], [(1.0,)]), (Tuple(int), [(1,)], [(1,)], [(1.0,), (None,)]), (Tuple(TSet[int]), [({1},)], [({1},)], [(1.0,), (None,)]), (Tuple(Optional[int]), [(1, None)], [(1, None)], [("",)]), (FixedTuple(Int()), [(1,)], [(1,)], [(None,), (1, 2)]), (FixedTuple(Int(), Str()), [(1, "")], [(1, "")], [(None,), (1, 2)]), (FixedTuple(int), [(1,)], [(1,)], [(None,), (1, 2)]), (FixedTuple(TSet[int]), [({1},)], [({1},)], [(None,), (1, 2)]), (List(), [[1]], [[1]], [(1,)]), (List(Int()), [[1]], [[1]], [[1.0]]), (List(float), [[1.0]], [[1.0]], [[1], [None]]), (List((int, float)), [[1, 1.0]], [[1, 1.0]], [[""]]), (List(TSet[int]), [[{1}]], [[{1}]], [[1], [None]]), (List(Optional[int]), [[1, None]], [[1, None]], [[""]]), (ContainerList(), [[1]], [[1]], [(1,)]), (ContainerList(Int()), [[1]], [[1]], [[1.0]]), (ContainerList(float), [[1.0]], [[1.0]], [[1], [None]]), (ContainerList((int, float)), [[1, 1.0]], [[1, 1.0]], [[""]]), (ContainerList(TSet[int]), [[{1}]], [[{1}]], [[1], [None]]), (ContainerList(Optional[int]), [[1, None]], [[1, None]], [[""]]), (Set(), [{1}], [{1}], [()]), (Set(Int()), [{1}], [{1}], [{""}]), (Set(item=Int()), [{1}], [{1}], [{""}]), (Set(int), [{1}], [{1}], [{""}, {None}]), (Set(Optional[int]), [{1, None}], [{1, None}], [[1]]), (Dict(), [{1: 2}], [{1: 2}], [()]), (Dict(Int()), [{1: 2}], [{1: 2}], [{"": 2}]), (Dict(value=Int()), [{1: 2}], [{1: 2}], [{2: ""}]), (Dict(int, int), [{1: 2}], [{1: 2}], [{"": 2}, {2: ""}, {None: 2}, {2: None}]), (Dict(Optional[int]), [{None: 1, 1: 2}], [{None: 1, 1: 2}], [[1]]), (Instance((int, float)), [1, 2.0, None], [1, 2.0, None], [""]), (Instance((int, float), ()), [1, 2.0], [1, 2.0], ["", None]), ( Instance((int, float), (), optional=True), [1, 2.0, None], [1, 2.0, None], [""], ), ( Instance(Optional[Union[int, float]], ()), [1, 2.0, None], [1, 2.0, None], [""], ), (Instance((int, float), optional=False), [1, 2.0], [1, 2.0], [None, ""]), (Instance(TList[int], optional=False), [[1]], [[1]], [None, ""]), (Instance(Sequence[int], optional=False), [[1]], [[1]], [None, 1]), (ForwardInstance(lambda: (int, float)), [1, 2.0, None], [1, 2.0, None], [""]), (ForwardInstance(lambda: (int, float), ()), [1, 2.0], [1, 2.0], ["", None]), ( ForwardInstance(lambda: (int, float), (), optional=True), [1, 2.0, None], [1, 2.0, None], [""], ), ( ForwardInstance(lambda: Optional[Union[int, float]], ()), [1, 2.0, None], [1, 2.0, None], [""], ), ( ForwardInstance(lambda: (int, float), optional=False), [1, 2.0], [1, 2.0], [None, ""], ), (Typed(float), [1.0, None], [1.0, None], [1]), (Typed(float, ()), [1.0], [1.0], [1, None]), (Typed(float, (), optional=True), [1.0, None], [1.0, None], [1]), (Typed(Optional[float], ()), [1.0, None], [1.0, None], [1]), (Typed(float, optional=False), [1.0], [1.0], [1, None]), (ForwardTyped(lambda: float), [1.0, None], [1.0, None], [1]), (ForwardTyped(lambda: float, ()), [1.0], [1.0], [1, None]), (ForwardTyped(lambda: float, (), optional=True), [1.0, None], [1.0, None], [1]), (ForwardTyped(lambda: float, optional=False), [1.0], [1.0], [1, None]), (Subclass(CAtom), [Atom], [Atom], [int, 1]), (Subclass((CAtom, float)), [Atom], [Atom], [int, 1]), (ForwardSubclass(lambda: CAtom), [Atom], [Atom], [int]), ], ) def test_validation_modes(member, set_values, values, raising_values): """Test the validation modes.""" class MemberTest(Atom): m = member tester = MemberTest() for sv, v in zip(set_values, values): tester.m = sv assert tester.m == v for rv in raising_values: if isinstance(member, Int) and isinstance(rv, float) and rv > 2**32: error_type = OverflowError elif isinstance(member, Enum): error_type = ValueError elif isinstance(member, (Range, FloatRange)): error_type = (ValueError, TypeError) else: error_type = TypeError with pytest.raises(error_type): tester.m = rv @pytest.mark.parametrize( "members, value", [ ((List(Int()), List()), [1]), ((ContainerList(Int()), ContainerList()), [1]), ((Dict(Int(), Int()), Dict()), {1: 1}), ], ) def test_validating_container_subclasses(members, value): """Ensure that we can pass atom containers to members.""" class MemberTest(Atom): m1 = members[0] m2 = members[1] tester = MemberTest() tester.m1 = value tester.m2 = tester.m1 @pytest.mark.parametrize( "member, mode, arg, msg", [ (List(), "List", 1, "Member or None"), (Tuple(), "Tuple", 1, "Member or None"), (FixedTuple(int), "FixedTuple", 1, "tuple of types or Members"), (ContainerList(), "ContainerList", 1, "Member or None"), (Set(), "Set", 1, "Member or None"), (Dict(), "Dict", 1, "2-tuple of Member or None"), (Dict(), "Dict", (), "2-tuple of Member or None"), (Dict(), "Dict", (1, None), "2-tuple of Member or None"), (Dict(), "Dict", (None, 1), "2-tuple of Member or None"), (Typed(int), "Typed", 1, "type"), (Enum(1, 2), "Enum", 1, "sequence"), (FloatRange(), "FloatRange", 1, "2-tuple of float or None"), (FloatRange(), "FloatRange", (), "2-tuple of float or None"), (FloatRange(), "FloatRange", ("", None), "2-tuple of float or None"), (FloatRange(), "FloatRange", (0, 0), "2-tuple of float or None"), (FloatRange(), "FloatRangePromote", (0.0, ""), "2-tuple of float or None"), (Range(), "Range", 1, "2-tuple of int or None"), (Range(), "Range", (), "2-tuple of int or None"), (Range(), "Range", ("", None), "2-tuple of int or None"), (Range(), "Range", (None, ""), "2-tuple of int or None"), (Coerced(int), "Coerced", 1, "2-tuple of (type, callable)"), (Coerced(int), "Coerced", (), "2-tuple of (type, callable)"), (Coerced(int), "Coerced", (int, 1), "2-tuple of (type, callable)"), (Instance(int), "Instance", 1, "type or tuple of types"), (Instance(int), "Instance", (int, 1), "type or tuple of types"), (Subclass(int), "Instance", 1, "type or tuple of types"), (Subclass(int), "Instance", (int, 1), "type or tuple of types"), (Delegator(Int()), "Delegate", 1, "Member"), ], ) def test_handling_wrong_context(member, mode, arg, msg): """Test handling wrong args to members leading to wrong validate behaviors.""" with pytest.raises(TypeError) as excinfo: member.set_validate_mode(getattr(Validate, mode), arg) assert msg in excinfo.exconly() def test_event_validation(): """Test validating the payload of an Event.""" class EventValidationTest(Atom): ev_member = Event(Int()) ev_type = Event(int) evt = EventValidationTest() evt.ev_member = 1 evt.ev_type = 1 with pytest.raises(TypeError): evt.ev_member = 1.0 with pytest.raises(TypeError): evt.ev_type = 1.0 def test_constant_validation(): """Test validating a constant.""" class A(Atom): c = Constant(kind=int) def _default_c(self): return id(self) A().c class B(A): def _default_c(self): return str(super()._default_c()) with pytest.raises(TypeError): B().c def no_atom(): """Set a member with a no-op validator.""" class NoOpValAtom(Atom): v = Value() v.set_validate_mode(Validate.NoOp, None) return NoOpValAtom() def om_on_atom(): """Use an object method to customize validate.""" class ValidatorTest(Atom): v = Value(0) def _validate_v(self, old, new): if not isinstance(new, int): raise TypeError() if old is not None and new != old + 1: raise ValueError() return new return ValidatorTest() def om_non_atom(): """Use an object method taking the member name to customize validate.""" class ValidatorTest(Atom): v = Value(0) v.set_validate_mode(Validate.ObjectMethod_NameOldNew, "validate_v") def validate_v(self, name, old, new): if not isinstance(new, int): raise TypeError() if old is not None and new != old + 1: raise ValueError() return new return ValidatorTest() def mm_oon_atom(): """Use a member method to customize validate.""" class CustomValue(Value): def __init__(self): super(CustomValue, self).__init__(0) self.set_validate_mode(Validate.MemberMethod_ObjectOldNew, "validate") def validate(self, object, old, new): if not isinstance(new, int): raise TypeError() if old is not None and new != old + 1: raise ValueError() return new class ValidatorTest(Atom): v = CustomValue() return ValidatorTest() @pytest.mark.parametrize( "mode, factory", [ ("NoOp", no_atom), ("ObjectMethod_OldNew", om_on_atom), ("ObjectMethod_NameOldNew", om_non_atom), ("MemberMethod_ObjectOldNew", mm_oon_atom), ], ) def test_custom_validate(mode, factory): """Test specifying a specific validator in the Atom and using do_validate.""" v = factory() assert type(v).v.validate_mode[0] == getattr(Validate, mode) assert v.v == (0 if mode != "NoOp" else None) if mode == "NoOp": return v.v = 1 assert v.v == 1 with pytest.raises(TypeError): v.v = None assert v.v == 1 with pytest.raises(ValueError): v.v = 4 v_member = type(factory()).v with pytest.raises(TypeError): v_member.do_validate(v, 1, None) assert v.v == 1 with pytest.raises(ValueError): v_member.do_full_validate(v, 1, 4) with pytest.raises(TypeError) as excinfo: type(v).v.set_validate_mode(getattr(Validate, mode), 1) assert "str" in excinfo.exconly() atom-0.12.1/tests/test_version.py000066400000000000000000000010401506756731600170060ustar00rootroot00000000000000# -------------------------------------------------------------------------------------- # Copyright (c) 2013-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # -------------------------------------------------------------------------------------- """Tests getting the version.""" from atom import version def test_version(): """Test that nothing leaks from the module.""" assert hasattr(version, "__version__") atom-0.12.1/tests/type_checking/000077500000000000000000000000001506756731600165315ustar00rootroot00000000000000atom-0.12.1/tests/type_checking/test_annotations.yml000066400000000000000000000015521506756731600226530ustar00rootroot00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2022-2024, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. #------------------------------------------------------------------------------ - case: annotated_member parametrized: - member: List annotation: List[int] member_instance: List() member_type: atom.list.List[builtins.int] member_value_type: builtins.list[builtins.int] main: | import _io from typing import Type from atom.api import Atom, {{ member }} class A(Atom): m: {{ annotation }} = {{ member_instance }} reveal_type(A.m) # N: Revealed type is "{{ member_type }}" reveal_type(A().m) # N: Revealed type is "{{ member_value_type }}" atom-0.12.1/tests/type_checking/test_coerced.yml000066400000000000000000000064411506756731600217240ustar00rootroot00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. #------------------------------------------------------------------------------ - case: coerced_no_coercer parametrized: - member: Coerced member_instance: Coerced(int) member_type: atom.coerced.Coerced[builtins.int, builtins.int] member_value_type: builtins.int - member: Coerced member_instance: Coerced(int, ()) member_type: atom.coerced.Coerced[builtins.int, builtins.int] member_value_type: builtins.int - member: Coerced member_instance: Coerced(_io.StringIO, None, dict(initial_value=str(1))) member_type: atom.coerced.Coerced[_io.StringIO, _io.StringIO] member_value_type: _io.StringIO - member: Coerced member_instance: Coerced(_io.StringIO, kwargs=dict(initial_value=str(1))) member_type: atom.coerced.Coerced[_io.StringIO, _io.StringIO] member_value_type: _io.StringIO - member: Coerced member_instance: Coerced(_io.StringIO, factory=g) member_type: atom.coerced.Coerced[_io.StringIO, _io.StringIO] member_value_type: _io.StringIO main: | import _io from typing import Type from atom.api import Atom, {{ member }} def g() -> _io.StringIO: return _io.StringIO() class A(Atom): m = {{ member_instance }} reveal_type(A.m) # N: Revealed type is "{{ member_type }}" reveal_type(A().m) # N: Revealed type is "{{ member_value_type }}" # FIXME Those are broken on mypy 1.17 and it is unclear why # - case: coerced_coercer # parametrized: # - member: Coerced # member_instance: Coerced(_io.StringIO, coercer=coercer) # member_type: atom.coerced.Coerced[_io.StringIO, _io.StringIO | builtins.str] # member_value_type: _io.StringIO # - member: Coerced # member_instance: Coerced(_io.StringIO, (), coercer=coercer) # member_type: atom.coerced.Coerced[_io.StringIO, _io.StringIO | builtins.str] # member_value_type: _io.StringIO # - member: Coerced # member_instance: Coerced(_io.StringIO, None, dict(initial_value=str(1)), coercer=coercer) # member_type: atom.coerced.Coerced[_io.StringIO, _io.StringIO | builtins.str] # member_value_type: _io.StringIO # - member: Coerced # member_instance: Coerced(_io.StringIO, kwargs=dict(initial_value=str(1)), coercer=coercer) # member_type: atom.coerced.Coerced[_io.StringIO, _io.StringIO | builtins.str] # member_value_type: _io.StringIO # - member: Coerced # member_instance: Coerced(_io.StringIO, factory=g, coercer=coercer) # member_type: atom.coerced.Coerced[_io.StringIO, _io.StringIO | builtins.str] # member_value_type: _io.StringIO # main: | # import _io # from typing import Type # from atom.api import Atom, {{ member }} # def coercer(x: str) -> _io.StringIO: # return _io.StringIO(x) # def g() -> _io.StringIO: # return _io.StringIO() # class A(Atom): # m = {{ member_instance }} # reveal_type(A.m) # N: Revealed type is "{{ member_type }}" # reveal_type(A().m) # N: Revealed type is "{{ member_value_type }}"atom-0.12.1/tests/type_checking/test_delegator.yml000066400000000000000000000012431506756731600222610ustar00rootroot00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. #------------------------------------------------------------------------------ - case: delegator main: | from atom.api import Atom, Delegator, Int class A(Atom): i = Int(strict=False) m = Delegator(i) reveal_type(A.m) # N: Revealed type is "atom.delegator.Delegator[builtins.int, builtins.int | builtins.float]" reveal_type(A().m) # N: Revealed type is "builtins.int" atom-0.12.1/tests/type_checking/test_dict.yml000066400000000000000000000445771506756731600212570ustar00rootroot00000000000000# ------------------------------------------------------------------------------------------------------ # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # ------------------------------------------------------------------------------------------------------ - case: dict parametrized: # Dict untyped - member: Dict member_instance: Dict() member_type: atom.dict.Dict[Any, Any] member_value_type: builtins.dict[Any, Any] # Dict untyped - member: Dict member_instance: Dict(default=dict(a=1)) member_type: atom.dict.Dict[Any, Any] member_value_type: builtins.dict[Any, Any] # Dict with typed key - member: Dict member_instance: Dict(int) member_type: atom.dict.Dict[builtins.int, Any] member_value_type: builtins.dict[builtins.int, Any] - member: Dict member_instance: Dict((int,)) member_type: atom.dict.Dict[builtins.int, Any] member_value_type: builtins.dict[builtins.int, Any] - member: Dict member_instance: Dict((int, float)) member_type: atom.dict.Dict[builtins.int | builtins.float, Any] member_value_type: builtins.dict[builtins.int | builtins.float, Any] - member: Dict member_instance: Dict((int, float, str)) member_type: atom.dict.Dict[builtins.int | builtins.float | builtins.str, Any] member_value_type: builtins.dict[builtins.int | builtins.float | builtins.str, Any] - member: Dict, Int member_instance: Dict(Int()) member_type: atom.dict.Dict[builtins.int, Any] member_value_type: builtins.dict[builtins.int, Any] # Dict with typed value - member: Dict member_instance: Dict(None, int) member_type: atom.dict.Dict[Any, builtins.int] member_value_type: builtins.dict[Any, builtins.int] - member: Dict member_instance: Dict(None, (int,)) member_type: atom.dict.Dict[Any, builtins.int] member_value_type: builtins.dict[Any, builtins.int] - member: Dict member_instance: Dict(None, (int, float)) member_type: atom.dict.Dict[Any, builtins.int | builtins.float] member_value_type: builtins.dict[Any, builtins.int | builtins.float] - member: Dict member_instance: Dict(None, (int, float, str)) member_type: atom.dict.Dict[Any, builtins.int | builtins.float | builtins.str] member_value_type: builtins.dict[Any, builtins.int | builtins.float | builtins.str] - member: Dict, Int member_instance: Dict(None, Int()) member_type: atom.dict.Dict[Any, builtins.int] member_value_type: builtins.dict[Any, builtins.int] # Dict with typed value as keyword - member: Dict member_instance: Dict(value=int) member_type: atom.dict.Dict[Any, builtins.int] member_value_type: builtins.dict[Any, builtins.int] - member: Dict member_instance: Dict(value=(int,)) member_type: atom.dict.Dict[Any, builtins.int] member_value_type: builtins.dict[Any, builtins.int] - member: Dict member_instance: Dict(value=(int, float)) member_type: atom.dict.Dict[Any, builtins.int | builtins.float] member_value_type: builtins.dict[Any, builtins.int | builtins.float] - member: Dict member_instance: Dict(value=(int, float, str)) member_type: atom.dict.Dict[Any, builtins.int | builtins.float | builtins.str] member_value_type: builtins.dict[Any, builtins.int | builtins.float | builtins.str] - member: Dict, Int member_instance: Dict(value=Int()) member_type: atom.dict.Dict[Any, builtins.int] member_value_type: builtins.dict[Any, builtins.int] # Typed key and value # simple type as key - member: Dict member_instance: Dict(int, int) member_type: atom.dict.Dict[builtins.int, builtins.int] member_value_type: builtins.dict[builtins.int, builtins.int] - member: Dict member_instance: Dict(int, (int,)) member_type: atom.dict.Dict[builtins.int, builtins.int] member_value_type: builtins.dict[builtins.int, builtins.int] - member: Dict member_instance: Dict(int, (int, float)) member_type: atom.dict.Dict[builtins.int, builtins.int | builtins.float] member_value_type: builtins.dict[builtins.int, builtins.int | builtins.float] - member: Dict member_instance: Dict(int, (int, float, str)) member_type: atom.dict.Dict[builtins.int, builtins.int | builtins.float | builtins.str] member_value_type: builtins.dict[builtins.int, builtins.int | builtins.float | builtins.str] - member: Dict, Int member_instance: Dict(int, Int()) member_type: atom.dict.Dict[builtins.int, builtins.int] member_value_type: builtins.dict[builtins.int, builtins.int] # 1-tuple as key - member: Dict member_instance: Dict((int,), int) member_type: atom.dict.Dict[builtins.int, builtins.int] member_value_type: builtins.dict[builtins.int, builtins.int] - member: Dict member_instance: Dict((int,), (int,)) member_type: atom.dict.Dict[builtins.int, builtins.int] member_value_type: builtins.dict[builtins.int, builtins.int] - member: Dict member_instance: Dict((int,), (int, float)) member_type: atom.dict.Dict[builtins.int, builtins.int | builtins.float] member_value_type: builtins.dict[builtins.int, builtins.int | builtins.float] - member: Dict member_instance: Dict((int,), (int, float, str)) member_type: atom.dict.Dict[builtins.int, builtins.int | builtins.float | builtins.str] member_value_type: builtins.dict[builtins.int, builtins.int | builtins.float | builtins.str] - member: Dict, Int member_instance: Dict((int,), Int()) member_type: atom.dict.Dict[builtins.int, builtins.int] member_value_type: builtins.dict[builtins.int, builtins.int] # 2-tuple as key - member: Dict member_instance: Dict((int, str), int) member_type: atom.dict.Dict[builtins.int | builtins.str, builtins.int] member_value_type: builtins.dict[builtins.int | builtins.str, builtins.int] - member: Dict member_instance: Dict((int, str), (int,)) member_type: atom.dict.Dict[builtins.int | builtins.str, builtins.int] member_value_type: builtins.dict[builtins.int | builtins.str, builtins.int] - member: Dict member_instance: Dict((int, str), (int, float)) member_type: atom.dict.Dict[builtins.int | builtins.str, builtins.int | builtins.float] member_value_type: builtins.dict[builtins.int | builtins.str, builtins.int | builtins.float] - member: Dict member_instance: Dict((int, str), (int, float, str)) member_type: atom.dict.Dict[builtins.int | builtins.str, builtins.int | builtins.float | builtins.str] member_value_type: builtins.dict[builtins.int | builtins.str, builtins.int | builtins.float | builtins.str] - member: Dict, Int member_instance: Dict((int, str), Int()) member_type: atom.dict.Dict[builtins.int | builtins.str, builtins.int] member_value_type: builtins.dict[builtins.int | builtins.str, builtins.int] # 3-tuple as key - member: Dict member_instance: Dict((int, str, bytes), int) member_type: atom.dict.Dict[builtins.int | builtins.str | builtins.bytes, builtins.int] member_value_type: builtins.dict[builtins.int | builtins.str | builtins.bytes, builtins.int] - member: Dict member_instance: Dict((int, str, bytes), (int,)) member_type: atom.dict.Dict[builtins.int | builtins.str | builtins.bytes, builtins.int] member_value_type: builtins.dict[builtins.int | builtins.str | builtins.bytes, builtins.int] - member: Dict member_instance: Dict((int, str, bytes), (int, float)) member_type: atom.dict.Dict[builtins.int | builtins.str | builtins.bytes, builtins.int | builtins.float] member_value_type: builtins.dict[builtins.int | builtins.str | builtins.bytes, builtins.int | builtins.float] - member: Dict member_instance: Dict((int, str, bytes), (int, float, str)) member_type: atom.dict.Dict[builtins.int | builtins.str | builtins.bytes, builtins.int | builtins.float | builtins.str] member_value_type: builtins.dict[builtins.int | builtins.str | builtins.bytes, builtins.int | builtins.float | builtins.str] - member: Dict, Int member_instance: Dict((int, str, bytes), Int()) member_type: atom.dict.Dict[builtins.int | builtins.str | builtins.bytes, builtins.int] member_value_type: builtins.dict[builtins.int | builtins.str | builtins.bytes, builtins.int] # member as key - member: Dict, Int member_instance: Dict(Int(), int) member_type: atom.dict.Dict[builtins.int, builtins.int] member_value_type: builtins.dict[builtins.int, builtins.int] - member: Dict, Int member_instance: Dict(Int(), (int,)) member_type: atom.dict.Dict[builtins.int, builtins.int] member_value_type: builtins.dict[builtins.int, builtins.int] - member: Dict, Int member_instance: Dict(Int(), (int, float)) member_type: atom.dict.Dict[builtins.int, builtins.int | builtins.float] member_value_type: builtins.dict[builtins.int, builtins.int | builtins.float] - member: Dict, Int member_instance: Dict(Int(), (int, float, str)) member_type: atom.dict.Dict[builtins.int, builtins.int | builtins.float | builtins.str] member_value_type: builtins.dict[builtins.int, builtins.int | builtins.float | builtins.str] - member: Dict, Int member_instance: Dict(Int(), Int()) member_type: atom.dict.Dict[builtins.int, builtins.int] member_value_type: builtins.dict[builtins.int, builtins.int] main: | from atom.api import Atom, {{ member }} class A(Atom): m = {{ member_instance }} reveal_type(A.m) # N: Revealed type is "{{ member_type }}" reveal_type(A().m) # N: Revealed type is "{{ member_value_type }}" - case: dict_with_default parametrized: # Dict with typed key - member: Dict member_instance: Dict(int member_type: atom.dict.Dict[builtins.int, Any] member_value_type: builtins.dict[builtins.int, Any] - member: Dict member_instance: Dict((int,) member_type: atom.dict.Dict[builtins.int, Any] member_value_type: builtins.dict[builtins.int, Any] - member: Dict member_instance: Dict((int, float) member_type: atom.dict.Dict[builtins.int | builtins.float, Any] member_value_type: builtins.dict[builtins.int | builtins.float, Any] - member: Dict member_instance: Dict((int, float, str) member_type: atom.dict.Dict[builtins.int | builtins.float | builtins.str, Any] member_value_type: builtins.dict[builtins.int | builtins.float | builtins.str, Any] - member: Dict, Int member_instance: Dict(Int() member_type: atom.dict.Dict[builtins.int, Any] member_value_type: builtins.dict[builtins.int, Any] # Dict with typed value - member: Dict member_instance: Dict(None, int member_type: atom.dict.Dict[Any, builtins.int] member_value_type: builtins.dict[Any, builtins.int] - member: Dict member_instance: Dict(None, (int,) member_type: atom.dict.Dict[Any, builtins.int] member_value_type: builtins.dict[Any, builtins.int] - member: Dict member_instance: Dict(None, (int, float) member_type: atom.dict.Dict[Any, builtins.int | builtins.float] member_value_type: builtins.dict[Any, builtins.int | builtins.float] - member: Dict member_instance: Dict(None, (int, float, str) member_type: atom.dict.Dict[Any, builtins.int | builtins.float | builtins.str] member_value_type: builtins.dict[Any, builtins.int | builtins.float | builtins.str] - member: Dict, Int member_instance: Dict(None, Int() member_type: atom.dict.Dict[Any, builtins.int] member_value_type: builtins.dict[Any, builtins.int] # Dict with typed value as keyword - member: Dict member_instance: Dict(value=int member_type: atom.dict.Dict[Any, builtins.int] member_value_type: builtins.dict[Any, builtins.int] - member: Dict member_instance: Dict(value=(int,) member_type: atom.dict.Dict[Any, builtins.int] member_value_type: builtins.dict[Any, builtins.int] - member: Dict member_instance: Dict(value=(int, float) member_type: atom.dict.Dict[Any, builtins.int | builtins.float] member_value_type: builtins.dict[Any, builtins.int | builtins.float] - member: Dict member_instance: Dict(value=(int, float, str) member_type: atom.dict.Dict[Any, builtins.int | builtins.float | builtins.str] member_value_type: builtins.dict[Any, builtins.int | builtins.float | builtins.str] - member: Dict, Int member_instance: Dict(value=Int() member_type: atom.dict.Dict[Any, builtins.int] member_value_type: builtins.dict[Any, builtins.int] # Typed key ad value # simple type as key - member: Dict member_instance: Dict(int, int member_type: atom.dict.Dict[builtins.int, builtins.int] member_value_type: builtins.dict[builtins.int, builtins.int] - member: Dict member_instance: Dict(int, (int,) member_type: atom.dict.Dict[builtins.int, builtins.int] member_value_type: builtins.dict[builtins.int, builtins.int] - member: Dict member_instance: Dict(int, (int, float) member_type: atom.dict.Dict[builtins.int, builtins.int | builtins.float] member_value_type: builtins.dict[builtins.int, builtins.int | builtins.float] - member: Dict member_instance: Dict(int, (int, float, str) member_type: atom.dict.Dict[builtins.int, builtins.int | builtins.float | builtins.str] member_value_type: builtins.dict[builtins.int, builtins.int | builtins.float | builtins.str] - member: Dict, Int member_instance: Dict(int, Int() member_type: atom.dict.Dict[builtins.int, builtins.int] member_value_type: builtins.dict[builtins.int, builtins.int] # 1-tuple as key - member: Dict member_instance: Dict((int,), int member_type: atom.dict.Dict[builtins.int, builtins.int] member_value_type: builtins.dict[builtins.int, builtins.int] - member: Dict member_instance: Dict((int,), (int,) member_type: atom.dict.Dict[builtins.int, builtins.int] member_value_type: builtins.dict[builtins.int, builtins.int] - member: Dict member_instance: Dict((int,), (int, float) member_type: atom.dict.Dict[builtins.int, builtins.int | builtins.float] member_value_type: builtins.dict[builtins.int, builtins.int | builtins.float] - member: Dict member_instance: Dict((int,), (int, float, str) member_type: atom.dict.Dict[builtins.int, builtins.int | builtins.float | builtins.str] member_value_type: builtins.dict[builtins.int, builtins.int | builtins.float | builtins.str] - member: Dict, Int member_instance: Dict((int,), Int() member_type: atom.dict.Dict[builtins.int, builtins.int] member_value_type: builtins.dict[builtins.int, builtins.int] # 2-tuple as key - member: Dict member_instance: Dict((int, str), int member_type: atom.dict.Dict[builtins.int | builtins.str, builtins.int] member_value_type: builtins.dict[builtins.int | builtins.str, builtins.int] - member: Dict member_instance: Dict((int, str), (int,) member_type: atom.dict.Dict[builtins.int | builtins.str, builtins.int] member_value_type: builtins.dict[builtins.int | builtins.str, builtins.int] - member: Dict member_instance: Dict((int, str), (int, float) member_type: atom.dict.Dict[builtins.int | builtins.str, builtins.int | builtins.float] member_value_type: builtins.dict[builtins.int | builtins.str, builtins.int | builtins.float] - member: Dict member_instance: Dict((int, str), (int, float, str) member_type: atom.dict.Dict[builtins.int | builtins.str, builtins.int | builtins.float | builtins.str] member_value_type: builtins.dict[builtins.int | builtins.str, builtins.int | builtins.float | builtins.str] - member: Dict, Int member_instance: Dict((int, str), Int() member_type: atom.dict.Dict[builtins.int | builtins.str, builtins.int] member_value_type: builtins.dict[builtins.int | builtins.str, builtins.int] # 3-tuple as key - member: Dict member_instance: Dict((int, str, bytes), int member_type: atom.dict.Dict[builtins.int | builtins.str | builtins.bytes, builtins.int] member_value_type: builtins.dict[builtins.int | builtins.str | builtins.bytes, builtins.int] - member: Dict member_instance: Dict((int, str, bytes), (int,) member_type: atom.dict.Dict[builtins.int | builtins.str | builtins.bytes, builtins.int] member_value_type: builtins.dict[builtins.int | builtins.str | builtins.bytes, builtins.int] - member: Dict member_instance: Dict((int, str, bytes), (int, float) member_type: atom.dict.Dict[builtins.int | builtins.str | builtins.bytes, builtins.int | builtins.float] member_value_type: builtins.dict[builtins.int | builtins.str | builtins.bytes, builtins.int | builtins.float] - member: Dict member_instance: Dict((int, str, bytes), (int, float, str) member_type: atom.dict.Dict[builtins.int | builtins.str | builtins.bytes, builtins.int | builtins.float | builtins.str] member_value_type: builtins.dict[builtins.int | builtins.str | builtins.bytes, builtins.int | builtins.float | builtins.str] - member: Dict, Int member_instance: Dict((int, str, bytes), Int() member_type: atom.dict.Dict[builtins.int | builtins.str | builtins.bytes, builtins.int] member_value_type: builtins.dict[builtins.int | builtins.str | builtins.bytes, builtins.int] # member as key - member: Dict, Int member_instance: Dict(Int(), int member_type: atom.dict.Dict[builtins.int, builtins.int] member_value_type: builtins.dict[builtins.int, builtins.int] - member: Dict, Int member_instance: Dict(Int(), (int,) member_type: atom.dict.Dict[builtins.int, builtins.int] member_value_type: builtins.dict[builtins.int, builtins.int] - member: Dict, Int member_instance: Dict(Int(), (int, float) member_type: atom.dict.Dict[builtins.int, builtins.int | builtins.float] member_value_type: builtins.dict[builtins.int, builtins.int | builtins.float] - member: Dict, Int member_instance: Dict(Int(), (int, float, str) member_type: atom.dict.Dict[builtins.int, builtins.int | builtins.float | builtins.str] member_value_type: builtins.dict[builtins.int, builtins.int | builtins.float | builtins.str] - member: Dict, Int member_instance: Dict(Int(), Int() member_type: atom.dict.Dict[builtins.int, builtins.int] member_value_type: builtins.dict[builtins.int, builtins.int] main: | from atom.api import Atom, {{ member }} class A(Atom): # Testing default value for dict is a pain since literal dict cannot be used # in yaml parametrization. m = {{ member_instance }}, default={1: 1}) reveal_type(A.m) # N: Revealed type is "{{ member_type }}" reveal_type(A().m) # N: Revealed type is "{{ member_value_type }}"atom-0.12.1/tests/type_checking/test_enum.yml000066400000000000000000000026301506756731600212600ustar00rootroot00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. #------------------------------------------------------------------------------ - case: enum parametrized: - member: Enum member_instance: Enum(1, 2) member_type: atom.enum.Enum[builtins.int] member_value_type: builtins.int - member: Enum member_instance: Enum(1, str(1)) member_type: atom.enum.Enum[builtins.object] member_value_type: builtins.object main: | from atom.api import Atom, {{ member }} class A(Atom): m = {{ member_instance }} reveal_type(A.m) # N: Revealed type is "{{ member_type }}" reveal_type(A().m) # N: Revealed type is "{{ member_value_type }}" A().m = 1 - case: enum_methods main: | from atom.api import Atom, Enum e = Enum(1, 2) class A(Atom): e1 = e e2 = e("1") e3 = e.added("1") e4 = e.removed(2) reveal_type(A.e1) # N: Revealed type is "atom.enum.Enum[builtins.int]" reveal_type(A.e2) # N: Revealed type is "atom.enum.Enum[builtins.int | builtins.str]" reveal_type(A.e3) # N: Revealed type is "atom.enum.Enum[builtins.int | builtins.str]" reveal_type(A.e4) # N: Revealed type is "atom.enum.Enum[builtins.int]" atom-0.12.1/tests/type_checking/test_event.yml000066400000000000000000000024161506756731600214370ustar00rootroot00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. #------------------------------------------------------------------------------ - case: event parametrized: - member: Event member_instance: Event() member_type: atom.event.Event[Any] - member: Event member_instance: Event(int) member_type: atom.event.Event[builtins.int] - member: Event member_instance: Event((int,)) member_type: atom.event.Event[builtins.int] - member: Event member_instance: Event((int, float)) member_type: atom.event.Event[builtins.int | builtins.float] - member: Event member_instance: Event((int, float, str)) member_type: atom.event.Event[builtins.int | builtins.float | builtins.str] - member: Event, Int member_instance: Event(Int()) member_type: atom.event.Event[builtins.int] main: | from atom.api import Atom, {{ member }} class A(Atom): m = {{ member_instance }} reveal_type(A.m) # N: Revealed type is "{{ member_type }}" reveal_type(A().m) # N: Revealed type is "atom.catom.EventBinder" A().m = 1atom-0.12.1/tests/type_checking/test_generic_aliases.yml000066400000000000000000000042221506756731600234300ustar00rootroot00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. #------------------------------------------------------------------------------ - case: member main: | from atom.api import Member m: Member[int, str] = Member() reveal_type(m) # N: Revealed type is "atom.catom.Member[builtins.int, builtins.str]" - case: generic_alias_as_type parametrized: - member: Typed member_instance: Typed(list[int], optional=False) member_type: atom.typed.Typed[builtins.list[builtins.int]] member_value_type: builtins.list[builtins.int] - member: ForwardTyped member_instance: ForwardTyped(lambda:list[int], optional=False) member_type: atom.typed.ForwardTyped[builtins.list[builtins.int]] member_value_type: builtins.list[builtins.int] - member: Instance member_instance: Instance(list[int], optional=False) member_type: atom.instance.Instance[builtins.list[builtins.int]] member_value_type: builtins.list[builtins.int] - member: Instance member_instance: Instance((list[int], int), optional=False) member_type: atom.instance.Instance[builtins.list[builtins.int] | builtins.int] member_value_type: builtins.list[builtins.int] | builtins.int - member: ForwardInstance member_instance: ForwardInstance(lambda:list[int], optional=False) member_type: atom.instance.ForwardInstance[builtins.list[builtins.int]] member_value_type: builtins.list[builtins.int] - member: ForwardInstance member_instance: ForwardInstance(lambda:(list[int], int), optional=False) member_type: atom.instance.ForwardInstance[builtins.list[builtins.int] | builtins.int] member_value_type: builtins.list[builtins.int] | builtins.int main: | from atom.api import Atom, {{ member }} class A(Atom): m = {{ member_instance }} reveal_type(A.m) # N: Revealed type is "{{ member_type }}" reveal_type(A().m) # N: Revealed type is "{{ member_value_type }}"atom-0.12.1/tests/type_checking/test_list.yml000066400000000000000000000223151506756731600212710ustar00rootroot00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. #------------------------------------------------------------------------------ - case: list parametrized: # List with no defaults - member: List member_instance: List() member_type: atom.list.List[Any] member_value_type: builtins.list[Any] - member: List member_instance: List(int) member_type: atom.list.List[builtins.int] member_value_type: builtins.list[builtins.int] - member: List member_instance: List((int,)) member_type: atom.list.List[builtins.int] member_value_type: builtins.list[builtins.int] - member: List member_instance: List((int, float)) member_type: atom.list.List[builtins.int | builtins.float] member_value_type: builtins.list[builtins.int | builtins.float] - member: List member_instance: List((int, float, str)) member_type: atom.list.List[builtins.int | builtins.float | builtins.str] member_value_type: builtins.list[builtins.int | builtins.float | builtins.str] - member: List, Int member_instance: List(Int()) member_type: atom.list.List[builtins.int] member_value_type: builtins.list[builtins.int] # List with defaults - member: List member_instance: List(default=[1]) member_type: atom.list.List[Any] member_value_type: builtins.list[Any] - member: List member_instance: List(int, default=[3]) member_type: atom.list.List[builtins.int] member_value_type: builtins.list[builtins.int] - member: List member_instance: List((int,), default=[1,2]) member_type: atom.list.List[builtins.int] member_value_type: builtins.list[builtins.int] # Ensure that for tuple of types any valid default works - member: List member_instance: List((int, str), default=[1]) member_type: atom.list.List[builtins.int | builtins.str] member_value_type: builtins.list[builtins.int | builtins.str] - member: List member_instance: List((int, str), default=[str(1)]) member_type: atom.list.List[builtins.int | builtins.str] member_value_type: builtins.list[builtins.int | builtins.str] - member: List member_instance: List((int, str), default=[1, str(1)]) member_type: atom.list.List[builtins.int | builtins.str] member_value_type: builtins.list[builtins.int | builtins.str] - member: List member_instance: List((int, str, Z), default=[1]) member_type: atom.list.List[builtins.int | builtins.str | main.Z] member_value_type: builtins.list[builtins.int | builtins.str | main.Z] - member: List member_instance: List((int, str, Z), default=[str(1)]) member_type: atom.list.List[builtins.int | builtins.str | main.Z] member_value_type: builtins.list[builtins.int | builtins.str | main.Z] - member: List member_instance: List((int, str, Z), default=[Z()]) member_type: atom.list.List[builtins.int | builtins.str | main.Z] member_value_type: builtins.list[builtins.int | builtins.str | main.Z] - member: List member_instance: List((int, str, Z), default=[1, str(1)]) member_type: atom.list.List[builtins.int | builtins.str | main.Z] member_value_type: builtins.list[builtins.int | builtins.str | main.Z] - member: List member_instance: List((int, str, Z), default=[1, Z()]) member_type: atom.list.List[builtins.int | builtins.str | main.Z] member_value_type: builtins.list[builtins.int | builtins.str | main.Z] - member: List member_instance: List((int, str, Z), default=[str(1), Z()]) member_type: atom.list.List[builtins.int | builtins.str | main.Z] member_value_type: builtins.list[builtins.int | builtins.str | main.Z] - member: List member_instance: List((int, str, Z), default=[1, str(1), Z()]) member_type: atom.list.List[builtins.int | builtins.str | main.Z] member_value_type: builtins.list[builtins.int | builtins.str | main.Z] - member: List, Int member_instance: List(Int(), default=[2]) member_type: atom.list.List[builtins.int] member_value_type: builtins.list[builtins.int] main: | from atom.api import Atom, {{ member }} class Z(Atom): pass class A(Atom): m = {{ member_instance }} reveal_type(A.m) # N: Revealed type is "{{ member_type }}" reveal_type(A().m) # N: Revealed type is "{{ member_value_type }}" - case: container_list parametrized: # List with no defaults - member: ContainerList member_instance: ContainerList() member_type: atom.containerlist.ContainerList[Any] member_value_type: builtins.list[Any] - member: ContainerList member_instance: ContainerList(int) member_type: atom.containerlist.ContainerList[builtins.int] member_value_type: builtins.list[builtins.int] - member: ContainerList member_instance: ContainerList((int,)) member_type: atom.containerlist.ContainerList[builtins.int] member_value_type: builtins.list[builtins.int] - member: ContainerList member_instance: ContainerList((int, float)) member_type: atom.containerlist.ContainerList[builtins.int | builtins.float] member_value_type: builtins.list[builtins.int | builtins.float] - member: ContainerList member_instance: ContainerList((int, float, str)) member_type: atom.containerlist.ContainerList[builtins.int | builtins.float | builtins.str] member_value_type: builtins.list[builtins.int | builtins.float | builtins.str] - member: ContainerList, Int member_instance: ContainerList(Int()) member_type: atom.containerlist.ContainerList[builtins.int] member_value_type: builtins.list[builtins.int] # ContainerList with defaults - member: ContainerList member_instance: ContainerList(default=[1]) member_type: atom.containerlist.ContainerList[Any] member_value_type: builtins.list[Any] - member: ContainerList member_instance: ContainerList(int, default=[3]) member_type: atom.containerlist.ContainerList[builtins.int] member_value_type: builtins.list[builtins.int] - member: ContainerList member_instance: ContainerList((int,), default=[1,2]) member_type: atom.containerlist.ContainerList[builtins.int] member_value_type: builtins.list[builtins.int] # Ensure that for tuple of types any valid default works - member: ContainerList member_instance: ContainerList((int, str), default=[1]) member_type: atom.containerlist.ContainerList[builtins.int | builtins.str] member_value_type: builtins.list[builtins.int | builtins.str] - member: ContainerList member_instance: ContainerList((int, str), default=[str(1)]) member_type: atom.containerlist.ContainerList[builtins.int | builtins.str] member_value_type: builtins.list[builtins.int | builtins.str] - member: ContainerList member_instance: ContainerList((int, str), default=[1, str(1)]) member_type: atom.containerlist.ContainerList[builtins.int | builtins.str] member_value_type: builtins.list[builtins.int | builtins.str] - member: ContainerList member_instance: ContainerList((int, str, Z), default=[1]) member_type: atom.containerlist.ContainerList[builtins.int | builtins.str | main.Z] member_value_type: builtins.list[builtins.int | builtins.str | main.Z] - member: ContainerList member_instance: ContainerList((int, str, Z), default=[str(1)]) member_type: atom.containerlist.ContainerList[builtins.int | builtins.str | main.Z] member_value_type: builtins.list[builtins.int | builtins.str | main.Z] - member: ContainerList member_instance: ContainerList((int, str, Z), default=[Z()]) member_type: atom.containerlist.ContainerList[builtins.int | builtins.str | main.Z] member_value_type: builtins.list[builtins.int | builtins.str | main.Z] - member: ContainerList member_instance: ContainerList((int, str, Z), default=[1, str(1)]) member_type: atom.containerlist.ContainerList[builtins.int | builtins.str | main.Z] member_value_type: builtins.list[builtins.int | builtins.str | main.Z] - member: ContainerList member_instance: ContainerList((int, str, Z), default=[1, Z()]) member_type: atom.containerlist.ContainerList[builtins.int | builtins.str | main.Z] member_value_type: builtins.list[builtins.int | builtins.str | main.Z] - member: ContainerList member_instance: ContainerList((int, str, Z), default=[str(1), Z()]) member_type: atom.containerlist.ContainerList[builtins.int | builtins.str | main.Z] member_value_type: builtins.list[builtins.int | builtins.str | main.Z] - member: ContainerList member_instance: ContainerList((int, str, Z), default=[1, str(1), Z()]) member_type: atom.containerlist.ContainerList[builtins.int | builtins.str | main.Z] member_value_type: builtins.list[builtins.int | builtins.str | main.Z] - member: ContainerList, Int member_instance: ContainerList(Int(), default=[2]) member_type: atom.containerlist.ContainerList[builtins.int] member_value_type: builtins.list[builtins.int] main: | from atom.api import Atom, {{ member }} class Z(Atom): pass class A(Atom): m = {{ member_instance }} reveal_type(A.m) # N: Revealed type is "{{ member_type }}" reveal_type(A().m) # N: Revealed type is "{{ member_value_type }}"atom-0.12.1/tests/type_checking/test_property.yml000066400000000000000000000037731506756731600222110ustar00rootroot00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. #------------------------------------------------------------------------------ - case: property_empty main: | from atom.api import Atom, Property class A(Atom): m = Property() reveal_type(A.m) # N: Revealed type is "atom.property.Property[Never, Never]" reveal_type(A().m) # N: Revealed type is "Never" - case: property_no_setter main: | from atom.api import Atom, Property def g(a) -> int: return a.b class A(Atom): m = Property(g) reveal_type(A.m) # N: Revealed type is "atom.property.Property[builtins.int, Never]" reveal_type(A().m) # N: Revealed type is "builtins.int" - case: property parametrized: - member: Property member_instance: Property(g, s) member_type: atom.property.Property[builtins.int, builtins.int | builtins.str] member_value_type: builtins.int - member: Property member_instance: Property(g, s, d) member_type: atom.property.Property[builtins.int, builtins.int | builtins.str] member_value_type: builtins.int main: | from typing import Union from atom.api import Atom, {{ member }} def g(a) -> int: return a._b def s(a, v: Union[int, str]) -> None: a._b = int(v) def d(a): pass class A(Atom): m = {{ member_instance }} reveal_type(A.m) # N: Revealed type is "{{ member_type }}" reveal_type(A().m) # N: Revealed type is "{{ member_value_type }}" - case: cached_property main: | from atom.api import Atom, cached_property class A(Atom): @cached_property def m(self) -> int: return 1 reveal_type(A.m) # N: Revealed type is "atom.property.Property[builtins.int, Never]" reveal_type(A().m) # N: Revealed type is "builtins.int"atom-0.12.1/tests/type_checking/test_scalars.yml000066400000000000000000000215301506756731600217440ustar00rootroot00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. #------------------------------------------------------------------------------ - case: scalars_no_annotations parametrized: - member: Value member_instance: Value() member_type: atom.scalars.Value[Any] member_value_type: Any - member: ReadOnly member_instance: ReadOnly() member_type: atom.scalars.ReadOnly[Any] member_value_type: Any - member: ReadOnly member_instance: ReadOnly(default=0) member_type: atom.scalars.ReadOnly[builtins.int] member_value_type: builtins.int - member: ReadOnly member_instance: ReadOnly(factory=g) member_type: atom.scalars.ReadOnly[builtins.int] member_value_type: builtins.int - member: ReadOnly member_instance: ReadOnly(int) member_type: atom.scalars.ReadOnly[builtins.int] member_value_type: builtins.int - member: ReadOnly member_instance: ReadOnly(int, default=0) member_type: atom.scalars.ReadOnly[builtins.int] member_value_type: builtins.int - member: ReadOnly member_instance: ReadOnly(int, factory=g) member_type: atom.scalars.ReadOnly[builtins.int] member_value_type: builtins.int - member: ReadOnly member_instance: ReadOnly((int,)) member_type: atom.scalars.ReadOnly[builtins.int] member_value_type: builtins.int - member: ReadOnly member_instance: ReadOnly((int,), default=0) member_type: atom.scalars.ReadOnly[builtins.int] member_value_type: builtins.int - member: ReadOnly member_instance: ReadOnly((int,), factory=g) member_type: atom.scalars.ReadOnly[builtins.int] member_value_type: builtins.int - member: ReadOnly member_instance: ReadOnly((int, str)) member_type: atom.scalars.ReadOnly[builtins.int | builtins.str] member_value_type: builtins.int | builtins.str - member: ReadOnly member_instance: ReadOnly((int, str), default=0) member_type: atom.scalars.ReadOnly[builtins.int | builtins.str] member_value_type: builtins.int | builtins.str - member: ReadOnly member_instance: ReadOnly((int, str), factory=g) member_type: atom.scalars.ReadOnly[builtins.int | builtins.str] member_value_type: builtins.int | builtins.str - member: ReadOnly member_instance: ReadOnly((int, str, float)) member_type: atom.scalars.ReadOnly[builtins.int | builtins.str | builtins.float] member_value_type: builtins.int | builtins.str | builtins.float - member: ReadOnly member_instance: ReadOnly((int, str, float), default=0) member_type: atom.scalars.ReadOnly[builtins.int | builtins.str | builtins.float] member_value_type: builtins.int | builtins.str | builtins.float - member: ReadOnly member_instance: ReadOnly((int, str, float), factory=g) member_type: atom.scalars.ReadOnly[builtins.int | builtins.str | builtins.float] member_value_type: builtins.int | builtins.str | builtins.float - member: Constant member_instance: Constant() member_type: atom.scalars.Constant[Any] member_value_type: Any - member: Constant member_instance: Constant(0) member_type: atom.scalars.Constant[builtins.int] member_value_type: builtins.int - member: Constant member_instance: Constant(factory=g) member_type: atom.scalars.Constant[builtins.int] member_value_type: builtins.int - member: Constant member_instance: Constant(kind=int) member_type: atom.scalars.Constant[builtins.int] member_value_type: builtins.int - member: Constant member_instance: Constant(0, kind=int) member_type: atom.scalars.Constant[builtins.int] member_value_type: builtins.int - member: Constant member_instance: Constant(factory=g, kind=int) member_type: atom.scalars.Constant[builtins.int] member_value_type: builtins.int - member: Constant member_instance: Constant(kind=(int,)) member_type: atom.scalars.Constant[builtins.int] member_value_type: builtins.int - member: Constant member_instance: Constant(0, kind=(int,)) member_type: atom.scalars.Constant[builtins.int] member_value_type: builtins.int - member: Constant member_instance: Constant(factory=g, kind=(int,)) member_type: atom.scalars.Constant[builtins.int] member_value_type: builtins.int - member: Constant member_instance: Constant(kind=(int, str)) member_type: atom.scalars.Constant[builtins.int | builtins.str] member_value_type: builtins.int | builtins.str - member: Constant member_instance: Constant(0, kind=(int, str)) member_type: atom.scalars.Constant[builtins.int | builtins.str] member_value_type: builtins.int | builtins.str - member: Constant member_instance: Constant(factory=g, kind=(int, str)) member_type: atom.scalars.Constant[builtins.int | builtins.str] member_value_type: builtins.int | builtins.str - member: Constant member_instance: Constant(kind=(int, str, float)) member_type: atom.scalars.Constant[builtins.int | builtins.str | builtins.float] member_value_type: builtins.int | builtins.str | builtins.float - member: Constant member_instance: Constant(0, kind=(int, str, float)) member_type: atom.scalars.Constant[builtins.int | builtins.str | builtins.float] member_value_type: builtins.int | builtins.str | builtins.float - member: Constant member_instance: Constant(factory=g, kind=(int, str, float)) member_type: atom.scalars.Constant[builtins.int | builtins.str | builtins.float] member_value_type: builtins.int | builtins.str | builtins.float - member: Bool member_instance: Bool() member_type: atom.scalars.Bool[builtins.bool] member_value_type: builtins.bool - member: Int member_instance: Int() member_type: atom.scalars.Int[builtins.int] member_value_type: builtins.int - member: Int member_instance: Int(strict=False) member_type: atom.scalars.Int[builtins.int | builtins.float] member_value_type: builtins.int - member: Int member_instance: Int(factory=f, strict=False) member_type: atom.scalars.Int[builtins.int | builtins.float] member_value_type: builtins.int # XXX add strict cases - member: FloatRange member_instance: FloatRange() member_type: atom.scalars.FloatRange[builtins.int | builtins.float] member_value_type: builtins.float - member: Range member_instance: Range() member_type: atom.scalars.Range[builtins.int] member_value_type: builtins.int - member: Float member_instance: Float() member_type: atom.scalars.Float[builtins.int | builtins.float] member_value_type: builtins.float - member: Float member_instance: Float(strict=True) member_type: atom.scalars.Float[builtins.float] member_value_type: builtins.float - member: Bytes member_instance: Bytes(strict=False) member_type: atom.scalars.Bytes[builtins.bytes | builtins.str] member_value_type: builtins.bytes - member: Bytes member_instance: Bytes() member_type: atom.scalars.Bytes[builtins.bytes] member_value_type: builtins.bytes - member: Str member_instance: Str(strict=False) member_type: atom.scalars.Str[builtins.str | builtins.bytes] member_value_type: builtins.str - member: Str member_instance: Str() member_type: atom.scalars.Str[builtins.str] member_value_type: builtins.str main: | from atom.api import Atom, {{ member }} def f(): pass def g() -> int: return 1 class A(Atom): m = {{ member_instance }} reveal_type(A.m) # N: Revealed type is "{{ member_type }}" reveal_type(A().m) # N: Revealed type is "{{ member_value_type }}" - case: callable main: | from atom.api import Atom, Callable class A(Atom): m = Callable() reveal_type(A.m) # N: Revealed type is "atom.scalars.Callable[def (*Any, **Any) -> Any]" reveal_type(A().m) # N: Revealed type is "def (*Any, **Any) -> Any" - case: str_with_default main: | from atom.api import Atom, Str class A(Atom): m = Str('', strict=True) reveal_type(A.m) # N: Revealed type is "atom.scalars.Str[builtins.str]" reveal_type(A().m) # N: Revealed type is "builtins.str" - case: bytes_with_default main: | from atom.api import Atom, Bytes class A(Atom): m = Bytes(b"", strict=True) reveal_type(A.m) # N: Revealed type is "atom.scalars.Bytes[builtins.bytes]" reveal_type(A().m) # N: Revealed type is "builtins.bytes" # XXX add tests with explicit annotations atom-0.12.1/tests/type_checking/test_set.yml000066400000000000000000000104751506756731600211150ustar00rootroot00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. #------------------------------------------------------------------------------ - case: set parametrized: # Set with no defaults - member: Set member_instance: Set() member_type: atom.set.Set[Any] member_value_type: builtins.set[Any] - member: Set member_instance: Set(int) member_type: atom.set.Set[builtins.int] member_value_type: builtins.set[builtins.int] - member: Set member_instance: Set((int,)) member_type: atom.set.Set[builtins.int] member_value_type: builtins.set[builtins.int] - member: Set member_instance: Set((int, float)) member_type: atom.set.Set[builtins.int | builtins.float] member_value_type: builtins.set[builtins.int | builtins.float] - member: Set member_instance: Set((int, float, str)) member_type: atom.set.Set[builtins.int | builtins.float | builtins.str] member_value_type: builtins.set[builtins.int | builtins.float | builtins.str] - member: Set, Int member_instance: Set(Int()) member_type: atom.set.Set[builtins.int] member_value_type: builtins.set[builtins.int] # Set with defaults - member: Set member_instance: Set(default={1}) member_type: atom.set.Set[Any] member_value_type: builtins.set[Any] - member: Set member_instance: Set(int, default={3}) member_type: atom.set.Set[builtins.int] member_value_type: builtins.set[builtins.int] - member: Set member_instance: Set((int,), default={1,2}) member_type: atom.set.Set[builtins.int] member_value_type: builtins.set[builtins.int] # Ensure that for tuple of types any valid default works - member: Set member_instance: Set((int, str), default={1}) member_type: atom.set.Set[builtins.int | builtins.str] member_value_type: builtins.set[builtins.int | builtins.str] - member: Set member_instance: Set((int, str), default={str(1)}) member_type: atom.set.Set[builtins.int | builtins.str] member_value_type: builtins.set[builtins.int | builtins.str] - member: Set member_instance: Set((int, str), default={1, str(1)}) member_type: atom.set.Set[builtins.int | builtins.str] member_value_type: builtins.set[builtins.int | builtins.str] - member: Set member_instance: Set((int, str, Z), default={1}) member_type: atom.set.Set[builtins.int | builtins.str | main.Z] member_value_type: builtins.set[builtins.int | builtins.str | main.Z] - member: Set member_instance: Set((int, str, Z), default={str(1)}) member_type: atom.set.Set[builtins.int | builtins.str | main.Z] member_value_type: builtins.set[builtins.int | builtins.str | main.Z] - member: Set member_instance: Set((int, str, Z), default={Z()}) member_type: atom.set.Set[builtins.int | builtins.str | main.Z] member_value_type: builtins.set[builtins.int | builtins.str | main.Z] - member: Set member_instance: Set((int, str, Z), default={1, str(1)}) member_type: atom.set.Set[builtins.int | builtins.str | main.Z] member_value_type: builtins.set[builtins.int | builtins.str | main.Z] - member: Set member_instance: Set((int, str, Z), default={1, Z()}) member_type: atom.set.Set[builtins.int | builtins.str | main.Z] member_value_type: builtins.set[builtins.int | builtins.str | main.Z] - member: Set member_instance: Set((int, str, Z), default={str(1), Z()}) member_type: atom.set.Set[builtins.int | builtins.str | main.Z] member_value_type: builtins.set[builtins.int | builtins.str | main.Z] - member: Set member_instance: Set((int, str, Z), default={1, str(1), Z()}) member_type: atom.set.Set[builtins.int | builtins.str | main.Z] member_value_type: builtins.set[builtins.int | builtins.str | main.Z] - member: Set, Int member_instance: Set(Int(), default={2}) member_type: atom.set.Set[builtins.int] member_value_type: builtins.set[builtins.int] main: | from atom.api import Atom, {{ member }} class Z(Atom): pass class A(Atom): m = {{ member_instance }} reveal_type(A.m) # N: Revealed type is "{{ member_type }}" reveal_type(A().m) # N: Revealed type is "{{ member_value_type }}" atom-0.12.1/tests/type_checking/test_signal.yml000066400000000000000000000011351506756731600215700ustar00rootroot00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. #------------------------------------------------------------------------------ - case: signal main: | from atom.api import Atom, Signal class A(Atom): m = Signal() reveal_type(A.m) # N: Revealed type is "atom.signal.Signal" reveal_type(A().m) # N: Revealed type is "atom.catom.SignalConnector" A().m()atom-0.12.1/tests/type_checking/test_subclass.yml000066400000000000000000000064351506756731600221420ustar00rootroot00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. #------------------------------------------------------------------------------ - case: subclass parametrized: - member: Subclass member_instance: Subclass(A) member_type: atom.subclass.Subclass[type[main.A]] member_value_type: type[main.A] - member: Subclass member_instance: Subclass(A, B) member_type: atom.subclass.Subclass[type[main.A]] member_value_type: type[main.A] - member: Subclass member_instance: Subclass((A,)) member_type: atom.subclass.Subclass[type[main.A]] member_value_type: type[main.A] - member: Subclass member_instance: Subclass((A,), B) member_type: atom.subclass.Subclass[type[main.A]] member_value_type: type[main.A] - member: Subclass member_instance: Subclass((int, A)) member_type: atom.subclass.Subclass[type[builtins.int] | type[main.A]] member_value_type: type[builtins.int] | type[main.A] - member: Subclass member_instance: Subclass((int, A), B) member_type: atom.subclass.Subclass[type[builtins.int] | type[main.A]] member_value_type: type[builtins.int] | type[main.A] - member: Subclass member_instance: Subclass((int, A, str)) member_type: atom.subclass.Subclass[type[builtins.int] | type[main.A] | type[builtins.str]] member_value_type: type[builtins.int] | type[main.A] | type[builtins.str] - member: Subclass member_instance: Subclass((int, A, str), B) member_type: atom.subclass.Subclass[type[builtins.int] | type[main.A] | type[builtins.str]] member_value_type: type[builtins.int] | type[main.A] | type[builtins.str] - member: ForwardSubclass member_instance: ForwardSubclass(resolve1) member_type: atom.subclass.ForwardSubclass[type[main.A]] member_value_type: type[main.A] - member: ForwardSubclass member_instance: ForwardSubclass(resolve2) member_type: atom.subclass.ForwardSubclass[type[main.A]] member_value_type: type[main.A] - member: ForwardSubclass member_instance: ForwardSubclass(resolve3) member_type: atom.subclass.ForwardSubclass[type[builtins.int] | type[main.A]] member_value_type: type[builtins.int] | type[main.A] - member: ForwardSubclass member_instance: ForwardSubclass(resolve4) member_type: atom.subclass.ForwardSubclass[type[builtins.int] | type[main.A] | type[builtins.str]] member_value_type: type[builtins.int] | type[main.A] | type[builtins.str] main: | import _io from typing import Tuple, Type from atom.api import Atom, {{ member }} class A: pass class B(A): pass def resolve1() -> Type[A]: return A def resolve2() -> Tuple[Type[A]]: return A, def resolve3() -> Tuple[Type[int], Type[A]]: return int, A def resolve4() -> Tuple[Type[int], Type[A], Type[str]]: return int, A, str class T(Atom): m = {{ member_instance }} reveal_type(T.m) # N: Revealed type is "{{ member_type }}" reveal_type(T().m) # N: Revealed type is "{{ member_value_type }}"atom-0.12.1/tests/type_checking/test_tuple.yml000066400000000000000000000137721506756731600214560ustar00rootroot00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. #------------------------------------------------------------------------------ - case: tuple parametrized: # Tuple with no defaults - member: Tuple member_instance: Tuple() member_type: atom.tuple.Tuple[Any] member_value_type: builtins.tuple[Any, ...] - member: Tuple member_instance: Tuple(int) member_type: atom.tuple.Tuple[builtins.int] member_value_type: builtins.tuple[builtins.int, ...] - member: Tuple member_instance: Tuple((int,)) member_type: atom.tuple.Tuple[builtins.int] member_value_type: builtins.tuple[builtins.int, ...] - member: Tuple member_instance: Tuple((int, float)) member_type: atom.tuple.Tuple[builtins.int | builtins.float] member_value_type: builtins.tuple[builtins.int | builtins.float, ...] - member: Tuple member_instance: Tuple((int, float, str)) member_type: atom.tuple.Tuple[builtins.int | builtins.float | builtins.str] member_value_type: builtins.tuple[builtins.int | builtins.float | builtins.str, ...] - member: Tuple, Int member_instance: Tuple(Int()) member_type: atom.tuple.Tuple[builtins.int] member_value_type: builtins.tuple[builtins.int, ...] # Tuple with defaults - member: Tuple member_instance: Tuple(default=(1,)) member_type: atom.tuple.Tuple[Any] member_value_type: builtins.tuple[Any, ...] - member: Tuple member_instance: Tuple(int, default=(3,)) member_type: atom.tuple.Tuple[builtins.int] member_value_type: builtins.tuple[builtins.int, ...] - member: Tuple member_instance: Tuple((int,), default=(1, 2)) member_type: atom.tuple.Tuple[builtins.int] member_value_type: builtins.tuple[builtins.int, ...] # Ensure that for tuple of types any valid default works - member: Tuple member_instance: Tuple((int, str), default=(1,)) member_type: atom.tuple.Tuple[builtins.int | builtins.str] member_value_type: builtins.tuple[builtins.int | builtins.str, ...] - member: Tuple member_instance: Tuple((int, str), default=(str(1),)) member_type: atom.tuple.Tuple[builtins.int | builtins.str] member_value_type: builtins.tuple[builtins.int | builtins.str, ...] - member: Tuple member_instance: Tuple((int, str), default=(1, str(1))) member_type: atom.tuple.Tuple[builtins.int | builtins.str] member_value_type: builtins.tuple[builtins.int | builtins.str, ...] - member: Tuple member_instance: Tuple((int, str, bytes), default=(1,)) member_type: atom.tuple.Tuple[builtins.int | builtins.str | builtins.bytes] member_value_type: builtins.tuple[builtins.int | builtins.str | builtins.bytes, ...] - member: Tuple member_instance: Tuple((int, str, bytes), default=(str(1),)) member_type: atom.tuple.Tuple[builtins.int | builtins.str | builtins.bytes] member_value_type: builtins.tuple[builtins.int | builtins.str | builtins.bytes, ...] - member: Tuple member_instance: Tuple((int, str, bytes), default=(bytes(1),)) member_type: atom.tuple.Tuple[builtins.int | builtins.str | builtins.bytes] member_value_type: builtins.tuple[builtins.int | builtins.str | builtins.bytes, ...] - member: Tuple member_instance: Tuple((int, str, bytes), default=(1, str(1),)) member_type: atom.tuple.Tuple[builtins.int | builtins.str | builtins.bytes] member_value_type: builtins.tuple[builtins.int | builtins.str | builtins.bytes, ...] - member: Tuple member_instance: Tuple((int, str, bytes), default=(1, bytes(1),)) member_type: atom.tuple.Tuple[builtins.int | builtins.str | builtins.bytes] member_value_type: builtins.tuple[builtins.int | builtins.str | builtins.bytes, ...] - member: Tuple member_instance: Tuple((int, str, bytes), default=(str(1), bytes(1))) member_type: atom.tuple.Tuple[builtins.int | builtins.str | builtins.bytes] member_value_type: builtins.tuple[builtins.int | builtins.str | builtins.bytes, ...] - member: Tuple member_instance: Tuple((int, str, bytes), default=(1, str(1), bytes(1))) member_type: atom.tuple.Tuple[builtins.int | builtins.str | builtins.bytes] member_value_type: builtins.tuple[builtins.int | builtins.str | builtins.bytes, ...] - member: Tuple, Int member_instance: Tuple(Int(), default=(2,)) member_type: atom.tuple.Tuple[builtins.int] member_value_type: builtins.tuple[builtins.int, ...] main: | from atom.api import Atom, {{ member }} class A(Atom): m = {{ member_instance }} reveal_type(A.m) # N: Revealed type is "{{ member_type }}" reveal_type(A().m) # N: Revealed type is "{{ member_value_type }}" - case: fixed_tuple parametrized: # Tuple with no defaults - member: FixedTuple member_instance: FixedTuple(int) member_type: atom.tuple.FixedTuple[tuple[builtins.int]] member_value_type: tuple[builtins.int] - member: FixedTuple member_instance: FixedTuple(int, float) member_type: atom.tuple.FixedTuple[tuple[builtins.int, builtins.float]] member_value_type: tuple[builtins.int, builtins.float] - member: FixedTuple member_instance: FixedTuple(int, float, str) member_type: atom.tuple.FixedTuple[tuple[builtins.int, builtins.float, builtins.str]] member_value_type: tuple[builtins.int, builtins.float, builtins.str] - member: FixedTuple, Int member_instance: FixedTuple(Int()) member_type: atom.tuple.FixedTuple[tuple[builtins.int]] member_value_type: tuple[builtins.int] # Tuple with defaults - member: FixedTuple member_instance: FixedTuple(int, default=(3,)) member_type: atom.tuple.FixedTuple[tuple[builtins.int]] member_value_type: tuple[builtins.int] main: | from atom.api import Atom, {{ member }} class A(Atom): m = {{ member_instance }} reveal_type(A.m) # N: Revealed type is "{{ member_type }}" reveal_type(A().m) # N: Revealed type is "{{ member_value_type }}" atom-0.12.1/tests/type_checking/test_typed_instance.yml000066400000000000000000000723361506756731600233370ustar00rootroot00000000000000# ------------------------------------------------------------------------------------------------------ # Copyright (c) 2021-2025, Nucleic Development Team. # # Distributed under the terms of the Modified BSD License. # # The full license is in the file LICENSE, distributed with this software. # ------------------------------------------------------------------------------------------------------ - case: typed_no_annotations parametrized: # Typed inferred optional - member: Typed member_instance: Typed(int) member_type: atom.typed.Typed[builtins.int | None] member_value_type: builtins.int | None - member: Typed member_instance: Typed(List[int]) member_type: atom.typed.Typed[builtins.list[builtins.int] | None] member_value_type: builtins.list[builtins.int] | None - member: Typed member_instance: Typed(int, ()) member_type: atom.typed.Typed[builtins.int] member_value_type: builtins.int - member: Typed member_instance: Typed(_io.StringIO, kwargs=dict(initial_value=str(1))) member_type: atom.typed.Typed[_io.StringIO] member_value_type: _io.StringIO - member: Typed member_instance: Typed(_io.StringIO, factory=g) member_type: atom.typed.Typed[_io.StringIO] member_value_type: _io.StringIO # Forward typed inferred optional - member: ForwardTyped member_instance: ForwardTyped(resolver) member_type: atom.typed.ForwardTyped[_io.StringIO | None] member_value_type: _io.StringIO | None - member: ForwardTyped member_instance: ForwardTyped(resolver, ()) member_type: atom.typed.ForwardTyped[_io.StringIO] member_value_type: _io.StringIO - member: ForwardTyped member_instance: ForwardTyped(resolver, kwargs=dict(initial_value=str(1))) member_type: atom.typed.ForwardTyped[_io.StringIO] member_value_type: _io.StringIO - member: ForwardTyped member_instance: ForwardTyped(resolver, factory=g) member_type: atom.typed.ForwardTyped[_io.StringIO] member_value_type: _io.StringIO # Typed optional - member: Typed member_instance: Typed(int, optional=True) member_type: atom.typed.Typed[builtins.int | None] member_value_type: builtins.int | None - member: Typed member_instance: Typed(int, (), optional=True) member_type: atom.typed.Typed[builtins.int | None] member_value_type: builtins.int | None - member: Typed member_instance: Typed(_io.StringIO, kwargs=dict(initial_value=str(1)), optional=True) member_type: atom.typed.Typed[_io.StringIO | None] member_value_type: _io.StringIO | None - member: Typed member_instance: Typed(_io.StringIO, factory=g, optional=True) member_type: atom.typed.Typed[_io.StringIO | None] member_value_type: _io.StringIO | None # Forward typed optional - member: ForwardTyped member_instance: ForwardTyped(resolver, optional=True) member_type: atom.typed.ForwardTyped[_io.StringIO | None] member_value_type: _io.StringIO | None - member: ForwardTyped member_instance: ForwardTyped(resolver, (), optional=True) member_type: atom.typed.ForwardTyped[_io.StringIO | None] member_value_type: _io.StringIO | None - member: ForwardTyped member_instance: ForwardTyped(resolver, kwargs=dict(initial_value=str(1)), optional=True) member_type: atom.typed.ForwardTyped[_io.StringIO | None] member_value_type: _io.StringIO | None - member: ForwardTyped member_instance: ForwardTyped(resolver, factory=g, optional=True) member_type: atom.typed.ForwardTyped[_io.StringIO | None] member_value_type: _io.StringIO | None # Typed non optional - member: Typed member_instance: Typed(int, optional=False) member_type: atom.typed.Typed[builtins.int] member_value_type: builtins.int - member: Typed member_instance: Typed(int, (), optional=False) member_type: atom.typed.Typed[builtins.int] member_value_type: builtins.int - member: Typed member_instance: Typed(_io.StringIO, kwargs=dict(initial_value=str(1)), optional=False) member_type: atom.typed.Typed[_io.StringIO] member_value_type: _io.StringIO - member: Typed member_instance: Typed(_io.StringIO, factory=g, optional=False) member_type: atom.typed.Typed[_io.StringIO] member_value_type: _io.StringIO # Forward typed non optional - member: ForwardTyped member_instance: ForwardTyped(resolver, optional=False) member_type: atom.typed.ForwardTyped[_io.StringIO] member_value_type: _io.StringIO - member: ForwardTyped member_instance: ForwardTyped(resolver, (), optional=False) member_type: atom.typed.ForwardTyped[_io.StringIO] member_value_type: _io.StringIO - member: ForwardTyped member_instance: ForwardTyped(resolver, kwargs=dict(initial_value=str(1)), optional=False) member_type: atom.typed.ForwardTyped[_io.StringIO] member_value_type: _io.StringIO - member: ForwardTyped member_instance: ForwardTyped(resolver, factory=g, optional=False) member_type: atom.typed.ForwardTyped[_io.StringIO] member_value_type: _io.StringIO main: | import _io from typing import List, Type from atom.api import Atom, {{ member }} def resolver() -> Type[_io.StringIO]: return _io.StringIO def g() -> _io.StringIO: return _io.StringIO() class A(Atom): m = {{ member_instance }} reveal_type(A.m) # N: Revealed type is "{{ member_type }}" reveal_type(A().m) # N: Revealed type is "{{ member_value_type }}" - case: instance_no_annotations parametrized: # Instance inferred optional - member: Instance member_instance: Instance(int) member_type: atom.instance.Instance[builtins.int | None] member_value_type: builtins.int | None - member: Instance member_instance: Instance((int,)) member_type: atom.instance.Instance[builtins.int | None] member_value_type: builtins.int | None - member: Instance member_instance: Instance((int, float)) member_type: atom.instance.Instance[builtins.int | builtins.float | None] member_value_type: builtins.int | builtins.float | None - member: Instance member_instance: Instance((int, float, str)) member_type: atom.instance.Instance[builtins.int | builtins.float | builtins.str | None] member_value_type: builtins.int | builtins.float | builtins.str | None - member: Instance member_instance: Instance(List[int]) member_type: atom.instance.Instance[builtins.list[builtins.int] | None] member_value_type: builtins.list[builtins.int] | None - member: Instance member_instance: Instance(int, ()) member_type: atom.instance.Instance[builtins.int] member_value_type: builtins.int - member: Instance member_instance: Instance((int,), ()) member_type: atom.instance.Instance[builtins.int] member_value_type: builtins.int - member: Instance member_instance: Instance((int, float), ()) member_type: atom.instance.Instance[builtins.int | builtins.float] member_value_type: builtins.int | builtins.float - member: Instance member_instance: Instance((int, float, str), ()) member_type: atom.instance.Instance[builtins.int | builtins.float | builtins.str] member_value_type: builtins.int | builtins.float | builtins.str - member: Instance member_instance: Instance(_io.StringIO, kwargs=dict(initial_value=str(1))) member_type: atom.instance.Instance[_io.StringIO] member_value_type: _io.StringIO - member: Instance member_instance: Instance((_io.StringIO,), kwargs=dict(initial_value=str(1))) member_type: atom.instance.Instance[_io.StringIO] member_value_type: _io.StringIO - member: Instance member_instance: Instance((_io.StringIO, str), kwargs=dict(initial_value=str(1))) member_type: atom.instance.Instance[_io.StringIO | builtins.str] member_value_type: _io.StringIO | builtins.str - member: Instance member_instance: Instance((_io.StringIO, str, bytes), kwargs=dict(initial_value=str(1))) member_type: atom.instance.Instance[_io.StringIO | builtins.str | builtins.bytes] member_value_type: _io.StringIO | builtins.str | builtins.bytes - member: Instance member_instance: Instance(_io.StringIO, factory=g) member_type: atom.instance.Instance[_io.StringIO] member_value_type: _io.StringIO - member: Instance member_instance: Instance((_io.StringIO,), factory=g) member_type: atom.instance.Instance[_io.StringIO] member_value_type: _io.StringIO - member: Instance member_instance: Instance((_io.StringIO, str), factory=g) member_type: atom.instance.Instance[_io.StringIO | builtins.str] member_value_type: _io.StringIO | builtins.str - member: Instance member_instance: Instance((_io.StringIO, str, bytes), factory=g) member_type: atom.instance.Instance[_io.StringIO | builtins.str | builtins.bytes] member_value_type: _io.StringIO | builtins.str | builtins.bytes # Forward instance inferred optional - member: ForwardInstance member_instance: ForwardInstance(resolver) member_type: atom.instance.ForwardInstance[_io.StringIO | None] member_value_type: _io.StringIO | None - member: ForwardInstance member_instance: ForwardInstance(resolver1) member_type: atom.instance.ForwardInstance[_io.StringIO | None] member_value_type: _io.StringIO | None - member: ForwardInstance member_instance: ForwardInstance(resolver2) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str | None] member_value_type: _io.StringIO | builtins.str | None - member: ForwardInstance member_instance: ForwardInstance(resolver3) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str | builtins.bytes | None] member_value_type: _io.StringIO | builtins.str | builtins.bytes | None - member: ForwardInstance member_instance: ForwardInstance(resolver, ()) member_type: atom.instance.ForwardInstance[_io.StringIO] member_value_type: _io.StringIO - member: ForwardInstance member_instance: ForwardInstance(resolver1, ()) member_type: atom.instance.ForwardInstance[_io.StringIO] member_value_type: _io.StringIO - member: ForwardInstance member_instance: ForwardInstance(resolver2, ()) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str] member_value_type: _io.StringIO | builtins.str - member: ForwardInstance member_instance: ForwardInstance(resolver3, ()) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str | builtins.bytes] member_value_type: _io.StringIO | builtins.str | builtins.bytes - member: ForwardInstance member_instance: ForwardInstance(resolver, kwargs=dict(initial_value=str(1))) member_type: atom.instance.ForwardInstance[_io.StringIO] member_value_type: _io.StringIO - member: ForwardInstance member_instance: ForwardInstance(resolver1, kwargs=dict(initial_value=str(1))) member_type: atom.instance.ForwardInstance[_io.StringIO] member_value_type: _io.StringIO - member: ForwardInstance member_instance: ForwardInstance(resolver2, kwargs=dict(initial_value=str(1))) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str] member_value_type: _io.StringIO | builtins.str - member: ForwardInstance member_instance: ForwardInstance(resolver3, kwargs=dict(initial_value=str(1))) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str | builtins.bytes] member_value_type: _io.StringIO | builtins.str | builtins.bytes - member: ForwardInstance member_instance: ForwardInstance(resolver, factory=g) member_type: atom.instance.ForwardInstance[_io.StringIO] member_value_type: _io.StringIO - member: ForwardInstance member_instance: ForwardInstance(resolver1, factory=g) member_type: atom.instance.ForwardInstance[_io.StringIO] member_value_type: _io.StringIO - member: ForwardInstance member_instance: ForwardInstance(resolver2, factory=g) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str] member_value_type: _io.StringIO | builtins.str - member: ForwardInstance member_instance: ForwardInstance(resolver3, factory=g) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str | builtins.bytes] member_value_type: _io.StringIO | builtins.str | builtins.bytes # optional instance - member: Instance member_instance: Instance(int, optional=True) member_type: atom.instance.Instance[builtins.int | None] member_value_type: builtins.int | None - member: Instance member_instance: Instance((int,), optional=True) member_type: atom.instance.Instance[builtins.int | None] member_value_type: builtins.int | None - member: Instance member_instance: Instance((int, float), optional=True) member_type: atom.instance.Instance[builtins.int | builtins.float | None] member_value_type: builtins.int | builtins.float | None - member: Instance member_instance: Instance((int, float, str), optional=True) member_type: atom.instance.Instance[builtins.int | builtins.float | builtins.str | None] member_value_type: builtins.int | builtins.float | builtins.str | None - member: Instance member_instance: Instance(int, (), optional=True) member_type: atom.instance.Instance[builtins.int | None] member_value_type: builtins.int | None - member: Instance member_instance: Instance((int,), (), optional=True) member_type: atom.instance.Instance[builtins.int | None] member_value_type: builtins.int | None - member: Instance member_instance: Instance((int, float), (), optional=True) member_type: atom.instance.Instance[builtins.int | builtins.float | None] member_value_type: builtins.int | builtins.float | None - member: Instance member_instance: Instance((int, float, str), (), optional=True) member_type: atom.instance.Instance[builtins.int | builtins.float | builtins.str | None] member_value_type: builtins.int | builtins.float | builtins.str | None - member: Instance member_instance: Instance(_io.StringIO, kwargs=dict(initial_value=str(1)), optional=True) member_type: atom.instance.Instance[_io.StringIO | None] member_value_type: _io.StringIO | None - member: Instance member_instance: Instance((_io.StringIO,), kwargs=dict(initial_value=str(1)), optional=True) member_type: atom.instance.Instance[_io.StringIO | None] member_value_type: _io.StringIO | None - member: Instance member_instance: Instance((_io.StringIO, str), kwargs=dict(initial_value=str(1)), optional=True) member_type: atom.instance.Instance[_io.StringIO | builtins.str | None] member_value_type: _io.StringIO | builtins.str | None - member: Instance member_instance: Instance((_io.StringIO, str, bytes), kwargs=dict(initial_value=str(1)), optional=True) member_type: atom.instance.Instance[_io.StringIO | builtins.str | builtins.bytes | None] member_value_type: _io.StringIO | builtins.str | builtins.bytes | None - member: Instance member_instance: Instance(_io.StringIO, factory=g, optional=True) member_type: atom.instance.Instance[_io.StringIO | None] member_value_type: _io.StringIO | None - member: Instance member_instance: Instance((_io.StringIO,), factory=g, optional=True) member_type: atom.instance.Instance[_io.StringIO | None] member_value_type: _io.StringIO | None - member: Instance member_instance: Instance((_io.StringIO, str), factory=g, optional=True) member_type: atom.instance.Instance[_io.StringIO | builtins.str | None] member_value_type: _io.StringIO | builtins.str | None - member: Instance member_instance: Instance((_io.StringIO, str, bytes), factory=g, optional=True) member_type: atom.instance.Instance[_io.StringIO | builtins.str | builtins.bytes | None] member_value_type: _io.StringIO | builtins.str | builtins.bytes | None # optional forward instance - member: ForwardInstance member_instance: ForwardInstance(resolver, optional=True) member_type: atom.instance.ForwardInstance[_io.StringIO | None] member_value_type: _io.StringIO | None - member: ForwardInstance member_instance: ForwardInstance(resolver1, optional=True) member_type: atom.instance.ForwardInstance[_io.StringIO | None] member_value_type: _io.StringIO | None - member: ForwardInstance member_instance: ForwardInstance(resolver2, optional=True) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str | None] member_value_type: _io.StringIO | builtins.str | None - member: ForwardInstance member_instance: ForwardInstance(resolver3, optional=True) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str | builtins.bytes | None] member_value_type: _io.StringIO | builtins.str | builtins.bytes | None - member: ForwardInstance member_instance: ForwardInstance(resolver, (), optional=True) member_type: atom.instance.ForwardInstance[_io.StringIO | None] member_value_type: _io.StringIO | None - member: ForwardInstance member_instance: ForwardInstance(resolver1, (), optional=True) member_type: atom.instance.ForwardInstance[_io.StringIO | None] member_value_type: _io.StringIO | None - member: ForwardInstance member_instance: ForwardInstance(resolver2, (), optional=True) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str | None] member_value_type: _io.StringIO | builtins.str | None - member: ForwardInstance member_instance: ForwardInstance(resolver3, (), optional=True) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str | builtins.bytes | None] member_value_type: _io.StringIO | builtins.str | builtins.bytes | None - member: ForwardInstance member_instance: ForwardInstance(resolver, kwargs=dict(initial_value=str(1)), optional=True) member_type: atom.instance.ForwardInstance[_io.StringIO | None] member_value_type: _io.StringIO | None - member: ForwardInstance member_instance: ForwardInstance(resolver1, kwargs=dict(initial_value=str(1)), optional=True) member_type: atom.instance.ForwardInstance[_io.StringIO | None] member_value_type: _io.StringIO | None - member: ForwardInstance member_instance: ForwardInstance(resolver2, kwargs=dict(initial_value=str(1)), optional=True) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str | None] member_value_type: _io.StringIO | builtins.str | None - member: ForwardInstance member_instance: ForwardInstance(resolver3, kwargs=dict(initial_value=str(1)), optional=True) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str | builtins.bytes | None] member_value_type: _io.StringIO | builtins.str | builtins.bytes | None - member: ForwardInstance member_instance: ForwardInstance(resolver, factory=g, optional=True) member_type: atom.instance.ForwardInstance[_io.StringIO | None] member_value_type: _io.StringIO | None - member: ForwardInstance member_instance: ForwardInstance(resolver1, factory=g, optional=True) member_type: atom.instance.ForwardInstance[_io.StringIO | None] member_value_type: _io.StringIO | None - member: ForwardInstance member_instance: ForwardInstance(resolver2, factory=g, optional=True) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str | None] member_value_type: _io.StringIO | builtins.str | None - member: ForwardInstance member_instance: ForwardInstance(resolver3, factory=g, optional=True) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str | builtins.bytes | None] member_value_type: _io.StringIO | builtins.str | builtins.bytes | None # non optional instance - member: Instance member_instance: Instance(int, optional=False) member_type: atom.instance.Instance[builtins.int] member_value_type: builtins.int - member: Instance member_instance: Instance((int,), optional=False) member_type: atom.instance.Instance[builtins.int] member_value_type: builtins.int - member: Instance member_instance: Instance((int, float), optional=False) member_type: atom.instance.Instance[builtins.int | builtins.float] member_value_type: builtins.int | builtins.float - member: Instance member_instance: Instance((int, float, str), optional=False) member_type: atom.instance.Instance[builtins.int | builtins.float | builtins.str] member_value_type: builtins.int | builtins.float | builtins.str - member: Instance member_instance: Instance(int, (), optional=False) member_type: atom.instance.Instance[builtins.int] member_value_type: builtins.int - member: Instance member_instance: Instance((int,), (), optional=False) member_type: atom.instance.Instance[builtins.int] member_value_type: builtins.int - member: Instance member_instance: Instance((int, float), (), optional=False) member_type: atom.instance.Instance[builtins.int | builtins.float] member_value_type: builtins.int | builtins.float - member: Instance member_instance: Instance((int, float, str), (), optional=False) member_type: atom.instance.Instance[builtins.int | builtins.float | builtins.str] member_value_type: builtins.int | builtins.float | builtins.str - member: Instance member_instance: Instance(_io.StringIO, None, dict(initial_value=str(1)), optional=False) member_type: atom.instance.Instance[_io.StringIO] member_value_type: _io.StringIO - member: Instance member_instance: Instance((_io.StringIO,), None, dict(initial_value=str(1)), optional=False) member_type: atom.instance.Instance[_io.StringIO] member_value_type: _io.StringIO - member: Instance member_instance: Instance(_io.StringIO, kwargs=dict(initial_value=str(1)), optional=False) member_type: atom.instance.Instance[_io.StringIO] member_value_type: _io.StringIO - member: Instance member_instance: Instance((_io.StringIO,), kwargs=dict(initial_value=str(1)), optional=False) member_type: atom.instance.Instance[_io.StringIO] member_value_type: _io.StringIO - member: Instance member_instance: Instance((_io.StringIO, str), kwargs=dict(initial_value=str(1)), optional=False) member_type: atom.instance.Instance[_io.StringIO | builtins.str] member_value_type: _io.StringIO | builtins.str - member: Instance member_instance: Instance((_io.StringIO, str, bytes), kwargs=dict(initial_value=str(1)), optional=False) member_type: atom.instance.Instance[_io.StringIO | builtins.str | builtins.bytes] member_value_type: _io.StringIO | builtins.str | builtins.bytes - member: Instance member_instance: Instance(_io.StringIO, factory=g, optional=False) member_type: atom.instance.Instance[_io.StringIO] member_value_type: _io.StringIO - member: Instance member_instance: Instance((_io.StringIO,), factory=g, optional=False) member_type: atom.instance.Instance[_io.StringIO] member_value_type: _io.StringIO - member: Instance member_instance: Instance((_io.StringIO, str), factory=g, optional=False) member_type: atom.instance.Instance[_io.StringIO | builtins.str] member_value_type: _io.StringIO | builtins.str - member: Instance member_instance: Instance((_io.StringIO, str, bytes), factory=g, optional=False) member_type: atom.instance.Instance[_io.StringIO | builtins.str | builtins.bytes] member_value_type: _io.StringIO | builtins.str | builtins.bytes # optional forward instance - member: ForwardInstance member_instance: ForwardInstance(resolver, optional=False) member_type: atom.instance.ForwardInstance[_io.StringIO] member_value_type: _io.StringIO - member: ForwardInstance member_instance: ForwardInstance(resolver1, optional=False) member_type: atom.instance.ForwardInstance[_io.StringIO] member_value_type: _io.StringIO - member: ForwardInstance member_instance: ForwardInstance(resolver2, optional=False) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str] member_value_type: _io.StringIO | builtins.str - member: ForwardInstance member_instance: ForwardInstance(resolver3, optional=False) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str | builtins.bytes] member_value_type: _io.StringIO | builtins.str | builtins.bytes - member: ForwardInstance member_instance: ForwardInstance(resolver, (), optional=False) member_type: atom.instance.ForwardInstance[_io.StringIO] member_value_type: _io.StringIO - member: ForwardInstance member_instance: ForwardInstance(resolver1, (), optional=False) member_type: atom.instance.ForwardInstance[_io.StringIO] member_value_type: _io.StringIO - member: ForwardInstance member_instance: ForwardInstance(resolver2, (), optional=False) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str] member_value_type: _io.StringIO | builtins.str - member: ForwardInstance member_instance: ForwardInstance(resolver3, (), optional=False) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str | builtins.bytes] member_value_type: _io.StringIO | builtins.str | builtins.bytes - member: ForwardInstance member_instance: ForwardInstance(resolver, None, dict(initial_value=str(1)), optional=False) member_type: atom.instance.ForwardInstance[_io.StringIO] member_value_type: _io.StringIO - member: ForwardInstance member_instance: ForwardInstance(resolver1, None, dict(initial_value=str(1)), optional=False) member_type: atom.instance.ForwardInstance[_io.StringIO] member_value_type: _io.StringIO - member: ForwardInstance member_instance: ForwardInstance(resolver2, None, dict(initial_value=str(1)), optional=False) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str] member_value_type: _io.StringIO | builtins.str - member: ForwardInstance member_instance: ForwardInstance(resolver3, None, dict(initial_value=str(1)), optional=False) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str | builtins.bytes] member_value_type: _io.StringIO | builtins.str | builtins.bytes - member: ForwardInstance member_instance: ForwardInstance(resolver, kwargs=dict(initial_value=str(1)), optional=False) member_type: atom.instance.ForwardInstance[_io.StringIO] member_value_type: _io.StringIO - member: ForwardInstance member_instance: ForwardInstance(resolver1, kwargs=dict(initial_value=str(1)), optional=False) member_type: atom.instance.ForwardInstance[_io.StringIO] member_value_type: _io.StringIO - member: ForwardInstance member_instance: ForwardInstance(resolver2, kwargs=dict(initial_value=str(1)), optional=False) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str] member_value_type: _io.StringIO | builtins.str - member: ForwardInstance member_instance: ForwardInstance(resolver3, kwargs=dict(initial_value=str(1)), optional=False) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str | builtins.bytes] member_value_type: _io.StringIO | builtins.str | builtins.bytes - member: ForwardInstance member_instance: ForwardInstance(resolver, factory=g, optional=False) member_type: atom.instance.ForwardInstance[_io.StringIO] member_value_type: _io.StringIO - member: ForwardInstance member_instance: ForwardInstance(resolver1, factory=g, optional=False) member_type: atom.instance.ForwardInstance[_io.StringIO] member_value_type: _io.StringIO - member: ForwardInstance member_instance: ForwardInstance(resolver2, factory=g, optional=False) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str] member_value_type: _io.StringIO | builtins.str - member: ForwardInstance member_instance: ForwardInstance(resolver3, factory=g, optional=False) member_type: atom.instance.ForwardInstance[_io.StringIO | builtins.str | builtins.bytes] member_value_type: _io.StringIO | builtins.str | builtins.bytes main: | import _io from typing import List, Tuple, Type from atom.api import Atom, {{ member }} def resolver() -> Type[_io.StringIO]: return _io.StringIO def resolver1() -> Tuple[Type[_io.StringIO]]: return (_io.StringIO,) def resolver2() -> Tuple[Type[_io.StringIO], Type[str]]: return (_io.StringIO, str) def resolver3() -> Tuple[Type[_io.StringIO], Type[str], Type[bytes]]: return (_io.StringIO, str, bytes) def g() -> _io.StringIO: return _io.StringIO() class A(Atom): m = {{ member_instance }} reveal_type(A.m) # N: Revealed type is "{{ member_type }}" reveal_type(A().m) # N: Revealed type is "{{ member_value_type }}"