pax_global_header00006660000000000000000000000064146111351710014512gustar00rootroot0000000000000052 comment=7e9eef4868f9c0a140ca7db973fbc17abb3a5702 beartype-0.18.5/000077500000000000000000000000001461113517100134205ustar00rootroot00000000000000beartype-0.18.5/.codecov.yml000066400000000000000000000017211461113517100156440ustar00rootroot00000000000000# --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. # # --------------------( SYNOPSIS )-------------------- # Project-wide "coverage" configuration specific to the third-party Codecov # service, which ignores the standard ".coveragerc" configuration leveraged by # the third-party "coverage" package collecting test coverage statistics in # favour of its own ad-hoc format. *sigh* # # --------------------( SEE ALSO )-------------------- # https://docs.codecov.io/docs/codecov-yaml # Codecov documentation on this file. # ....................{ MAIN }.................... # List of all glob-interpolated pathnames to avoid measuring coverage for. See # also: https://docs.codecov.io/docs/ignoring-paths ignore: # Ignore testing-specific paths. - beartype_test - conftest.py beartype-0.18.5/.coveragerc000066400000000000000000000076071461113517100155530ustar00rootroot00000000000000# --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. # # --------------------( SYNOPSIS )-------------------- # Project-wide "coverage" configuration, applied to all invocations of the # third-party "coverage" package collecting test coverage statistics. # # This configuration also applies to the higher-level third-party "pytest-cov" # pytest plugin, which leverages "coverage" under the hood and is thus also # configured via this configuration file. Of course, *NOTE THAT ONE SHOULD # NEVER USE "pytest-cov"* for the reasons discussed in "tox.ini". # # --------------------( CAVEATS )-------------------- # This configuration does *NOT* apply to the third-party Codecov service, which # instead leverages its own ad-hoc ".codecov.yml" format. *sigh* # # --------------------( SEE ALSO )-------------------- # https://pytest-cov.readthedocs.io/en/latest/config.html # "pytest-cov" documentation on this file and configuration in general. # https://coverage.readthedocs.io/en/latest/config.html # "coverage" documentation on this file. # ....................{ RUN }................... # Low-level settings configuring how coverage is measured. [run] #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: "coverage" is fragile and well-known to silently fail to behave as # expected if the option "source = ." is *NOT* explicitly specified here. # Notably, enabling the following option succeeds when generating CLI-based # reports but inexplicably fails when generating XML-formatted reports: # # Comma- or newline-separated list of package names to be measured. # source_pkgs = beartype #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Comma- or newline-separated list of all pathnames and/or package names to be # measured. Note that this should typically *NEVER* be modified. source = . # Comma- or newline-separated list of glob-formatted pathnames to be omitted # from measurement, specified only for completeness. omit = beartype_test/* # Measure branch coverage in additional to the default statement coverage. # Whereas statement coverage only measures the lower-level unintelligent metric # of the total number of executed statements, branch coverage measures the # higher-level intelligent metric of the total number of executed branch # destinations (i.e., the two or more target statements to which a source # branch may "jump" conditionally depending on the decision computed by that # branch). Branches include "if", "elif", "else", and "while" statements. # # See also: https://coverage.readthedocs.io/en/latest/branch.html#branch branch = True # ....................{ REPORT }................... # High-level settings configuring how coverage reports measurements. [report] # Comma- or newline-separated list of all pathnames measured above to be # included in on-disk reports. include = beartype/* # If the total coverage measurement falls under this number, exit with a # "coverage"-specific failure status code of 2. # # Note this number should be suffixed by at most a number of fractional digits # equal to the "precision" setting defined below. fail_under = 75.00 # Number of fractional digits to display for reported coverage percentages # *AND* constrain the "fail_under" setting defined above to. precision = 2 # Add a column "Missing" listing the comma-delimited line number ranges of all # missing lines for each covered submodule to coverage reports. show_missing = True # ....................{ REPORT ~ format }................... # Low-level settings configuring how coverage emits format-specific reports. [html] directory = ./coverage.html [xml] # output = ./coverage.xml beartype-0.18.5/.github/000077500000000000000000000000001461113517100147605ustar00rootroot00000000000000beartype-0.18.5/.github/FUNDING.yml000066400000000000000000000016441461113517100166020ustar00rootroot00000000000000--- # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. # # --------------------( SYNOPSIS )-------------------- # Project-wide GitHub-specific project funding configuration. # # --------------------( SEE ALSO )-------------------- # * Official documentation on file format, which is sufficiently deeply nested # that it will absolutely be broken by the time you eventually read this: # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository # ....................{ PLATFORMS }.................... # List of the GitHub usernames of one to four users that have successfully # enabled the GitHub Sponsors workflow. github: ['leycec'] beartype-0.18.5/.github/workflows/000077500000000000000000000000001461113517100170155ustar00rootroot00000000000000beartype-0.18.5/.github/workflows/python_release.yml000066400000000000000000000112511461113517100225610ustar00rootroot00000000000000--- # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. # # --------------------( SYNOPSIS )-------------------- # GitHub-specific continuous deployment (CD) configuration, enabling automated # publication of both source tarballs and binary wheels in various popular # formats to both GitHub itself and PyPI on each push of a tag to the "master" # branch of this repository. # # --------------------( SEE ALSO )-------------------- # * https://blog.chezo.uno/how-to-release-python-package-from-github-actions-d5a1d8edba6e # Well-authored blog post strongly inspiring this configuration. # * https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows # Official PyPA workflow also inspiring this configuration. # ....................{ TODO }.................... #FIXME: This workflow, while technically working, now emits this warning: # Trusted Publishers allows publishing packages to PyPI from automated # environments like GitHub Actions without needing to use username/password # combinations or API tokens to authenticate with PyPI. Read more: # https://docs.pypi.org/trusted-publishers # #Seems quite reasonable. Let's dig a bit deeper into this when time permits. # ....................{ METADATA }.................... # Non-human-readable (i.e., machine-readable) label associated with this # GitHub Actions workflow. name: release # ....................{ TRIGGER }.................... # Confine deployment to only new tags satisfying a release-specific format. on: push: # Sequence of glob expressions matched against "refs/tags" pushed to the # branches above. tags: - 'v*' # Match "v"-prefixed tags (e.g., "v6.9.6"). # ....................{ MAIN }.................... jobs: # ...................{ GITHUB }................... # Job publishing a human-readable changelog and codebase tarballs to GitHub # for this release. release: name: "Create tagged release on GitHub" runs-on: ubuntu-latest steps: - name: "Checking out repository..." uses: 'actions/checkout@v4' - name: "Publishing GitHub release..." uses: 'ncipollo/release-action@v1' with: name: "beartype ${{ github.ref }}" body: ${{ github.event.commits[0].message }} token: ${{ secrets.GITHUB_TOKEN }} # ...................{ PYPI }................... # Job publishing both a static distribution (sdist) and binary wheel to PyPI # for this release. pypi: name: "Publish tagged release to PyPI" runs-on: ubuntu-latest # Perform this job *ONLY* if the prior job succeeded. needs: release steps: - name: "Checking out repository..." uses: 'actions/checkout@v4' - name: "Installing latest stable Python 3.x..." uses: 'actions/setup-python@v5' with: python-version: '3.x' # See "python_test.yml" for further details. - name: 'Upgrading packager dependencies...' run: | set -xe python -VV python -m site python -m pip --quiet install --upgrade pip setuptools wheel - name: 'Installing package dependencies...' run: | python -m pip --quiet install --upgrade twine - name: "Creating source tarball and binary wheel..." run: | set -xe python setup.py sdist bdist_wheel - name: "Publishing PyPI release from tag..." uses: 'pypa/gh-action-pypi-publish@release/v1' with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }} #FIXME: Enable after integrating this "pypi" job into the "release" job #above. In theory, this should be trivial. Let's test this later, eh? # - name: "Publishing GitHub release assets from tag..." # uses: actions/upload-release-asset@v1.0.1 # env: # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # with: # upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps # asset_path: "dist/beartype-${{ github.ref }}.zip" # asset_name: "beartype-${{ github.ref }}.zip" # asset_content_type: application/zip beartype-0.18.5/.github/workflows/python_test.yml000066400000000000000000000356621461113517100221340ustar00rootroot00000000000000--- # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. # # --------------------( SYNOPSIS )-------------------- # GitHub-specific continuous integration (CI) configuration, enabling the usual # GitHub Actions workflow for pure-Python packages exercised by "tox". # # --------------------( SEE ALSO )-------------------- # * https://hynek.me/articles/python-github-actions # Well-authored blog post strongly inspiring this configuration. # ....................{ TODO }.................... #FIXME: [CACHING] Add support for caching "pip" downloads across runs. #Currently, unresolved issues in GitHub Actions prevents sane caching of "pip" #downloads. Naturally, horrifying hacks circumventing these issues do exist but #are presumably worse than these issues. See also this pertinent comment: # https://github.com/actions/cache/issues/342#issuecomment-673371329 #FIXME: [CACHING] Huzzah! The "setup-python" action itself now supports #out-of-the-box caching. Note that doing so will require a minor of #configuration on our part -- but nothing *TOO* tremendous, hopefully. See: # https://github.com/actions/setup-python#caching-packages-dependencies # ....................{ METADATA }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: Changes to this name *MUST* be manually synchronized with: # * The "|GitHub Actions badge|" image URL in our top-level "README.rst". #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Non-human-readable (i.e., machine-readable) label associated with this # GitHub Actions workflow. name: test # ....................{ TRIGGER }.................... # Confine testing to only... # # Note that "**" matches all (possibly deeply "/"-nested) branches. See also: # * https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet # GitHub-specific glob syntax for matching branches and tags below. on: # Pushes to the main branch. Pushes to other branches are assumed to be # developer-specific and thus already tested locally by that developer. push: branches: - main # Pull requests against the main branch. Pull requests against other branches # should, ideally, *NEVER* occur; if and when they do, we ignore them. pull_request: branches: - main # '**' # ....................{ VARIABLES }.................... # List of private environment variables specific to this configuration and # globally set for *ALL* jobs declared below. To avoid conflict with # third-party processes, prefix the name of each variable by "_". env: #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Optional in-flight test-time dependencies (e.g., NumPy, mypy) are # intentionally listed in the "beartype.meta.LIBS_TESTTIME_OPTIONAL" global # rather than below. "tox" isolates both the package being tested and its # dependency tree to virtual environments. Listing in-flight dependencies # here would install those dependencies outside those virtual environments, # thus reducing to a pointless, expensive, and failure-prone noop. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Whitespace-delimited list of the names of all Python packages to be # installed by "pip" below. Although this package has *NO* mandatory runtime # dependencies, fully exercising all tests necessitates these pre-flight # test-time dependencies. These include: # * "tox", the only mandatory test-time dependency. _PIP_PACKAGE_NAMES: | tox # ....................{ MAIN }.................... jobs: # ...................{ TESTS }................... # Job iteratively exercising our test suite against all Python interpreters # supported by this package (and also measuring the coverage of that suite). tests: # ..................{ MATRIX }.................. strategy: matrix: # List of all platform-specific Docker images to test against, # including: # * The latest Long-Term Service (LTS) release of Ubuntu Linux, still # the most popular Linux distro and thus a sane baseline. # * The latest *whatever* release of Microsoft Windows. Although Linux # and macOS are both POSIX-compliant and thus crudely comparable from # the low-level CLI perspective, Windows is POSIX-noncompliant and # thus heavily divergent from both macOS and Linux. # * The latest *whatever* release of Apple macOS. We don't particularly # need to exercise tests on macOS, given the platform's patent # POSIX-compliant low-level similarities to Linux, but... what the # heck. Why not? Since this is the lowest priority, we defer macOS # testing until last. platform: [ubuntu-latest, windows-latest, macos-latest] #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: Changes to this section *MUST* be manually synchronized with: # * The "envlist" setting of the "[tox]" subsection in "tox.ini". # * The "include" setting below. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # List of all "tox" environments (defined by the "envlist" setting of # the "[tox]" subsection in "tox.ini") to be tested, which the # ${TOXENV} environment variable declared below exposes to "tox". tox-env: - py38-coverage - py39-coverage - py310-coverage - py311-coverage - py312-coverage #FIXME: Uncomment after we resolve tests currently broken under *ANY* #PyPy version. All tests used to pass under PyPy 3.7 and 3.8, but were #recently broken by work generalizing @beartype to decorate builtin #method descriptors (e.g., @property, @classmethod, @staticmethod). # - pypy37-coverage # - pypy38-coverage # - pypy39-coverage # Map each "tox" environment name listed in the "tox-env" list above to # the corresponding "python-version" string supported by the # "actions/setup-python" GitHub Action run below. # # Note that: # * Python version specifiers *MUST* be quoted: e.g., # # Do this. # python-version: "3.10" # # Do *NOT* do this. # python-version: 3.10 # Why? Because YAML sensibly treats an unquoted literal satisfying # floating-point syntax as a floating-point number and thus silently # truncates *ALL* ignorable zeroes suffixing that number (e.g., # truncating 3.10 to 3.1). That then results in non-human-readable CI # errors, as discussed upstream at: # https://github.com/actions/setup-python/issues/160#issuecomment-724485470 # * Python pre-releases may be selected with a space-delimited range # embedded in a single quoted version specifier. For example, # selecting the Python 3.11 pre-release reduces to: # python-version: "3.11.0-alpha - 3.11.0" include: - tox-env: py38-coverage python-version: "3.8" - tox-env: py39-coverage python-version: "3.9" - tox-env: py310-coverage python-version: "3.10" - tox-env: py311-coverage python-version: "3.11" - tox-env: py312-coverage python-version: "3.12" #- tox-env: pypy37-coverage # python-version: "pypy-3.7" #- tox-env: pypy38-coverage # python-version: "pypy-3.8" #FIXME: Uncomment after PyPy 3.9 goes live. # - tox-env: pypy39-coverage # python-version: "pypy-3.9" # ..................{ SETTINGS }.................. # Arbitrary human-readable description. name: "[${{ matrix.platform }}] Python ${{ matrix.python-version }} CI" # Name of the current Docker image to run tests under. runs-on: "${{ matrix.platform }}" # Time in minutes to wait on the command pipeline run below to exit # *BEFORE* sending a non-graceful termination request (i.e., "SIGTERM" # under POSIX-compliant systems). timeout-minutes: 10 # ..................{ VARIABLES }.................. # External shell environment variables exposed to commands run below. env: # Prevent "pip" from wasting precious continuous integration (CI) minutes # deciding whether it should be upgrading. We're *NOT* upgrading you, # "pip". Accept this and let us test faster. PIP_NO_PIP_VERSION_CHECK: 1 # Map from the current item of the "tox-env" list defined above to the # ${TOXENV} environment variable recognized by "tox". TOXENV: "${{ matrix.tox-env }}" # ..................{ PROCESS }.................. steps: # ..................{ SETUP }.................. - name: 'Checking out repository...' uses: 'actions/checkout@v4' - name: "Installing Python ${{ matrix.python-version }}..." uses: 'actions/setup-python@v5' with: python-version: "${{ matrix.python-version }}" - name: 'Displaying Python metadata...' run: | python3 -VV python3 -m site # ..................{ INSTALL }.................. # Note that: # * This command *MUST* be platform-agnostic by running under both: # * POSIX-compliant platforms (e.g., Linux, macOS). # * POSIX-noncompliant platforms (e.g., Windows). # In particular, commands that assume a POSIX-compliant shell (e.g., # Bash) *MUST* be avoided. # * Packaging dependencies (e.g., "pip") are upgraded *BEFORE* all # remaining dependencies (e.g., "tox"). - name: 'Upgrading packager dependencies...' run: | python3 -m pip --quiet install --upgrade pip setuptools wheel - name: 'Installing package dependencies...' run: | python3 -m pip --quiet install --upgrade ${{ env._PIP_PACKAGE_NAMES }} # ..................{ TYPE-CHECK }.................. # Type-check this package *BEFORE* testing this package. Why? Because # "tox" internally creates a copy of this package residing at a temporary # "build/lib/{package_name}" subdirectory. When type-checking this package # *AFTER* testing this package, the existence of that subdirectory # confuses mypy into believing that multiple duplicate copies of this # package exist, which induces mypy to emit false positives, which induces # this entire job and thus run to fail. In short, mypy. # Type-check this package with "pyright". See also: # https://github.com/jakebailey/pyright-action - name: 'Type-checking package with "pyright"...' uses: 'jakebailey/pyright-action@v2' with: python-version: "${{ matrix.python-version }}" #FIXME: Revisit @jpetrucciani's otherwise stellar GitHub Action #"jpetrucciani/mypy-check" once the issue tracker there settles a bit. #See also these open issue currently blocking our usage here: # https://github.com/jpetrucciani/mypy-check/issues/31 # https://github.com/jpetrucciani/mypy-check/issues/30 # Type-check this package with "mypy". - name: 'Type-checking package with "mypy:"...' run: | # Manually install "mypy" in the standard way. python3 -m pip --quiet install mypy # Log this "mypy" version for debuggability. mypy --version # Run this "mypy" instance against our main package. mypy ./beartype/ # ..................{ TEST }.................. - name: 'Testing package with "tox"...' # Run the subsequent script as a Bash script. Although POSIX-compliant # platforms (e.g., Linux, macOS) sensibly default to Bash, Windows # insanely defaults to a Windows-specific shell (e.g., PowerShell). shell: bash run: | # If the current platform is macOS, export a "tox"-specific # environment variable circumventing "pip" installation issues by # instructing "tox" to reinstall already installed Python packages. # By default, "tox" avoids doing so for efficiency. This is required # to specifically circumvent installation of NumPy under macOS. As # discussed at numpy/numpy#15947, macOS bundles a patently broken # BLAS replacement called "Accelerate" causing NumPy to raise # exceptions on importation resembling: # RuntimeError: Polyfit sanity test emitted a warning, most # likely due to using a buggy Accelerate backend. If you compiled # yourself, more information is available at # https://numpy.org/doc/stable/user/building.html#accelerated-blas-lapack-libraries # Otherwise report this to the vendor that provided NumPy. # RankWarning: Polyfit may be poorly conditioned # # The kludge leveraged here is the canonical solution. See also: # https://github.com/numpy/numpy/issues/15947#issuecomment-745428684 # # Ideally, we would instead isolate setting this environment variable # in a prior step with sane GitHub Actions syntax: e.g., # if: ${{ matrix.platform }} == 'macos-latest' # env: # _TOX_PIP_INSTALL_OPTIONS: '--force-reinstall' # # Sadly, the "env:" map only locally exports the environment # variables it declares to the current step. Thanks, GitHub Actions. if [[ ${{ matrix.platform }} == 'macos-latest' ]]; then export _TOX_PIP_INSTALL_OPTIONS='--force-reinstall' echo "Massaging macOS dependencies with \"pip install ${_TOX_PIP_INSTALL_OPTIONS}\"..." fi # Dismantled, this is: # * "--skip-missing-interpreters=false" disables the corresponding # "skip_missing_interpreters = true" setting globally enabled by # our top-level "tox.ini" configuration, forcing CI failures for # unavailable Python environments. See also: # https://github.com/tox-dev/tox/issues/903 python3 -m tox --skip-missing-interpreters=false # ..................{ COVERAGE }.................. - name: 'Publishing test coverage to Codecov...' uses: 'codecov/codecov-action@v4' with: name: "${{ matrix.platform }}-${{ matrix.python-version }}" beartype-0.18.5/.gitignore000066400000000000000000000124121461113517100154100ustar00rootroot00000000000000# --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. # # --------------------( SYNOPSIS )-------------------- # Git-specific dotfile instructing git to avoid tracking repository paths # matching one or more glob expressions listed below by default. # # --------------------( SEE ALSO )-------------------- # For further details, see: # * "man gitignore" for high-level commentary. # * "man 7 glob" for low-level commentary on glob syntax. Note, in particular, # that glob() and hence ".gitignore" files support only a proper subset of # full glob syntax supported by POSIX-compatible shells (e.g., bash, zsh). # ....................{ DIRECTORIES ~ top-level }.................... # Ignore all top-level Buildout-specific state directories. /develop-eggs/ /downloads/ /eggs/ /lib/ /lib64/ # Ignore all top-level Coverage.py-specific temporary directories. /htmlcov/ # Ignore all top-level Flask-specific temporary and private directories. /.webassets-cache/ /instance/ # Ignore all top-level Hypothesis-specific temporary directories. /.hypothesis/ # Ignore all top-level mkdocs-specific output directories. /site/ # Ignore all top-level mypy-specific temporary directories. /.mypy_cache/ # Ignore all top-level nox-specific temporary directories. /.nox/ # Ignore all top-level pip-specific temporary directories. /pip-wheel-metadata/ # Ignore all top-level pytest-specific temporary directories. /.cache/ /.pytest_cache/ # Ignore all top-level PyBuilder-specific temporary directories. /target/ # Ignore all top-level Scrapy-specific temporary directories. /.scrapy/ # Ignore all top-level setuptools-specific temporary directories. /build/ /dist/ /.eggs/ /*.egg-info/ #FIXME: We'll want to additionally list the "/doc/src/" subdirectory containing #API documentation autogenerated by the "autodoc" extension. # Ignore all top-level Sphinx-specific output subdirectories, including: # * "/doc/src/api", the output subdirectory managed by the "autoapi" extension. # * "/doc/trg", the output subdirectory managed by Sphinx itself. # # Note this constitutes a usability versus space tradeoff: ignoring these # directories substantially reduces repository size, but requires end users to # manually install Sphinx to locally generate HTML documentation if they so # choose. Since HTML documentation is remotely available via Read The Docs # (RTD), we consider this a more than worthwhile tradeoff. /doc/src/api /doc/trg/ # Ignore all top-level tox-specific temporary directories. /.tox/ # Ignore all top-level user-specific PEP 582-compliant directories. /__pypackages__/ # Ignore all top-level user-specific Spyder IDE project directories. /.spyderproject/ /.spyproject/ # Ignore all top-level user-specific venv (virtual environment) directories. /env/ /venv/ /ENV/ /env.bak/ /venv.bak/ # ....................{ DIRECTORIES ~ general }.................... # Ignore all Buildout-specific state subdirectories. parts/ # Ignore all Python-specific cache subdirectories. __pycache__/ # Ignore all PyCharm-specific project subdirectories. .idea/ # Ignore all Pyre-specific cache subdirectories. .pyre/ # Ignore all Rope-specific project subdirectories. .ropeproject/ # ....................{ FILES ~ top-level }.................... # Ignore all top-level Buildout-specific state files. /.installed.cfg # Ignore all top-level Celery-specific state files. /celerybeat-schedule /celerybeat.pid # Ignore all top-level Coverage.py-specific output files. /.coverage /.coverage.* /coverage.xml # Ignore all top-level Django-specific binary databases. /db.sqlite3 /db.sqlite3-journal # Ignore all top-level mypy-specific state files. /.dmypy.json /dmypy.json # Ignore all top-level Nose-specific output files. /nosetests.xml # Ignore all top-level pip-specific output files. /pip-log.txt /pip-delete-this-directory.txt # Ignore all top-level setuptools-specific output files. /MANIFEST # Ignore top-level PyInstaller-specific output files *NOT* intended to be # modified. ".spec"-suffixed files *ARE* intended to be modified and are thus # excluded. /*.manifest # Ignore all top-level user-specific venv (virtual environment) directories. /.env /.venv # ....................{ FILES ~ general }.................... # Ignore all audio and video files. *.mp4 # Ignore all C extensions. *.so # Ignore all data interchange files. *.csv # Ignore all Django-specific private files. local_settings.py # Ignore all Jython-specific byte-compiled Python files. *$py.class # Ignore all "gettext"-specific intermediary translation files. *.mo *.pot # Ignore all Jupyter Notebook-specific checkpoint files. .ipynb_checkpoints # Ignore all logfiles. *.log # Ignore all macOS-specific filesystem viewer configuration files. .DS_Store # Ignore all pyenv-specific state files. .python-version # Ignore all "python-coverage"-specific output Python files. *.py,cover # Ignore all Python-specific byte-compiled, optimized, and DLL files. *.py[cod] # Ignore all Python-specific EGG packages. *.egg # Ignore all SageMath-specific output Python files. *.sage.py # Ignore all temporary files. *~ *.sw? # Ignore all "trace"-specific output files. *.cover beartype-0.18.5/.readthedocs.yaml000066400000000000000000000033271461113517100166540ustar00rootroot00000000000000# --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. # # --------------------( SYNOPSIS )-------------------- # Project-wide Read The Docs (RTD) configuration. # ....................{ METADATA }.................... # RTD configuration scheme version this file complies with. version: 2 # ....................{ BUILD }.................... build: # Build under the most recent Long Term Service (LTS) release of Ubuntu. os: ubuntu-22.04 tools: # Build under the most recently released minor version of CPython. python: '3.12' # ....................{ PYTHON }.................... python: install: # Install documentation dependencies by effectively running this command: # $ pip install -e .[doc-rtd] # # That is, instruct RTD to install the "doc-rtd" extra defined by our # top-level setuptools-based "setup.py" script, preserving DRY. - method: pip path: . extra_requirements: - doc-rtd # ....................{ SPHINX }.................... sphinx: # Relative filename of the Sphinx-specific configuration script configuring # document generation for this project. configuration: doc/src/conf.py #FIXME: Unclear why we need this, but we probably do. *shrug* builder: dirhtml #FIXME: Uncomment the following *AFTER* eliminating all warnings across our #documentation. Since we have yet to do so, this remains commented. It is sad. # # For safety, convert non-fatal Sphinx warnings into fatal errors. # fail_on_warning: true beartype-0.18.5/LICENSE000066400000000000000000000020671461113517100144320ustar00rootroot00000000000000MIT License Copyright (c) 2014-2024 Beartype authors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. beartype-0.18.5/MANIFEST.in000066400000000000000000000035751461113517100151700ustar00rootroot00000000000000# --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. # # --------------------( SYNOPSIS )-------------------- # Template with which setuptools generates this project's "MANIFEST" file. # ....................{ TODO }.................... #FIXME: Add a new functional test exercising the correctness of this file, #probably by manually running a shell command resembling: # $ python3 setup.py sdist bdist_wheel #...where "python3" should be replaced by the command invoking the active #Python interpreter. It's likely that we'll then have to parse the output of #that command for lines suggestive of syntactic errors in this file. *shrug* # ....................{ INCLUDE }.................... # Include all requisite top-level install-time files. include .coveragerc include .readthedocs.yaml include LICENSE include MANIFEST.in include README.rst include conftest.py include mypy.ini include pyproject.toml include pyrightconfig.json include pytest.ini include setup.cfg include setup.py include tox.ini # Include all requisite package-level install-time files. include beartype/py.typed # ....................{ INCLUDE ~ recursive }.................... # Include all requisite project-specific test packages. # # Note that these packages are *ONLY* required at test time and hence omitted # from the "packages" key passed to the setup() function by "setup.py". Welcome # to Setuptools Hell, dear friend. recursive-include beartype_test * # Include all optional documentation. recursive-include doc * # ....................{ EXCLUDE }.................... # Exclude all ignorable cache files. recursive-exclude * __pycache__ recursive-exclude * *.pyc recursive-exclude * *.pyo beartype-0.18.5/README.rst000066400000000000000000000645161461113517100151230ustar00rootroot00000000000000.. # ------------------( LICENSE )------------------ .. # Copyright (c) 2014-2024 Beartype authors. .. # See "LICENSE" for further details. .. # .. # ------------------( SEO )------------------ .. # Metadata converted into HTML-specific meta tags parsed by search engines. .. # Note that: .. # * The "description" should be no more than 300 characters and ideally no .. # more than 150 characters, as search engines may silently truncate this .. # description to 150 characters in edge cases. .. meta:: :description lang=en: Beartype is an open-source pure-Python PEP-compliant constant-time runtime type checker emphasizing efficiency and portability. .. # ------------------( SYNOPSIS )------------------ ================= |beartype-banner| ================= |rtd-badge| |ci-badge| |codecov-badge| ⚠ `Beartype documentation lives at ReadTheDocs (RTD) `__. It's readable, structured, and soothing to the deep folds of your big galactic brain. Open your mind to an ocean of mundane knowledge that will exhaust you at work. Enter... **the Bearpedia:** https://beartype.readthedocs.io The document you are now reading was once a monolithic ~316Kb file known to induce migraines in 22% of the whole devops population. For your safety, that document no longer exists. This is how much beartype cares. **Beartype** is an `open-source `__ `pure-Python `__ `PEP-compliant `__ `near-real-time `__ `hybrid runtime-static `__ `third-generation `__ `type checker `__ emphasizing efficiency, usability, unsubstantiated jargon we just made up, and thrilling puns. .. #FIXME: Once we actually receive a sponsor at this tier, please remove this .. #placeholder as well as the icon links below. kthx .. #The `Bear Team `__ gratefully thanks `our family of .. #breathtaking GitHub Sponsors `__: .. # .. #* **Your iconic URL here.** `Let us bestow you with eyeballs `__. .. #FIXME: Once we actually receive a sponsor at this tier, please remove this .. #placeholder as well as the icon links below. kthx .. # |icon-for-glorious-sponsor| .. code-block:: bash # Install beartype. $ pip3 install beartype # Edit the "{your_package}.__init__" submodule with your favourite IDE. $ vim {your_package}/__init__.py # <-- so, i see that you too vim .. code-block:: python from beartype.claw import beartype_this_package # <-- hype comes beartype_this_package() # <-- hype goes Beartype now implicitly type-checks *all* annotated classes, callables, and variable assignments across *all* submodules of your package. Congrats. This day all bugs die. But why stop at the burning tires in only *your* code? Your app depends on a sprawling ghetto of other packages, modules, and services. How riddled with infectious diseases is *that* code? You're about to find out. .. code-block:: python # ....................{ BIG BEAR }.................... # Warn about type hint violations in *OTHER* packages outside your control; # only raise exceptions from violations in your package under your control. # Again, at the very top of your "{your_package}.__init__" submodule: from beartype import BeartypeConf # <-- this isn't your fault from beartype.claw import beartype_all, beartype_this_package # <-- you didn't sign up for this beartype_this_package() # <-- raise exceptions in your code beartype_all(conf=BeartypeConf(violation_type=UserWarning)) # <-- emit warnings from other code Beartype now implicitly type-checks *all* annotated classes, callables, and variable assignments across *all* submodules of *all* packages. When **your** package violates type safety, beartype raises an exception. When any **other** package violates type safety, beartype just emits a warning. The triumphal fanfare you hear is probably your userbase cheering. This is how the QA was won. Beartype also publishes a `plethora of APIs for fine-grained control over type-checking `. For those who are about to QA, beartype salutes you. Would you like to know more? # So let's do this. $ python3 .. code-block:: python # ....................{ RAISE THE PAW }.................... # Manually enforce type hints across individual classes and callables. # Do this only if you want a(nother) repetitive stress injury. # Import the @beartype decorator. >>> from beartype import beartype # <-- eponymous import; it's eponymous # Annotate @beartype-decorated classes and callables with type hints. >>> @beartype # <-- you too will believe in magic ... def quote_wiggum(lines: list[str]) -> None: ... print('“{}”\n\t— Police Chief Wiggum'.format("\n ".join(lines))) # Call those callables with valid parameters. >>> quote_wiggum(["Okay, folks. Show's over!", " Nothing to see here. Show's…",]) “Okay, folks. Show's over! Nothing to see here. Show's…” — Police Chief Wiggum # Call those callables with invalid parameters. >>> quote_wiggum([b"Oh, my God! A horrible plane crash!", b"Hey, everybody! Get a load of this flaming wreckage!",]) Traceback (most recent call last): File "", line 1, in File "", line 30, in quote_wiggum File "/home/springfield/beartype/lib/python3.9/site-packages/beartype/_decor/_code/_pep/_error/errormain.py", line 220, in get_beartype_violation raise exception_cls( beartype.roar.BeartypeCallHintParamViolation: @beartyped quote_wiggum() parameter lines=[b'Oh, my God! A horrible plane crash!', b'Hey, everybody! Get a load of thi...'] violates type hint list[str], as list item 0 value b'Oh, my God! A horrible plane crash!' not str. # ....................{ MAKE IT SO }.................... # Squash bugs by refining type hints with @beartype validators. >>> from beartype.vale import Is # <---- validator factory >>> from typing import Annotated # <---------------- if Python ≥ 3.9.0 # >>> from typing_extensions import Annotated # <-- if Python < 3.9.0 # Validators are type hints constrained by lambda functions. >>> ListOfStrings = Annotated[ # <----- type hint matching non-empty list of strings ... list[str], # <----------------- type hint matching possibly empty list of strings ... Is[lambda lst: bool(lst)] # <-- lambda matching non-empty object ... ] # Annotate @beartype-decorated callables with validators. >>> @beartype ... def quote_wiggum_safer(lines: ListOfStrings) -> None: ... print('“{}”\n\t— Police Chief Wiggum'.format("\n ".join(lines))) # Call those callables with invalid parameters. >>> quote_wiggum_safer([]) beartype.roar.BeartypeCallHintParamViolation: @beartyped quote_wiggum_safer() parameter lines=[] violates type hint typing.Annotated[list[str], Is[lambda lst: bool(lst)]], as value [] violates validator Is[lambda lst: bool(lst)]. # ....................{ AT ANY TIME }.................... # Type-check anything against any type hint – anywhere at anytime. >>> from beartype.door import ( ... is_bearable, # <-------- like "isinstance(...)" ... die_if_unbearable, # <-- like "assert isinstance(...)" ... ) >>> is_bearable(['The', 'goggles', 'do', 'nothing.'], list[str]) True >>> die_if_unbearable([0xCAFEBEEF, 0x8BADF00D], ListOfStrings) beartype.roar.BeartypeDoorHintViolation: Object [3405692655, 2343432205] violates type hint typing.Annotated[list[str], Is[lambda lst: bool(lst)]], as list index 0 item 3405692655 not instance of str. # ....................{ GO TO PLAID }.................... # Type-check anything in around 1µs (one millionth of a second) – including # this list of one million 2-tuples of NumPy arrays. >>> from beartype.door import is_bearable >>> from numpy import array, ndarray >>> data = [(array(i), array(i)) for i in range(1000000)] >>> %time is_bearable(data, list[tuple[ndarray, ndarray]]) CPU times: user 31 µs, sys: 2 µs, total: 33 µs Wall time: 36.7 µs True Beartype brings Rust_- and `C++`_-inspired `zero-cost abstractions `__ into the lawless world of `dynamically-typed`_ Python by `enforcing type safety at the granular level of functions and methods `__ against `type hints standardized by the Python community `__ in `O(1) non-amortized worst-case time with negligible constant factors `__. If the prior sentence was unreadable jargon, `see our friendly and approachable FAQ for a human-readable synopsis `__. Beartype is `portably implemented `__ in `Python 3 `__, `continuously stress-tested `__ via `GitHub Actions`_ **×** tox_ **×** pytest_ **×** Codecov_, and `permissively distributed `__ under the `MIT license`_. Beartype has *no* runtime dependencies, `only one test-time dependency `__, and `only one documentation-time dependency `__. Beartype supports `all actively developed Python versions `__, `all Python package managers `__, and `multiple platform-specific package managers `__. .. # FIXME: Gah! Libraries.io has fallen down and cannot get back up... *AGAIN.* .. # Beartype `powers quality assurance across the Python ecosystem `__. .. # FIXME: Remove *ALL* of the following URLs except those specifically .. # required above -- which should be most of them, frankly. .. # ------------------( IMAGES )------------------ .. |beartype-banner| image:: https://raw.githubusercontent.com/beartype/beartype-assets/main/banner/logo.png :target: https://beartype.readthedocs.io :alt: beartype —[ the bare-metal type checker ]— .. |beartype-contributors| image:: https://contrib.rocks/image?repo=beartype/beartype :target: https://github.com/beartype/beartype/graphs/contributors :alt: Beartype contributors .. |beartype-stars| image:: https://star-history.com/#beartype/beartype&Date :target: https://github.com/beartype/beartype/stargazers :alt: Beartype stargazers .. # ------------------( IMAGES ~ badge )------------------ .. |bear-ified| image:: https://raw.githubusercontent.com/beartype/beartype-assets/main/badge/bear-ified.svg :align: top :target: https://beartype.readthedocs.io :alt: bear-ified .. |ci-badge| image:: https://github.com/beartype/beartype/workflows/test/badge.svg :target: https://github.com/beartype/beartype/actions?workflow=test :alt: beartype continuous integration (CI) status .. |codecov-badge| image:: https://codecov.io/gh/beartype/beartype/branch/main/graph/badge.svg?token=E6F4YSY9ZQ :target: https://codecov.io/gh/beartype/beartype :alt: beartype test coverage status .. |rtd-badge| image:: https://readthedocs.org/projects/beartype/badge/?version=latest :target: https://beartype.readthedocs.io/en/latest/?badge=latest :alt: beartype Read The Docs (RTD) status .. # ------------------( IMAGES ~ downstream )------------------ .. # Insert links to GitHub Sponsors funding at the icon level here, please! .. # ------------------( LINKS ~ beartype : funding )------------------ .. _BETSE: https://github.com/betsee/betse .. _BETSEE: https://github.com/betsee/betsee .. _GitHub Sponsors: https://github.com/sponsors/leycec .. _Paul Allen: https://en.wikipedia.org/wiki/Paul_Allen .. _Paul Allen Discovery Center: http://www.alleninstitute.org/what-we-do/frontiers-group/discovery-centers/allen-discovery-center-tufts-university .. _Paul Allen Discovery Center award: https://www.alleninstitute.org/what-we-do/frontiers-group/news-press/press-resources/press-releases/paul-g-allen-frontiers-group-announces-allen-discovery-center-tufts-university .. _Paul G. Allen Frontiers Group: https://www.alleninstitute.org/what-we-do/frontiers-group .. _Tufts University: https://www.tufts.edu .. _beartype sponsorship: https://github.com/sponsors/leycec .. # ------------------( LINKS ~ beartype : local )------------------ .. _beartype license: LICENSE .. # ------------------( LINKS ~ beartype : local : module )------------------ .. _beartype errormain: beartype/_decor/_code/_pep/_error/errormain.py .. _beartype pephint: beartype/_decor/_code/_pep/_pephint.py .. _beartype test data pep: beartype_test/unit/data/hint/pep/proposal/ .. _beartype test data pep 484: beartype_test/unit/data/hint/pep/proposal/data_hintpep484.py .. _@callable_cached: beartype/_util/cache/utilcachecall.py .. _beartype util data pep: beartype/_util/hint/data/pep/proposal/ .. _beartype util data pep parent: beartype/_util/hint/data/pep/utilhintdatapep.py .. _beartype util pep: beartype/_util/hint/pep/proposal .. # ------------------( LINKS ~ beartype : package )------------------ .. _beartype Anaconda: https://anaconda.org/conda-forge/beartype .. _beartype Gentoo: https://github.com/leycec/raiagent .. _beartype Homebrew: https://github.com/beartype/homebrew-beartype .. _beartype MacPorts: https://ports.macports.org/port/py-beartype .. _beartype PyPI: https://pypi.org/project/beartype .. # ------------------( LINKS ~ beartype : package : meta )------------------ .. _Libraries.io: https://libraries.io .. _beartype dependents: https://libraries.io/pypi/beartype/dependents .. # ------------------( LINKS ~ beartype : github )------------------ .. _beartype: https://github.com/beartype/beartype .. _beartype issues: https://github.com/beartype/beartype/issues .. _beartype 1.0.0: https://github.com/beartype/beartype/issues/7 .. _beartype codebase: https://github.com/beartype/beartype/tree/main/beartype .. _beartype organization: https://github.com/beartype .. _beartype profiler: https://github.com/beartype/beartype/blob/main/bin/profile.bash .. _beartype pulls: https://github.com/beartype/beartype/pulls .. _beartype tests: https://github.com/beartype/beartype/actions?workflow=tests .. # ------------------( LINKS ~ beartype : github : user )------------------ .. _patrick-kidger: https://github.com/patrick-kidger .. _harens: https://github.com/harens .. _leycec: https://github.com/leycec .. # ------------------( LINKS ~ beartype : rtd )------------------ .. _beartype APIs: https://beartype.readthedocs.io/en/latest/api .. _beartype RTD: https://beartype.readthedocs.io .. _beartype ELI5: https://beartype.readthedocs.io/en/latest/eli5 .. _beartype FAQ: https://beartype.readthedocs.io/en/latest/faq .. _beartype PEPs: https://beartype.readthedocs.io/en/latest/pep .. _beartype hybrid: https://beartype.readthedocs.io/en/latest/faq/#faq-hybrid .. _beartype install: https://beartype.readthedocs.io/en/latest/install .. _beartype math: https://beartype.readthedocs.io/en/latest/math .. _beartype pure: https://beartype.readthedocs.io/en/latest/faq/#faq-pure .. _beartype third: https://beartype.readthedocs.io/en/latest/faq/#faq-third .. # ------------------( LINKS ~ github )------------------ .. _GitHub Actions: https://github.com/features/actions .. _GitHub account signin: https://github.com/login .. _GitHub account signup: https://github.com/join .. _gitter: https://gitter.im .. # ------------------( LINKS ~ idea )------------------ .. _Denial-of-Service: https://en.wikipedia.org/wiki/Denial-of-service_attack .. _DRY: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself .. _IDE: https://en.wikipedia.org/wiki/Integrated_development_environment .. _JIT: https://en.wikipedia.org/wiki/Just-in-time_compilation .. _SQA: https://en.wikipedia.org/wiki/Software_quality_assurance .. _amortized analysis: https://en.wikipedia.org/wiki/Amortized_analysis .. _computer vision: https://en.wikipedia.org/wiki/Computer_vision .. _continuous integration: https://en.wikipedia.org/wiki/Continuous_integration .. _duck typing: https://en.wikipedia.org/wiki/Duck_typing .. _gratis versus libre: https://en.wikipedia.org/wiki/Gratis_versus_libre .. _memory safety: https://en.wikipedia.org/wiki/Memory_safety .. _multiple dispatch: https://en.wikipedia.org/wiki/Multiple_dispatch .. _near-real-time: https://en.wikipedia.org/wiki/Real-time_computing#Near_real-time .. _random walk: https://en.wikipedia.org/wiki/Random_walk .. _real-time: https://en.wikipedia.org/wiki/Real-time_computing .. _set theory: https://en.wikipedia.org/wiki/Set_theory .. _shield wall: https://en.wikipedia.org/wiki/Shield_wall .. _dynamic typing: .. _dynamically-typed: .. _static typing: .. _statically-typed: https://en.wikipedia.org/wiki/Type_system .. _topological sort: https://en.wikipedia.org/wiki/Topological_sorting .. _type inference: https://en.wikipedia.org/wiki/Type_inference .. _zero-cost abstraction: https://boats.gitlab.io/blog/post/zero-cost-abstractions .. # ------------------( LINKS ~ kipling )------------------ .. _The Jungle Book: https://www.gutenberg.org/files/236/236-h/236-h.htm .. _Shere Khan: https://en.wikipedia.org/wiki/Shere_Khan .. # ------------------( LINKS ~ math )------------------ .. _Euler–Mascheroni constant: https://en.wikipedia.org/wiki/Euler%E2%80%93Mascheroni_constant .. _coupon collector's problem: https://en.wikipedia.org/wiki/Coupon_collector%27s_problem .. _Big O: https://en.wikipedia.org/wiki/Big_O_notation .. # ------------------( LINKS ~ math : set )------------------ .. _conjunction: https://en.wikipedia.org/wiki/Logical_conjunction .. _disjunction: https://en.wikipedia.org/wiki/Logical_disjunction .. _intersection: https://en.wikipedia.org/wiki/Intersection_(set_theory) .. _relative set complement: https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement .. # ------------------( LINKS ~ math : type )------------------ .. _covariance: https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science) .. # ------------------( LINKS ~ meme )------------------ .. _RNGesus: https://knowyourmeme.com/memes/rngesus .. _goes up to eleven: https://www.youtube.com/watch?v=uMSV4OteqBE .. _greased lightning: https://www.youtube.com/watch?v=H-kL8A4RNQ8 .. _ludicrous speed: https://www.youtube.com/watch?v=6tTvklMXeFE .. _the gripping hand: http://catb.org/jargon/html/O/on-the-gripping-hand.html .. # ------------------( LINKS ~ os : linux )------------------ .. _Gentoo: https://www.gentoo.org .. # ------------------( LINKS ~ os : macos )------------------ .. _macOS: https://en.wikipedia.org/wiki/MacOS .. _HomeBrew: https://brew.sh .. _MacPorts: https://www.macports.org .. # ------------------( LINKS ~ other )------------------ .. _heliotrope: https://en.wikipedia.org/wiki/Heliotropium .. # ------------------( LINKS ~ py )------------------ .. _Python: https://www.python.org .. _Python status: https://devguide.python.org/#status-of-python-branches .. _pip: https://pip.pypa.io .. # ------------------( LINKS ~ py : cli )------------------ .. _-O: https://docs.python.org/3/using/cmdline.html#cmdoption-o .. _PYTHONOPTIMIZE: https://docs.python.org/3/using/cmdline.html#envvar-PYTHONOPTIMIZE .. # ------------------( LINKS ~ py : interpreter )------------------ .. _Brython: https://brython.info .. _CPython: https://github.com/python/cpython .. _Nuitka: https://nuitka.net .. _Numba: https://numba.pydata.org .. _PyPy: https://www.pypy.org .. # ------------------( LINKS ~ py : interpreter : cpython )------------------ .. _CPython bug tracker: https://github.com/python/cpython/issues .. # ------------------( LINKS ~ py : lang )------------------ .. _generic alias parameters: https://docs.python.org/3/library/stdtypes.html#genericalias.__parameters__ .. _isinstancecheck: https://docs.python.org/3/reference/datamodel.html#customizing-instance-and-subclass-checks .. _mro: https://docs.python.org/3/library/stdtypes.html#class.__mro__ .. _object: https://docs.python.org/3/reference/datamodel.html#basic-customization .. _operator precedence: https://docs.python.org/3/reference/expressions.html#operator-precedence .. # ------------------( LINKS ~ py : misc )------------------ .. _Guido van Rossum: https://en.wikipedia.org/wiki/Guido_van_Rossum .. _RealPython: https://realpython.com/python-type-checking .. # ------------------( LINKS ~ py : package )------------------ .. _Django: https://www.djangoproject.com .. _NetworkX: https://networkx.org .. _Pandas: https://pandas.pydata.org .. _PyTorch: https://pytorch.org .. _SymPy: https://www.sympy.org .. _numerary: https://github.com/posita/numerary .. _pyenv: https://operatingops.org/2020/10/24/tox-testing-multiple-python-versions-with-pyenv .. _typing_extensions: https://pypi.org/project/typing-extensions .. # ------------------( LINKS ~ py : package : boto3 )------------------ .. _Boto3: https://aws.amazon.com/sdk-for-python .. _bearboto3: https://github.com/beartype/bearboto3 .. _mypy-boto3: https://mypy-boto3.readthedocs.io .. # ------------------( LINKS ~ py : package : jax )------------------ .. _jax.numpy: https://jax.readthedocs.io/en/latest/notebooks/thinking_in_jax.html .. # ------------------( LINKS ~ py : package : numpy )------------------ .. _NumPy: https://numpy.org .. _numpy.dtype: https://numpy.org/doc/stable/reference/arrays.dtypes.html .. _numpy.empty_like: https://numpy.org/doc/stable/reference/generated/numpy.empty_like.html .. _numpy.floating: https://numpy.org/doc/stable/reference/arrays.scalars.html?highlight=numpy%20generic#numpy.floating .. _numpy.generic: https://numpy.org/doc/stable/reference/arrays.scalars.html?highlight=numpy%20generic#numpy.generic .. _numpy.integer: https://numpy.org/doc/stable/reference/arrays.scalars.html?highlight=numpy%20generic#numpy.integer .. _numpy.typing: https://numpy.org/devdocs/reference/typing.html .. _numpy.typing.NDArray: https://numpy.org/devdocs/reference/typing.html#ndarray .. # ------------------( LINKS ~ py : package : sphinx )------------------ .. _Sphinx: https://www.sphinx-doc.org .. _sphinx.ext.autodoc: https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html .. # ------------------( LINKS ~ py : package : test )------------------ .. _Codecov: https://about.codecov.io .. _pytest: https://docs.pytest.org .. _tox: https://tox.readthedocs.io .. # ------------------( LINKS ~ py : service )------------------ .. _Anaconda: https://docs.conda.io/en/latest/miniconda.html .. _PyPI: https://pypi.org .. # ------------------( LINKS ~ py : type : runtime )------------------ .. _enforce: https://github.com/RussBaz/enforce .. _enforce_typing: https://github.com/matchawine/python-enforce-typing .. _pydantic: https://pydantic-docs.helpmanual.io .. _pytypes: https://github.com/Stewori/pytypes .. _typeen: https://github.com/k2bd/typen .. _typical: https://github.com/seandstewart/typical .. # ------------------( LINKS ~ py : type : runtime : typeg )------------------ .. _typeguard: https://github.com/agronholm/typeguard .. _typeguard.check_type: https://typeguard.readthedocs.io/en/latest/userguide.html#checking-types-directly .. # ------------------( LINKS ~ py : type : runtime : data )------------------ .. _PyContracts: https://github.com/AlexandruBurlacu/pycontracts .. _contracts: https://pypi.org/project/contracts .. _covenant: https://github.com/kisielk/covenant .. _dpcontracts: https://pypi.org/project/dpcontracts .. _icontract: https://github.com/Parquery/icontract .. _pyadbc: https://pypi.org/project/pyadbc .. _pcd: https://pypi.org/project/pcd .. # ------------------( LINKS ~ py : type : static )------------------ .. _Pyre: https://pyre-check.org .. _pytype: https://github.com/google/pytype .. # ------------------( LINKS ~ py : type : static : pyright)------------------ .. _pyright: https://github.com/Microsoft/pyright .. _pyright plugins: https://github.com/microsoft/pyright/issues/607#issuecomment-873467941 .. _pyright PEP violation #1: https://github.com/beartype/beartype/issues/126 .. _pyright PEP violation #2: https://github.com/beartype/beartype/issues/127 .. # ------------------( LINKS ~ py : type : static : mypy )------------------ .. _mypy: http://mypy-lang.org .. _mypy install: https://mypy.readthedocs.io/en/stable/getting_started.html .. _mypy plugin: https://mypy.readthedocs.io/en/stable/extending_mypy.html .. _type narrowing: https://mypy.readthedocs.io/en/stable/type_narrowing.html .. # ------------------( LINKS ~ py : type : tensor )------------------ .. _jaxtyping: https://github.com/google/jaxtyping .. _nptyping: https://github.com/ramonhagenaars/nptyping .. _TorchTyping: https://github.com/patrick-kidger/torchtyping .. # ------------------( LINKS ~ soft : ide )------------------ .. _PyCharm: https://en.wikipedia.org/wiki/PyCharm .. _Vim: https://www.vim.org .. # ------------------( LINKS ~ soft : ide : vscode )------------------ .. _Pylance: https://github.com/microsoft/pylance-release .. _VSCode: https://code.visualstudio.com .. _VSCode Mypy extension: https://marketplace.visualstudio.com/items?itemName=matangover.mypy .. # ------------------( LINKS ~ soft : lang )------------------ .. _C: https://en.wikipedia.org/wiki/C_(programming_language) .. _C++: https://en.wikipedia.org/wiki/C%2B%2B .. _Ruby: https://www.ruby-lang.org .. _Rust: https://www.rust-lang.org .. # ------------------( LINKS ~ soft : license )------------------ .. _MIT license: https://opensource.org/licenses/MIT .. # ------------------( LINKS ~ soft : web )------------------ .. _React: https://reactjs.org beartype-0.18.5/beartype/000077500000000000000000000000001461113517100152335ustar00rootroot00000000000000beartype-0.18.5/beartype/__init__.py000066400000000000000000000221631461113517100173500ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype.** For :pep:`8` compliance, this namespace exposes a subset of the metadata constants published by the :mod:`beartype.meta` submodule. These metadata constants are commonly inspected (and thus expected) by external automation. ''' # ....................{ TODO }.................... #FIXME: Consider significantly expanding the above module docstring, assuming #Sphinx presents this module in its generated frontmatter. # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Explicitly list *ALL* public attributes imported below in the # "__all__" list global declared below to avoid linter complaints. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: To avoid race conditions during setuptools-based installation, this # module may import *ONLY* from modules guaranteed to exist at the start of # installation. This includes all standard Python and package submodules but # *NOT* third-party dependencies, which if currently uninstalled will only be # installed at some later time in the installation. Likewise, to avoid circular # import dependencies, the top-level of this module should avoid importing # package submodules where feasible. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: To avoid polluting the public module namespace, external attributes # should be locally imported at module scope *ONLY* under alternate private # names (e.g., "from argparse import ArgumentParser as _ArgumentParser" rather # than merely "from argparse import ArgumentParser"). #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ IMPORTS ~ meta }.................... # For PEP 8 compliance, versions constants expected by external automation are # imported under their PEP 8-mandated names. from beartype.meta import VERSION as __version__ from beartype.meta import VERSION_PARTS as __version_info__ # ....................{ IMPORTS ~ non-meta }.................... from sys import modules as _modules # If this submodule is being imported at install time from our top-level # "setup.py" script, avoid implicitly importing from *ANY* "beartype" submodule # other than the "beartype.meta" submodule. By sheer force of will, # "beartype.meta" is the *ONLY* "beartype" submodule guaranteed to be safely # importable at install time. All other "beartype" submodules should be assumed # to be unsafe due to potentially importing one or more optional runtime # dependencies yet to be installed (e.g., "typing_extensions"). # # See "setup.py" for gruesome details you do *NOT* want to know about. if 'beartype.__is_installing__' not in _modules: # Publicize the private @beartype._decor.beartype decorator as # @beartype.beartype, preserving all implementation details as private. from beartype._decor.decormain import ( beartype as beartype) # Publicize all top-level configuration attributes required to configure the # @beartype.beartype decorator. from beartype._conf.confcls import ( BeartypeConf as BeartypeConf) from beartype._conf.confenum import ( BeartypeStrategy as BeartypeStrategy, BeartypeViolationVerbosity as BeartypeViolationVerbosity, ) from beartype._conf.confoverrides import ( BeartypeHintOverrides as BeartypeHintOverrides) # Else, this submodule is *NOT* being imported at install time. # Delete the temporarily imported "sys.modules" global for ultimate safety. del _modules # ....................{ GLOBALS }.................... # Document all global variables imported into this namespace above. __version__ = __version__ ''' Human-readable package version as a ``.``-delimited string. For PEP 8 compliance, this specifier has the canonical name ``__version__`` rather than that of a typical global (e.g., ``VERSION_STR``). ''' __version_info__ = __version_info__ ''' Machine-readable package version as a tuple of integers. For PEP 8 compliance, this specifier has the canonical name ``__version_info__`` rather than that of a typical global (e.g., ``VERSION_PARTS``). ''' __all__ = [ 'BeartypeConf', 'BeartypeHintOverrides', 'BeartypeStrategy', 'BeartypeViolationVerbosity', 'beartype', '__version__', '__version_info__', ] ''' Special list global of the unqualified names of all public package attributes explicitly exported by and thus safely importable from this package. Caveats ------- **This global is defined only for conformance with static type checkers,** a necessary prerequisite for :pep:`561`-compliance. This global is *not* intended to enable star imports of the form ``from beartype import *`` (now largely considered a harmful anti-pattern by the Python community), although it technically does the latter as well. This global would ideally instead reference *only* a single package attribute guaranteed *not* to exist (e.g., ``'STAR_IMPORTS_CONSIDERED_HARMFUL'``), effectively disabling star imports. Since doing so induces spurious static type-checking failures, we reluctantly embrace the standard approach. For example, :mod:`mypy` emits an error resembling: error: Module 'beartype' does not explicitly export attribute 'beartype'; implicit reexport disabled. ''' # ....................{ DEPRECATIONS }.................... def __getattr__(attr_deprecated_name: str) -> object: ''' Dynamically retrieve a deprecated attribute with the passed unqualified name from this submodule and emit a non-fatal deprecation warning on each such retrieval if this submodule defines this attribute *or* raise an exception otherwise. The Python interpreter implicitly calls this :pep:`562`-compliant module dunder function under Python >= 3.7 *after* failing to directly retrieve an explicit attribute with this name from this submodule. Since this dunder function is only called in the event of an error, neither space nor time efficiency are a concern here. Parameters ---------- attr_deprecated_name : str Unqualified name of the deprecated attribute to be retrieved. Returns ---------- object Value of this deprecated attribute. Warns ---------- DeprecationWarning If this attribute is deprecated. Raises ---------- AttributeError If this attribute is unrecognized and thus erroneous. ''' # Isolate imports to avoid polluting the module namespace. from beartype._util.module.utilmoddeprecate import deprecate_module_attr # Package scope (i.e., dictionary mapping from the names to values of all # non-deprecated attributes defined by this package). attr_nondeprecated_name_to_value = globals() # If this deprecated attribute is the deprecated "beartype.abby" submodule, # forcibly import the non-deprecated "beartype.door" submodule aliased to # "beartype.abby" into this package scope. For efficiency, this package does # *NOT* unconditionally import and expose the "beartype.door" submodule # above. That submodule does *NOT* exist in the globals() dictionary # defaulted to above and *MUST* now be forcibly injected there. if attr_deprecated_name == 'abby': from beartype import door attr_nondeprecated_name_to_value = {'door': door} attr_nondeprecated_name_to_value.update(globals()) #FIXME: To support attribute-based deferred importation ala "lazy loading" #of heavyweight subpackages like "beartype.door" and "beartype.vale", it #looks like we'll need to manually add support here for that: e.g., # elif attr_deprecated_name in {'cave', 'claw', 'door', 'vale',}: # #FIXME: Dynamically import this attribute here... somehow. Certainly, if # #such functionality does *NOT* exist, add it to the existing # #"utilmodimport" submodule: e.g., # attr_value = import_module_attr(f'beartype.{attr_deprecated_name}') # attr_nondeprecated_name_to_value = {attr_deprecated_name: attr_value} #FIXME: Rename "attr_deprecated_name" to merely "attr_name", please. #FIXME: Revise docstring accordingly, please. #FIXME: Exhaustively test this, please. Because we'll never manage to keep #this in sync, we *ABSOLUTELY* should author a unit test that: #* Decides the set of all public subpackages of "beartype". #* Validates that each subpackage in this set is accessible as a # "beartype.{subpackage_name}" attribute. # Else, this deprecated attribute is any other attribute. # Return the value of this deprecated attribute and emit a warning. return deprecate_module_attr( attr_deprecated_name=attr_deprecated_name, attr_deprecated_name_to_nondeprecated_name={ 'abby': 'door', }, attr_nondeprecated_name_to_value=attr_nondeprecated_name_to_value, ) beartype-0.18.5/beartype/_cave/000077500000000000000000000000001461113517100163105ustar00rootroot00000000000000beartype-0.18.5/beartype/_cave/__init__.py000066400000000000000000000000001461113517100204070ustar00rootroot00000000000000beartype-0.18.5/beartype/_cave/_caveabc.py000066400000000000000000000141401461113517100204050ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' :mod:`beartype.cave`-specific **abstract base classes (ABCs).** ''' # ....................{ TODO }.................... #FIXME: Refactor this private submodule into a new public "beartype.caver" #submodule, so-named as it enables users to externally create new ad-hoc #protocols implementing structural subtyping resembling those predefined by #"beartype.cave". To do so: # #* In the "beartype.caver" submodule: # * Define a new make_type_structural() function with signature resembling: # def make_type_structural(name: str, method_names: Iterable) -> type: # * Implement this function to dynamically create a new type with the passed # classname defining: # * Abstract methods with the passed method names. # * A __subclasshook__() dunder method checking the passed class for # concrete methods with these names. # To do so, note that abstract methods *CANNOT* be dynamically # monkey-patched in after class creation but *MUST* instead be statically # defined at class creation time (due to metaclass shenanigans). # Fortunately, doing so is trivial; simply use the three-argument form of # the type() constructor, as demonstrated by this StackOverflow answer: # https://stackoverflow.com/a/14219244/2809027 # * *WAIT!* There's no need to call the type() constructor directly. Instead, # define a new make_type() function in this new submodule copied from the # betse.util.type.classes.define_class() function (but renamed, obviously). #* Replace the current manual definition of "_BoolType" below with an in-place # call to that method from the "beartype.cave" submodule: e.g., # BoolType = _make_type_structural( # name='BoolType', method_names=('__bool__',)) # #Dis goin' be good. #FIXME: Actually, don't do any of the above. That would simply be reinventing #the wheel, as the "typing.Protocol" superclass already exists and is more than #up to the task. In fact, once we drop support for Python < 3.7, we should: #* Redefine the "_BoolType" class declared below should in terms of the # "typing.Protocol" superclass. #* Shift the "_BoolType" class directly into the "beartype.cave" submodule. #* Refactor away this entire submodule. # ....................{ IMPORTS }.................... from abc import ABCMeta, abstractmethod # ....................{ FUNCTIONS }.................... def _check_methods(C: type, *methods: str): ''' Private utility function called by abstract base classes (ABCs) implementing structural subtyping by detecting whether the passed class or some superclass of that class defines all of the methods with the passed method names. For safety, this function has been duplicated as is from its eponymous counterpart in the private stdlib :mod:`_colletions_abc` module. Parameters ---------- C : type Class to be validated as defining these methods. methods : Tuple[str, ...] Tuple of the names of all methods to validate this class as defining. Returns ---------- Either: * ``True`` if this class defines all of these methods. * ``NotImplemented`` if this class fails to define one or more of these methods. ''' mro = C.__mro__ for method in methods: for B in mro: # pyright: ignore[reportGeneralTypeIssues] if method in B.__dict__: if B.__dict__[method] is None: return NotImplemented break else: return NotImplemented return True # ....................{ SUPERCLASSES }.................... class BoolType(object, metaclass=ABCMeta): ''' Type of all **booleans** (i.e., objects defining the ``__bool__()`` dunder method; objects reducible in boolean contexts like ``if`` conditionals to either ``True`` or ``False``). This type matches: * **Builtin booleans** (i.e., instances of the standard :class:`bool` class implemented in low-level C). * **NumPy booleans** (i.e., instances of the :class:`numpy.bool_` class implemented in low-level C and Fortran) if :mod:`numpy` is importable. Usage ---------- Non-standard boolean types like NumPy booleans are typically *not* interoperable with the standard standard :class:`bool` type. In particular, it is typically *not* the case, for any variable ``my_bool`` of non-standard boolean type and truthy value, that either ``my_bool is True`` or ``my_bool == True`` yield the desired results. Rather, such variables should *always* be coerced into the standard :class:`bool` type before being compared -- either: * Implicitly (e.g., ``if my_bool: pass``). * Explicitly (e.g., ``if bool(my_bool): pass``). Caveats ---------- **There exists no abstract base class governing booleans in Python.** Although various Python Enhancement Proposals (PEPs) were authored on the subject, all were rejected as of this writing. Instead, this type trivially implements an ad-hoc abstract base class (ABC) detecting objects satisfying the boolean protocol via structural subtyping. Although no actual real-world classes subclass this :mod:`beartype`-specific ABC, the detection implemented by this ABC suffices to match *all* boolean types. See Also ---------- :class:`beartype.cave.ContainerType` Further details on structural subtyping. ''' # ..................{ DUNDERS }.................. # This abstract base class (ABC) has been implemented ala standard # container ABCs in the private stdlib "_collections_abc" module (e.g., the # trivial "_collections_abc.Sized" type). __slots__ = () @abstractmethod def __bool__(self): return False @classmethod def __subclasshook__(cls, C): if cls is BoolType: return _check_methods(C, '__bool__') return NotImplemented beartype-0.18.5/beartype/_cave/_cavefast.py000066400000000000000000001670341461113517100206300ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype fast cave** (i.e., private subset of the public :mod:`beartype.cave` subpackage profiled to be efficiently importable at :mod:`beartype` startup and thus safely importable throughout the internal :mod:`beartype` codebase). The public :mod:`beartype.cave` subpackage has been profiled to *not* be efficiently importable at :mod:`beartype` startup and thus *not* safely importable throughout the internal :mod:`beartype` codebase. Why? Because :mod:`beartype.cave` currently imports from expensive third-party packages on importation (e.g., :mod:`numpy`) despite :mod:`beartype` itself *never* requiring those imports. Until resolved, that subpackage is considered tainted. ''' # ....................{ TODO }.................... #FIXME: Add types for all remaining useful "collections.abc" interfaces, #including: #* "Reversible". #* "AsyncIterable". #* "AsyncIterator". #* "AsyncGenerator". # #There certainly exist other "collections.abc" interfaces as well, but it's #unclear whether they have any practical real-world utility during type #checking. These include: #* "ByteString". (wut) #* Dictionary-specific views (e.g., "MappingView", "ItemsView"). # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To avoid polluting the public module namespace, external attributes # should be locally imported at module scope *ONLY* under alternate private # names (e.g., "from argparse import ArgumentParser as _ArgumentParser" rather # than merely "from argparse import ArgumentParser"). #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! import functools as _functools import numbers as _numbers import re as _re import types as _types import typing as _typing from beartype.roar import BeartypeCallUnavailableTypeException from beartype._cave._caveabc import BoolType from beartype._util.py.utilpyversion import ( IS_PYTHON_AT_LEAST_3_12, IS_PYTHON_AT_LEAST_3_10, IS_PYTHON_AT_LEAST_3_9, ) from collections import deque as _deque from collections.abc import ( Collection as _Collection, Container as _Container, Generator as _Generator, Hashable as _Hashable, Iterable as _Iterable, Iterator as _Iterator, Mapping as _Mapping, MutableMapping as _MutableMapping, Sequence as _Sequence, MutableSequence as _MutableSequence, Set as _Set, Sized as _Sized, ) from enum import ( Enum as _Enum, EnumMeta as _EnumMeta, ) from io import IOBase as _IOBase from typing import ( TYPE_CHECKING, Any, Tuple as _TupleTyping, ) # Note that: # # * "BuiltinMethodType" is intentionally *NOT* imported, as that type is # exactly synonymous with "BuiltinFunctionType", implying C-based methods are # indistinguishable from C-based functions. To prevent C-based functions from # being misidentified as C-based methods, all C-based functions and methods # are ambiguously identified as C-based callables. # * "LambdaType" is intentionally *NOT* imported, as that type is exactly # synonymous with "FunctionType", implying lambdas are indistinguishable from # pure-Python functions. To prevent pure-Python functions from being # misidentified as lambdas, all lambdas are currently misidentified as # pure-Python functions. # # These are the lesser of multiple evils. from types import ( AsyncGeneratorType as _AsyncGeneratorType, BuiltinFunctionType as _BuiltinFunctionType, CellType as _CellType, CoroutineType as _CoroutineType, FrameType as _FrameType, FunctionType as _FunctionType, GeneratorType as _GeneratorType, GetSetDescriptorType as _GetSetDescriptorType, MemberDescriptorType as _MemberDescriptorType, MethodType as _MethodType, ModuleType as _ModuleType, TracebackType as _TracebackType, ) # ....................{ IMPORTS ~ conditional }.................... #FIXME: Preserve for when we inevitably require similar logic in the future. # # Attempt to import types unavailable under Python 3.5, all of which should # # be passed through the intermediary _get_type_or_unavailable() helper # # function first before being assigned to module globals below. The # # docstrings for such globals should contain a sentence resembling: # # **This type is unavailable under Python 3.5,** where it defaults to # # :class:`UnavailableType` for safety. # try: # _Collection = type(list[str]) # # If this is Python 3.5, define placeholder globals of the same name. # except ImportError: # _Collection = None # ....................{ CLASSES }.................... class UnavailableType(object): ''' **Unavailable type** (i.e., type *not* available under the active Python interpreter, typically due to insufficient Python version or non-installed third-party dependencies). ''' def __instancecheck__(self, obj) -> None: raise BeartypeCallUnavailableTypeException( f'{self} not passable as the second parameter to isinstance().') def __subclasscheck__(self, cls) -> None: raise BeartypeCallUnavailableTypeException( f'{self} not passable as the second parameter to issubclass().') # This is private, as it's unclear whether anyone requires access to this yet. class _UnavailableTypesTuple(tuple): ''' Type of any **tuple of unavailable types** (i.e., types *not* available under the active Python interpreter, typically due to insufficient Python version or non-installed third-party dependencies). ''' pass # ....................{ TYPES ~ core }.................... AnyType = object ''' Type of all objects regardless of type. ''' ClassType = type ''' Type of all types. ''' FileType = _IOBase ''' Abstract base class of all **file-like objects** (i.e., objects implementing the standard ``read()``, ``write()``, and ``close()`` methods). ''' ModuleType = _ModuleType ''' Type of all **C- and Python-based modules** (i.e., importable files implemented either as C extensions or in pure Python). ''' # ....................{ TYPES ~ core : singleton }.................... EllipsisType: type = type(Ellipsis) ''' Type of the :data:`Ellipsis` singleton. ''' NoneType: type = type(None) ''' Type of the :data:`None` singleton. Curiously, although the type of the :data:`None` object is a class object whose ``__name__`` attribute is ``NoneType``, there exists no globally accessible class by that name. To circumvents this obvious oversight, this global globally exposes this class. This class is principally useful for annotating both: * Callable parameters accepting :data:`None` as a valid value. * Callables returning :data:`None` as a valid value. Note that, for obscure and uninteresting reasons, the standard :mod:`types` module defined the same type with the same name under Python 2.x but *not* 3.x. Depressingly, this type must now be manually redefined everywhere. ''' # Define this type as either... NotImplementedType: type = ( # If the active Python interpreter targets at least Python >= 3.10 and thus # exposes this type in the standard "types" module, this type; _types.NotImplementedType # type: ignore[assignment,attr-defined] if IS_PYTHON_AT_LEAST_3_10 else # Else, this type manually introspected from this builtin singleton. type(NotImplemented) # type: ignore[misc] ) ''' Type of the :data:`NotImplemented` singleton. ''' # ....................{ TYPES ~ call }.................... CallableCodeObjectType: Any = type((lambda: None).__code__) ''' Type of all **code objects** (i.e., C-based objects underlying all pure-Python callables to which those callables are compiled for efficiency). ''' # Alias this type to this standard type. # # Note that this is explicitly required for "nuitka" support, which supports # this standard type but *NOT* the non-standard approach used to deduce this # type under Python 3.7 leveraged below. ClosureVarCellType = _CellType ''' Type of all **pure-Python closure cell variables.** ''' # ....................{ TYPES ~ call : exception }.................... ExceptionTracebackType = _TracebackType ''' Type of all **traceback objects** (i.e., C-based objects comprising the full stack traces associated with raised exceptions). ''' CallableFrameType = _FrameType ''' Type of all **call stack frame objects** (i.e., C-based objects encapsulating each call to each callable on the current call stack). ''' # ....................{ TYPES ~ call : function }.................... FunctionType = _FunctionType ''' Type of all **pure-Python functions** (i.e., functions implemented in Python *not* associated with an owning class or instance of a class). Caveats ------- **This type ambiguously matches many callables not commonly associated with standard functions,** including: * **Lambda functions.** Of course, distinguishing between conventional named functions and unnamed lambda functions would usually be seen as overly specific. So, this ambiguity is *not* necessarily a bad thing. * **Unbound instance methods** (i.e., instance methods accessed on their declaring classes rather than bound instances). * **Static methods** (i.e., methods decorated with the builtin :func:`staticmethod` decorator, regardless of whether those methods are accessed on their declaring classes or associated instances). **This type matches no callables whatsoever under some non-CPython interpreters,** including: * PyPy, which unconditionally compiles *all* pure-Python functions into C-based functions. Ergo, under PyPy, *all* functions are guaranteed to be of the type :class:`FunctionOrMethodCType` regardless of whether those functions were initially defined in Python or C. See Also -------- :class:`.MethodBoundInstanceOrClassType` Type of all pure-Python bound instance and class methods. ''' FunctionOrMethodCType = _BuiltinFunctionType ''' Type of all **C-based callables** (i.e., functions and methods implemented with low-level C rather than high-level Python, typically either in third-party C extensions, official stdlib C extensions, or the active Python interpreter itself). ''' # ....................{ TYPES ~ call : method : bound }.................... MethodBoundInstanceOrClassType = _MethodType ''' Type of all **pure-Python bound instance and class methods** (i.e., methods implemented in pure Python, bound to either instances of classes or classes *and* implicitly passed those instances or classes as their first parameters). Caveats ------- There exists *no* corresponding :class:`MethodUnboundInstanceType` type, as unbound pure-Python instance methods are ambiguously implemented as functions of type :class:`.FunctionType` indistinguishable from conventional functions. Indeed, `official documentation `__ for the ``PyInstanceMethod_Type`` C type explicitly admits that: This instance of PyTypeObject represents the Python instance method type. It is not exposed to Python programs. .. _PyInstanceMethod_Type documentation: https://docs.python.org/3/c-api/method.html#c.PyInstanceMethod_Type ''' #FIXME: Directly alias this to "_types.MethodWrapperType" now, please. # Although Python >= 3.7 now exposes an explicit method wrapper type via the # standard "types.MethodWrapperType" object, this is of no benefit to older # versions of Python. Ergo, the type of an arbitrary method wrapper guaranteed # to *ALWAYS* exist is obtained instead. MethodBoundInstanceDunderCType: Any = type(''.__add__) ''' Type of all **C-based bound method wrappers** (i.e., callable objects implemented in low-level C, associated with special methods of builtin types when accessed as instance rather than class attributes). See Also -------- :class:`MethodUnboundInstanceDunderCType` Type of all C-based unbound dunder method wrapper descriptors. ''' # ....................{ TYPES ~ call : method : unbound }.................... # Although Python >= 3.7 now exposes an explicit method wrapper type via the # standard "types.ClassMethodDescriptorType" object, this is of no benefit to # older versions of Python. Ergo, the type of an arbitrary method descriptor # guaranteed to *ALWAYS* exist is obtained instead. MethodUnboundClassCType: Any = type(dict.__dict__['fromkeys']) ''' Type of all **C-based unbound class method descriptors** (i.e., callable objects implemented in low-level C, associated with class methods of builtin types when accessed with the low-level :attr:`object.__dict__` dictionary rather than as class or instance attributes). Despite being unbound, class method descriptors remain callable (e.g., by explicitly passing the intended ``cls`` objects as their first parameters). ''' # Although Python >= 3.7 now exposes an explicit method wrapper type via the # standard "types.WrapperDescriptorType" object, this is of no benefit to older # versions of Python. Ergo, the type of an arbitrary method descriptor # guaranteed to *ALWAYS* exist is obtained instead. MethodUnboundInstanceDunderCType: Any = type(str.__add__) ''' Type of all **C-based unbound dunder method wrapper descriptors** (i.e., callable objects implemented in low-level C, associated with dunder methods of builtin types when accessed as class rather than instance attributes). Despite being unbound, method descriptor wrappers remain callable (e.g., by explicitly passing the intended ``self`` objects as their first parameters). See Also -------- :class:`MethodBoundInstanceDunderCType` Type of all C-based unbound dunder method wrappers. :class:`MethodUnboundInstanceNondunderCType` Type of all C-based unbound non-dunder method descriptors. ''' # Although Python >= 3.7 now exposes an explicit method wrapper type via the # standard "types.MethodDescriptorType" object, this is of no benefit to older # versions of Python. Ergo, the type of an arbitrary method descriptor # guaranteed to *ALWAYS* exist is obtained instead. MethodUnboundInstanceNondunderCType: Any = type(str.upper) ''' Type of all **C-based unbound non-dunder method descriptors** (i.e., callable objects implemented in low-level C, associated with non-dunder methods of builtin types when accessed as class rather than instance attributes). Despite being unbound, method descriptors remain callable (e.g., by explicitly passing the intended ``self`` objects as their first parameters). See Also -------- :class:`MethodUnboundInstanceDunderCType` Type of all C-based unbound dunder method wrapper descriptors. ''' MethodUnboundPropertyNontrivialCExtensionType = _GetSetDescriptorType ''' Type of all **C extension-specific unbound non-trivial property method descriptors** (i.e., uncallable objects implemented in low-level C extensions, associated with **non-trivial property methods** (i.e., wrapping underlying attributes that are *not* trivially convertible to C types) of C extensions when accessed with the low-level :attr:`object.__dict__` dictionary rather than as class or instance attributes). ''' MethodUnboundPropertyTrivialCExtensionType = _MemberDescriptorType ''' Type of all **C extension-specific unbound trivial property method descriptors** (i.e., uncallable objects implemented in low-level C extensions, associated with **trivial property methods** (i.e., wrapping underlying attributes that are trivially convertible to C types) of C extensions when accessed with the low-level :attr:`object.__dict__` dictionary rather than as class or instance attributes). ''' # ....................{ TYPES ~ call : method : decorator }.................... MethodDecoratorClassType = classmethod ''' Type of all **C-based unbound class method descriptors** (i.e., non-callable instances of the builtin :class:`classmethod` decorator class implemented in low-level C, associated with class methods implemented in pure Python, and accessed with the low-level :attr:`object.__dict__` dictionary rather than as class or instance attributes). Caveats ------- Class method objects are *only* directly accessible via the low-level :attr:`object.__dict__` dictionary. When accessed as class or instance attributes, class methods reduce to instances of the standard :class:`MethodBoundInstanceOrClassType` type. Class method objects are *not* callable, as their implementations fail to define the ``__call__`` dunder method. ''' MethodDecoratorPropertyType = property ''' Type of all **C-based unbound property method descriptors** (i.e., non-callable instances of the builtin :class:`property` decorator class implemented in low-level C, associated with property getter and setter methods implemented in pure Python, and accessed as class rather than instance attributes). Caveats ------- Property objects are directly accessible both as class attributes *and* via the low-level :attr:`object.__dict__` dictionary. Property objects are *not* accessible as instance attributes, for hopefully obvious reasons. Property objects are *not* callable, as their implementations fail to define the ``__call__`` dunder method. ''' MethodDecoratorStaticType = staticmethod ''' Type of all **C-based unbound static method descriptors** (i.e., non-callable instances of the builtin :class:`classmethod` decorator class implemented in low-level C, associated with static methods implemented in pure Python, and accessed with the low-level :attr:`object.__dict__` dictionary rather than as class or instance attributes). Caveats ------- Static method objects are *only* directly accessible via the low-level :attr:`object.__dict__` dictionary. When accessed as class or instance attributes, static methods reduce to instances of the standard :class:`FunctionType` type. Static method objects are *not* callable, as their implementations fail to define the ``__call__`` dunder method. ''' # ....................{ TYPES ~ call : return : async }.................... AsyncGeneratorCType = _AsyncGeneratorType ''' C-based type returned by all **asynchronous pure-Python generators** (i.e., callables implemented in pure Python containing one or more ``yield`` statements whose declaration is preceded by the ``async`` keyword). Caveats ------- **This is not the type of asynchronous generator callables** but rather the type implicitly created and *returned* by these callables. Since these callables are simply callables subject to syntactic sugar, the type of these callables is simply :data:`CallableTypes`. ''' AsyncCoroutineCType = _CoroutineType ''' C-based type returned by all **asynchronous coroutines** (i.e., callables implemented in pure Python *not* containing one or more ``yield`` statements whose declaration is preceded by the ``async`` keyword). Caveats ------- **This is not the type of asynchronous coroutine callables** but rather the type implicitly created and *returned* by these callables. Since these callables are simply callables subject to syntactic sugar, the type of these callables is simply :data:`CallableTypes`. ''' # ....................{ TYPES ~ call : return : generator }.................... GeneratorType = _Generator ''' Type of all **C- and Python-based generator objects** (i.e., iterators implementing the :class:`collections.abc.Generator` protocol), including: * Pure-Python subclasses of the :class:`collections.abc.Generator` superclass. * C-based generators returned by pure-Python callables containing one or more ``yield`` statements. * C-based generator comprehensions created by pure-Python syntax delimited by ``(`` and ``)``. Caveats ------- **This is not the type of generator callables** but rather the type implicitly created and *returned* by these callables. Since these callables are simply callables subject to syntactic sugar, the type of these callables is simply :data:`CallableTypes`. See Also -------- :class:`GeneratorCType` Subtype of all C-based generators. ''' GeneratorCType = _GeneratorType ''' C-based type returned by all **pure-Python generators** (i.e., callables implemented in pure Python containing one or more ``yield`` statements, implicitly converted at runtime to return a C-based iterator of this type) as well as the C-based type of all **pure-Python generator comprehensions** (i.e., ``(``- and ``)``-delimited syntactic sugar implemented in pure Python, also implicitly converted at runtime to return a C-based iterator of this type). Caveats ------- **This is not the type of generator callables** but rather the type implicitly created and *returned* by these callables. Since these callables are simply callables subject to syntactic sugar, the type of these callables is simply :data:`CallableTypes`. This special-purpose type is a subtype of the more general-purpose :class:`GeneratorType`. Whereas the latter applies to *all* generators implementing the :class:`collections.abc.Iterator` protocol, the former only applies to generators implicitly created by Python itself. ''' # ....................{ TYPES ~ call : module : functools }.................... CallableFunctoolsPartialType = _functools.partial ''' Pure-Python type of all **partial callables** (i.e., possibly C-based callable wrapped by the pure-Python callable :class:`functools.partial` type). Caveats ------- This type does *not* distinguish between whether the original callable wrapped by :class:`functools.partial` is C-based or pure Python -- only that some callable of indeterminate origin is in fact wrapped. ''' @_functools.lru_cache def _lru_cache_func(n: int) -> int: ''' Arbitrary :func:`functools.lru_cache`-memoized function defined solely to inspect various dunder attributes common to all such functions. ''' return n + 1 # If this submodule is currently being statically type-checked by a pure static # type-checker, ignore false positives complaining that this type is not a type. if TYPE_CHECKING: class CallableFunctoolsLruCacheType(object): pass # Else, this submodule is *NOT* currently being statically type-checked by a # pure static type-checker. In this case, define this type properly. *sigh* else: CallableFunctoolsLruCacheType = type(_lru_cache_func) ''' C-based type of all low-level private objects created and returned by the :func:`functools.lru_cache` decorator (e.g., :class:`functools._lru_cache_wrapper`). This type enables functionality elsewhere to reliably detect when a callable has been decorated by that decorator. ''' # print(f'LRU_CACHE_TYPE: {LRU_CACHE_TYPE}') # Delete temporary private callables defined above as a negligible safety (and # possible space complexity) measure. del _lru_cache_func # ....................{ TYPES ~ class }.................... # If this submodule is currently being statically type-checked by a pure static # type-checker, ignore false positives complaining that this type is not a type. if TYPE_CHECKING: class ClassDictType(object): pass # Else, this submodule is *NOT* currently being statically type-checked by a # pure static type-checker. In this case, define this type properly. *sigh* else: ClassDictType = type(type.__dict__) ''' Type of all **pure-Python class dictionaries** (i.e., immutable mappings officially referred to as "mapping proxies," whose keys are strictly constrained for both efficiency and correctness to be Python identifier strings). ''' # ....................{ TYPES ~ data }.................... ContainerType = _Container ''' Type of all **containers** (i.e., concrete instances of the abstract :class:`collections.abc.Container` base class as well as arbitrary objects whose classes implement all abstract methods declared by that base class regardless of whether those classes actually subclass that base class). Caveats ------- This type ambiguously matches both: * **Explicit container subtypes** (i.e., concrete subclasses of the :class:`collections.abc.Container` abstract base class (ABC)). * **Structural container subtypes** (i.e., arbitrary classes implementing the abstract ``__contains__`` method declared by that ABC *without* subclassing that ABC), as formalized by :pep:`544`. Notably, since the **NumPy array type** (i.e., :class:`numpy.ndarray`) defines that method, this type magically matches the NumPy array type as well. Of course, distinguishing between explicit and structural subtypes would usually be seen as overly specific. So, this ambiguity is *not* necessarily a BadThing™. What is a BadThing™ is that container ABCs violate the "explicit is better than implicit" maxim of :pep:`20` by intentionally deceiving you for your own benefit, which you of course appreciate. Thanks to arcane dunder magics buried in the :class:`abc.ABCMeta` metaclass, the :func:`isinstance` and :func:`issubclass` builtin functions (which the :func:`beartype.beartype` decorator internally defers to) ambiguously mistype structural container subtypes as explicit container subtypes: .. code-block:: pycon >>> from collections.abc import Container >>> class FakeContainer(object): ... def __contains__(self, obj): return True >>> FakeContainer.__mro__ ... (FakeContainer, object) >>> issubclass(FakeContainer, Container) True >>> isinstance(FakeContainer(), Container) True ''' IterableType = _Iterable ''' Type of all **iterables** (i.e., both concrete and structural instances of the abstract :class:`collections.abc.Iterable` base class). Iterables are containers that may be indirectly iterated over by calling the :func:`iter` builtin, which internally calls the ``__iter__()`` dunder methods implemented by these containers, which return **iterators** (i.e., instances of the :class:`IteratorType` type), which directly support iteration. This type also matches **NumPy arrays** (i.e., instances of the concrete :class:`numpy.ndarray` class) via structural subtyping. See Also -------- :class:`ContainerType` Further details on structural subtyping. :class:`IteratorType` Further details on iteration. ''' IteratorType = _Iterator ''' Type of all **iterators** (i.e., both concrete and structural instances of the abstract :class:`collections.abc.Iterator` base class; objects iterating over associated data streams, which are typically containers). Iterators implement at least two dunder methods: * ``__next__()``, iteratively returning successive items from associated data streams (e.g., container objects) until throwing standard :data:`StopIteration` exceptions on reaching the ends of those streams. * ``__iter__()``, returning themselves. Since iterables (i.e., instances of the :class:`IterableType` type) are *only* required to implement the ``__iter__()`` dunder method, all iterators are by definition iterables as well. See Also -------- :class:`ContainerType` Further details on structural subtyping. :class:`IterableType` Further details on iteration. ''' SizedType = _Sized ''' Type of all **sized containers** (i.e., both concrete and structural instances of the abstract :class:`collections.abc.Sized` base class; containers defining the ``__len__()`` dunder method internally called by the :func:`len` builtin). This type also matches **NumPy arrays** (i.e., instances of the concrete :class:`numpy.ndarray` class) via structural subtyping. See Also -------- :class:`ContainerType` Further details on structural subtyping. ''' CollectionType = _Collection ''' Type of all **collections** (i.e., both concrete and structural instances of the abstract :class:`collections.abc.Collection` base class; sized iterable containers defining the ``__contains__()``, ``__iter__()``, and ``__len__()`` dunder methods). This type also matches **NumPy arrays** (i.e., instances of the concrete :class:`numpy.ndarray` class) via structural subtyping. See Also -------- :class:`ContainerType` Further details on structural subtyping. ''' QueueType = _deque ''' Type of all **double-ended queues** (i.e., instances of the concrete :class:`collections.deque` class, the only queue type defined by the Python stdlib). Caveats ------- The :mod:`collections.abc` subpackage currently provides no corresponding abstract interface to formalize queue types. Double-ended queues are it, sadly. ''' SetType = _Set ''' Type of all **set-like containers** (i.e., both concrete and structural instances of the abstract :class:`collections.abc.Set` base class; containers guaranteeing uniqueness across all contained items). This type matches both the standard :class:`set` and :class:`frozenset` types *and* the types of the :class:`dict`-specific views returned by the :meth:`dict.items` and :meth:`dict.keys` (but *not* :meth:`dict.values`) methods. See Also -------- :class:`ContainerType` Further details on structural subtyping. ''' # ....................{ TYPES ~ data : mapping }.................... HashableType = _Hashable ''' Type of all **hashable objects** (i.e., both concrete and structural instances of the abstract :class:`collections.abc.Hashable` base class; objects implementing the ``__hash__()`` dunder method required for all dictionary keys and set items). See Also -------- :class:`ContainerType` Further details on structural subtyping. ''' MappingType = _Mapping ''' Type of all **mutable** and **immutable mappings** (i.e., both concrete and structural instances of the abstract :class:`collections.abc.Mapping` base class; dictionary-like containers containing key-value pairs mapping from hashable keys to corresponding values). Caveats ------- **This type does not guarantee mutability** (i.e., the capacity to modify instances of this type after instantiation). This type ambiguously matches both mutable mapping types (e.g., :class:`dict`) and immutable mapping types (e.g., :class:`ClassDictType`). Where mutability is required, prefer the non-ambiguous :class:`MappingMutableType` type instead. See Also -------- :class:`ContainerType` Further details on structural subtyping. ''' MappingMutableType = _MutableMapping ''' Type of all **mutable mappings** (i.e., both concrete and structural instances of the abstract :class:`collections.abc.MutableMapping` base class; dictionary-like containers permitting modification of contained key-value pairs). See Also -------- :class:`ContainerType` Further details on structural subtyping. :class:`MappingType` Type of all mutable and immutable mappings. ''' # ....................{ TYPES ~ data : sequence }.................... SequenceType = _Sequence ''' Type of all **mutable** and **immutable sequences** (i.e., both concrete and structural instances of the abstract :class:`collections.abc.Sequence` base class; reversible collections whose items are efficiently accessible but *not* necessarily modifiable with 0-based integer-indexed lookup). Caveats ------- **This type does not guarantee mutability** (i.e., the capacity to modify instances of this type after instantiation). This type ambiguously matches both mutable sequence types (e.g., :class:`list`) and immutable sequence types (e.g., :class:`tuple`). Where mutability is required, prefer the non-ambiguous :class:`SequenceMutableType` type instead. **This type matches the string type (i.e., :class:`str`),** which satisfies the :class:`collections.abc.Sequence` API but *not* the :class:`collections.abc.MutableSequence` API. Where **non-string sequences** (i.e., sequences that are anything but strings) are required, prefer the non-ambiguous :class:`SequenceMutableType` type instead. **This type does not match NumPy arrays (i.e., instances of the concrete :class:`numpy.ndarray` class),** which satisfy most but *not* all of the :class:`collections.abc.Sequence` API. Specifically, NumPy arrays fail to define: * The ``__reversible__`` dunder method. * The ``count`` public method. * The ``index`` public method. Most callables accepting sequences *never* invoke these edge-case methods and should thus be typed to accept NumPy arrays as well. To do so, prefer either: * The :class:`beartype.cave.SequenceOrNumpyArrayTypes` tuple of types matching both sequences and NumPy arrays. * The :class:`beartype.cave.SequenceMutableOrNumpyArrayTypes` tuple of types matching both mutable sequences and NumPy arrays. See Also -------- :class:`ContainerType` Further details on structural subtyping. ''' SequenceMutableType = _MutableSequence ''' Type of all **mutable sequences** (i.e., both concrete and structural instances of the abstract :class:`collections.abc.Sequence` base class; reversible collections whose items are both efficiently accessible *and* modifiable with 0-based integer-indexed lookup). Caveats ------- **This type does not match NumPy arrays (i.e., instances of the concrete :class:`numpy.ndarray` class),** which satisfy most but *not* all of the :class:`collections.abc.MutableSequence` API. Specifically, NumPy arrays fail to define: * The ``__reversible__`` dunder method. * The ``append`` public method. * The ``count`` public method. * The ``extend`` public method. * The ``index`` public method. * The ``insert`` public method. * The ``pop`` public method. * The ``remove`` public method. * The ``reverse`` public method. Most callables accepting mutable sequences *never* invoke these edge-case methods and should thus be typed to accept NumPy arrays as well. To do so, prefer the :class:`beartype.cave.SequenceMutableOrNumpyArrayTypes` tuple of types matching both mutable sequences and NumPy arrays. See Also -------- :class:`ContainerType` Further details on structural subtyping. :class:`SequenceType` Further details on sequences. ''' # ....................{ TYPES ~ enum }.................... # Enumeration types are sufficiently obscure to warrant formalization here. EnumType = _EnumMeta ''' Type of all **enumeration types** (i.e., metaclass of all classes containing all enumeration members comprising those enumerations). Motivation ---------- This type is commonly used to validate callable parameters as enumerations. In recognition of its popularity, this type is intentionally named ``EnumType`` rather than ``EnumMetaType``. While the latter *would* technically be less ambiguous, the former has the advantage of inviting correctness throughout downstream codebases -- a less abundant resource. Why? Because *all* enumeration types are instances of this type rather than the :class:`Enum` class despite being superficially defined as instances of the :class:`Enum` class. Thanks to metaclass abuse, enumeration types do *not* adhere to standard Pythonic semantics. Notably, the following non-standard invariants hold across *all* enumerations: .. code-block:: pycon >>> from enum import Enum >>> GyreType = Enum( ... 'GyreType', ('THE', 'FALCON', 'CANNOT', 'HEAR', 'THE', 'FALCONER')) >>> from beartype import cave >>> isinstance(GyreType, Enum) False >>> isinstance(GyreType, cave.EnumType) True >>> isinstance(GyreType, cave.ClassType) True >>> isinstance(GyreType.FALCON, cave.EnumType) False >>> isinstance(GyreType.FALCON, cave.EnumMemberType) True >>> isinstance(GyreType.FALCON, cave.ClassType) False Yes, this is insane. Yes, this is Python. ''' EnumMemberType = _Enum ''' Type of all **enumeration members** (i.e., abstract base class of all alternative choices defined as enumeration fields). Caveats ------- When type checking callable parameters, this class should *only* be referenced where the callable permissively accepts any enumeration member type rather than a specific enumeration member type. In the latter case, that type is simply that enumeration's type and should be directly referenced as such: e.g., .. code-block:: pycon >>> from enum import Enum >>> from beartype import beartype >>> EndymionType = Enum('EndymionType', ('BEAUTY', 'JOY',)) >>> @beartype ... def our_feet_were_soft_in_flowers(superlative: EndymionType) -> str: ... return str(superlative).lower() ''' # ....................{ TYPES ~ hint : pep : 585 }.................... # Define this type as either... HintGenericSubscriptedType: type = ( # If the active Python interpreter targets at least Python >= 3.9 and thus # supports PEP 585, this type; type(list[str]) # type: ignore[misc] if IS_PYTHON_AT_LEAST_3_9 else # Else, a placeholder type. UnavailableType ) ''' C-based type of all subscripted generics if the active Python interpreter targets Python >= 3.9 *or* :class:`.UnavailableType` otherwise. This type is a version-agnostic generalization of the standard :class:`types.GenericAlias` type available only under Python >= 3.9. Subscripted generics include: * :pep:`585`-compliant **builtin type hints** (i.e., C-based type hints instantiated by subscripting either a concrete builtin container class like :class:`list` or :class:`tuple` *or* an abstract base class (ABC) declared by the :mod:`collections.abc` submodule like :class:`collections.abc.Iterable` or :class:`collections.abc.Sequence`). * :pep:`484`-compliant **subscripted generics** (i.e., user-defined classes subclassing one or more :pep:`484`-compliant type hints subsequently subscripted by one or more PEP-compliant type hints). * :pep:`585`-compliant **subscripted generics** (i.e., user-defined classes subclassing one or more :pep:`585`-compliant type hints subsequently subscripted by one or more PEP-compliant type hints). Caveats ---------- **This low-level type ambiguously matches semantically unrelated PEP-compliant type hints,** rendering this type all but useless for most practical purposes. To distinguish between the various semantic types of hints ambiguously matched by this type, higher-level PEP-specific functions *must* be called instead. These include: * :func:`beartype._util.hint.pep.proposal.pep484.utilpep484.is_hint_pep484_generic`, detecting :pep:`484`-compliant generic type hints. * :func:`beartype._util.hint.pep.proposal.utilpep585.is_hint_pep585_builtin_subscripted`, detecting :pep:`585`-compliant builtin type hints. * :func:`beartype._util.hint.pep.proposal.utilpep585.is_hint_pep585_generic`, detecting :pep:`585`-compliant generic type hints. ''' # ....................{ TYPES ~ hint : pep : 604 }.................... # If this submodule is currently being statically type-checked by a pure static # type-checker, ignore false positives complaining that this type is not a type. if TYPE_CHECKING: class HintPep604Type(object): pass # Else, this submodule is *NOT* currently being statically type-checked by a # pure static type-checker. In this case, define this type properly. *sigh* else: # Define this type as either... HintPep604Type = ( # If the active Python interpreter targets at least Python >= 3.10 and # thus supports PEP 604, this type; _types.UnionType if IS_PYTHON_AT_LEAST_3_10 else # Else, a placeholder type. UnavailableType ) ''' C-based type of all :pep:`604`-compliant **new unions** (i.e., objects created by expressions of the form ``{type1} | {type2} | ... | {typeN}``) if the active Python interpreter targets Python >= 3.10 *or* :class:`.UnavailableType` otherwise. This type is a version-agnostic generalization of the standard :class:`types.UnionType` type available only under Python >= 3.10. ''' HintPep604Types: _TupleTyping[type, ...] = (type, HintGenericSubscriptedType) ''' Tuple of all :pep:`604`-compliant **new union item types** (i.e., types of all objects permissible as the items of new unions), including: * The C-based type of all types (e.g., the type of the first item in the new union ``list | None``). * The C-based type of all subscripted generics (e.g., the type of the first item in the new union ``list[dict[str, int]] | None``). ''' # ....................{ TYPES ~ hint : pep : 695 }.................... # If this submodule is currently being statically type-checked by a pure static # type-checker, ignore false positives complaining that this type is not a type. # Notably, mypy inexplicably refuses to accept this by emitting "errors" # resembling the following wherever this type is accessed: # beartype/_util/hint/pep/proposal/utilpep695.py:120: error: Variable # "beartype._cave._cavefast.HintPep695Type" is not valid as a type # [valid-type] # beartype/_util/hint/pep/proposal/utilpep695.py:120: note: See # https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases if TYPE_CHECKING: class HintPep695Type(object): pass # Else, this submodule is *NOT* currently being statically type-checked by a # pure static type-checker. In this case, define this type properly. *sigh* else: # Define this type as either... HintPep695Type = ( # If the active Python interpreter targets at least Python >= 3.12 and # thus supports PEP 695, this type; _typing.TypeAliasType if IS_PYTHON_AT_LEAST_3_12 else # Else, a placeholder type. UnavailableType ) ''' C-based type of all :pep:`695`-compliant **type aliases** (i.e., objects created by statements of the form ``type {alias_name} = {alias_value}``) if the active Python interpreter targets Python >= 3.12 *or* :class:`.UnavailableType` otherwise. This type is a version-agnostic generalization of the standard :class:`typing.TypeAliasType` type available only under Python >= 3.12. ''' # ....................{ TYPES ~ scalar }.................... StrType = str # Well, isn't that special. ''' Type of all **unencoded Unicode strings** (i.e., instances of the builtin :class:`str` class; sequences of abstract Unicode codepoints that have yet to be encoded into physical encoded bytes in encoded byte strings). This type matches: * **Builtin Unicode strings** (i.e., :class:`str` instances). * **NumPy Unicode strings** (i.e., :class:`numpy.str_` instances) if :mod:`numpy` is importable. Whereas most NumPy scalar types do *not* subclass builtin scalar types, the :class:`numpy.str_` class *does* subclass the builtin :class:`str` type. NumPy Unicode strings are thus usable wherever builtin Unicode strings are usable. Caveats ---------- This type does *not* match **encoded byte strings** (i.e., sequences of physical encoded bytes, including the builtin :class:`bytestring` type), which require foreknowledge of the encoding previously used to encode those bytes. Unencoded Unicode strings require no such foreknowledge and are thus incompatible with encoded byte strings at the API level. This type only matches **builtin Unicode strings** (i.e., :class:`str` instances) and instances of subtypes of that type (e.g., :class:`numpy.str_`, the NumPy Unicode string type). Whereas the comparable :class:`BoolType` matches arbitrary objects satisfying the boolean protocol (i.e., ``__bool__()`` dunder method) via structural subtyping, this type does *not* match arbitrary objects satisfying the string protocol via structural subtyping -- because there is no string protocol. While Python's data model does define a ``__str__()`` dunder method called to implicitly convert arbitrary objects into strings, that method is called infrequently. As exhibited by the infamously rejected :pep:`3140` proposal, the :meth:`list.__str__` implementation stringifies list items by erroneously calling the unrelated ``__repr__()`` method rather than the expected ``__str__()`` method on those items. Moreover, ``__str__()`` fails to cover common string operations such as string concatenation and repetition. Covering those operations would require a new abstract base class (ABC) matching arbitrary objects satisfying the :class:`Sequence` protocol as well as ``__str__()`` via structural subtyping; while trivial, that ABC would then ambiguously match all builtin sequence types (e.g., :class:`list`, :class:`tuple`) as string types, which they clearly are not. In short, matching only :class:`str` is the *only* unambiguous means of matching Unicode string types. ''' # ....................{ TYPES ~ scalar : number }.................... NumberType = _numbers.Number ''' Type of all **numbers** (i.e., concrete instances of the abstract :class:`numbers.Number` base class). This type effectively matches *all* numbers regardless of implementation, including: * **Integers** (i.e., real numbers expressible without fractional components), including: * **Builtin integers** (i.e., :class:`int` instances). * **NumPy integers** (e.g., :class:`numpy.int_` instances), whose types are all implicitly registered at :mod:`numpy` importation time as satisfying the :class:`numbers.Integral` protocol. * **SymPy integers** (e.g., :class:`sympy.core.numbers.Integer` instances), whose type is implicitly registered at :mod:`sympy` importation time as satisfying the class:`numbers.Integral` protocol. * **Rational numbers** (i.e., real numbers expressible as the ratio of two integers), including: * **Builtin floating-point numbers** (i.e., :class:`float` instances). * **NumPy floating-point numbers** (e.g., :class:`numpy.single` instances), all of which are implicitly registered at :mod:`numpy` importation time as :class:`numbers.Rational` subclasses. * **Stdlib fractions** (i.e., :class:`fractions.Fraction` instances). * **SymPy floating-point numbers** (e.g., :class:`sympy.core.numbers.Float` instances), whose type implicitly registered at :mod:`sympy` importation time as satisfying the class:`numbers.Real` protocol. * **SymPy rational numbers** (e.g., :class:`sympy.core.numbers.Rational` instances), whose type implicitly registered at :mod:`sympy` importation time as satisfying the class:`numbers.Rational` protocol. * **Irrational numbers** (i.e., real numbers *not* expressible as the ratio of two integers), including: * **SymPy irrational numbers** (i.e., SymPy-specific symbolic objects whose ``is_irrational`` assumption evaluates to ``True``). Caveats ---------- This type does *not* match: * **Stdlib decimals** (i.e., :class:`decimal.Decimal` instances), which support both unrounded decimal (i.e., fixed-point arithmetic) and rounded floating-point arithmetic. Despite being strictly rational, the :class:`decimal.Decimal` class only subclasses the coarse-grained abstract :class:`numbers.Number` base superclass rather than the fine-grained abstract :class:`numbers.Rational` base subclass. So it goes. * **SymPy complex numbers,** which are "non-atomic" (i.e., defined as the combination of two separate real and imaginary components rather than as one unified complex number containing these components) and thus incommensurable with all of the above "atomic" types. ''' NumberRealType = IntOrFloatType = _numbers.Real ''' Type of all **real numbers** (i.e., concrete instances of the abstract :class:`numbers.Real` base class; numbers expressible as linear values on the real number line). This type matches all numbers matched by :class:`NumberType` *except* complex numbers with non-zero imaginary components, which (as the name implies) are non-real. Equivalently, this type matches all integers (e.g., :class:`int`, :class:`numpy.int_`), floating-point numbers (e.g., :class:`float`, :class:`numpy.single`), rational numbers (e.g., :class:`fractions.Fraction`, :class:`sympy.core.numbers.Rational`), and irrational numbers. However, rational and irrational numbers are rarely used in comparison to integers and floating-point numbers. This type thus reduces to matching all integer and floating-point types in practice and is thus also accessible under the alias :class:`IntOrFloatType` -- a less accurate but more readable name than :class:`NumberRealType`. See Also ---------- :class:`NumberType` Further details. ''' IntType = _numbers.Integral ''' Type of all **integers** (i.e., concrete instances of the abstract :class:`numbers.Integral` base class; real numbers expressible without fractional components). This type matches all numbers matched by the :class:`NumberType` *except* complex numbers with non-zero imaginary components, rational numbers with denominators not equal to one, and irrational numbers. Equivalently, this type matches all integers (e.g., :class:`int`, :class:`numpy.int_`). See Also ---------- :class:`NumberType` Further details. ''' # ....................{ TYPES ~ stdlib : re }.................... # Regular expression types are also sufficiently obscure to warrant # formalization here. # Yes, this is the only reliable means of obtaining the type of compiled # regular expressions. For unknown reasons presumably concerning the archaic # nature of Python's regular expression support, this type is *NOT* publicly # exposed. While the private "re._pattern_type" attribute does technically # provide this type, it does so in a private and hence non-portable manner. RegexCompiledType: type = _re.Pattern ''' Type of all **compiled regular expressions** (i.e., objects created and returned by the stdlib :func:`re.compile` function). ''' # Yes, this type is required for type validation at module scope elsewhere. # Yes, this is the most time-efficient means of obtaining this type. No, this # type is *NOT* directly importable. Although this type's classname is # published to be "_sre.SRE_Match", the "_sre" C extension provides no such # type for pure-Python importation. So it goes. RegexMatchType: type = _re.Match ''' Type of all **regular expression match objects** (i.e., objects returned by the :func:`re.match` function). ''' # ....................{ TUPLES ~ unavailable }.................... # Unavailable types are defined *BEFORE* any subsequent types, as the latter # commonly leverage the former. UnavailableTypes = _UnavailableTypesTuple() ''' **Tuple of unavailable types** (i.e., types *not* available under the active Python interpreter, typically due to insufficient Python version or non-installed third-party dependencies). Caveats ---------- **This tuple should always be used in lieu of the empty tuple.** Although technically equivalent to the empty tuple, the :func:`beartype.beartype` decorator explicitly distinguishes between this tuple and the empty tuple. Specifically, for any callable parameter or return type annotated with: * This tuple, :func:`beartype.beartype` emits a non-fatal warning ignorable with a simple :mod:`warnings` filter. * The empty tuple, :func:`beartype.beartype` raises a fatal exception. ''' # ....................{ TUPLES ~ py }.................... ModuleOrStrTypes = (ModuleType, StrType) ''' Tuple of both the module *and* string type. ''' #FIXME: This is probably incorrect under Python >= 3.9, where isinstance() also #accepts "|"-delimited unions of types (e.g., float | int | str). What are #those types, exactly? TestableTypes = (ClassType, tuple) ''' Tuple of all **testable types** (i.e., types suitable for use as the second parameter passed to the :func:`isinstance` and :func:`issubclass` builtins). ''' # ....................{ TUPLES ~ call }.................... FunctionTypes = (FunctionType, FunctionOrMethodCType,) ''' Tuple of all **function types** (i.e., types whose instances are either built-in or user-defined functions). Caveats ---------- **This tuple may yield false positives when used to validate types.** Since Python fails to distinguish between C-based functions and methods, this tuple is the set of all function types as well as the ambiguous type of all C-based functions and methods. ''' # ....................{ TUPLES ~ call : method }.................... MethodBoundTypes = ( MethodBoundInstanceOrClassType, MethodBoundInstanceDunderCType) ''' Tuple of all **bound method types** (i.e., types whose instances are callable objects bound to either instances or classes). ''' MethodUnboundTypes = ( MethodUnboundClassCType, MethodUnboundInstanceDunderCType, MethodUnboundInstanceNondunderCType, ) ''' Tuple of all **unbound method types** (i.e., types whose instances are callable objects bound to neither instances nor classes). Unbound decorator objects (e.g., non-callable instances of the builtin :class:`classmethod`, :class:`property`, or :class:`staticmethod` decorator classes) are *not* callable and thus intentionally excluded. ''' MethodDecoratorBuiltinTypes = ( MethodDecoratorClassType, MethodDecoratorPropertyType, MethodDecoratorStaticType, ) ''' Tuple of all **C-based unbound method decorator types** (i.e., builtin decorator types implemented in low-level C whose instances are typically uncallable, associated with callable methods implemented in pure Python). ''' MethodDescriptorNondataTypes = ( MethodDecoratorClassType, MethodDecoratorStaticType, MethodBoundInstanceOrClassType, ) ''' Tuple of all **builtin method non-data descriptor types** (i.e., C-based descriptors builtin to Python defining only the ``__get__()`` dunder method, encapsulating read-only access to some kind of method). ''' MethodDescriptorTypes = ( # @classmethod, @staticmethod, and @property descriptor types. MethodDecoratorBuiltinTypes + ( # Method descriptor type. MethodBoundInstanceOrClassType, ) ) ''' Tuple of all **builtin method descriptor types** (i.e., C-based descriptors builtin to Python, encapsulating various operations on various kinds of methods whose instances are typically uncallable). This tuple matches the types of all: * **Class method descriptors** (i.e., methods decorated by the builtin :class:`classmethod` decorator). * **Instance method descriptors** (i.e., methods *not* decorated by a builtin method decorator). * **Property method descriptors** (i.e., methods decorated by the builtin :class:`property` decorator). * **Static method descriptors** (i.e., methods decorated by the builtin :class:`staticmethod` decorator). ''' MethodTypes = (FunctionOrMethodCType,) + MethodBoundTypes + MethodUnboundTypes ''' Tuple of all **method types** (i.e., types whose instances are callable objects associated with methods implemented in either low-level C or pure Python). Unbound decorator objects (e.g., non-callable instances of the builtin :class:`classmethod`, :class:`property`, or :class:`staticmethod` decorator classes) are *not* callable and thus intentionally excluded. Caveats ---------- **This tuple may yield false positives when used to validate types.** Since Python fails to distinguish between C-based functions and methods, this tuple is the set of all pure-Python bound and unbound method types as well as the ambiguous type of all C-based bound methods and non-method functions. ''' # ....................{ TUPLES ~ call : callable }.................... # For DRY, this tuple is defined as the set union of all function and method # types defined above converted back to a tuple. # # While this tuple could also be defined as the simple concatenation of the # "FunctionTypes" and "MethodTypes" tuples, doing so would duplicate all types # ambiguously residing in both tuples (i.e., "FunctionOrMethodCType"). Doing so # would induce inefficiencies during type checking. That would be bad. CallableTypes = tuple(set(FunctionTypes) | set(MethodTypes)) ''' Tuple of all **callable types** (i.e., types whose instances are callable objects implemented in either low-level C or high-level Python, including both built-in and user-defined functions, lambdas, methods, and method descriptors). ''' CallableCTypes = ( FunctionOrMethodCType, MethodBoundInstanceDunderCType, MethodUnboundInstanceDunderCType, MethodUnboundInstanceNondunderCType, MethodUnboundClassCType, ) ''' Tuple of all **C-based callable types** (i.e., types whose instances are callable objects implemented in low-level C rather than high-level Python). ''' CallablePyTypes = ( FunctionType, MethodBoundInstanceOrClassType, ) ''' Tuple of all **pure-Python callable types** (i.e., types whose instances are callable objects implemented in high-level Python rather than low-level C). **This tuple is empty under PyPy,** which unconditionally compiles *all* pure-Python callables into C-based callables. ''' CallableOrClassTypes = CallableTypes + (ClassType,) ''' Tuple of all callable types as well as the type of all types. ''' CallableOrStrTypes = CallableTypes + (StrType,) ''' Tuple of all callable types as well as the string type. ''' #FIXME: Define a new "CallableClassType" by copying the "BoolType" approach #except for the __call__() dunder method instead. #FIXME: Replace "ClassType" below by "CallableClassType". #FIXME: Add the "CallableClassType" type to the "CallableTypes" tuple as well. DecoratorTypes = CallableTypes + (ClassType,) ''' Tuple of all **decorator types** (i.e., both callable classes *and* the type of those classes). Caveats ---------- **This tuple may yield false positives when used to validate types.** Since classes themselves may be callable (i.e., by defining the special ``__call__`` method), this tuple is the set of all standard callable types as well as that of classes. In particular, this tuple describes all types permissible for use as decorators. Since most classes are *not* callable, however, this tuple may yield false positives when passed classes. ''' # ....................{ TUPLES ~ call : return }.................... AsyncCTypes = (AsyncGeneratorCType, AsyncCoroutineCType) ''' Tuple of all C-based types returned by all **asynchronous callables** (i.e., callables implemented in pure Python whose declaration is preceded by the ``async`` keyword). ''' # ....................{ TUPLES ~ scalar }.................... BoolOrNumberTypes = (BoolType, NumberType,) ''' Tuple of all **boolean** and **number types** (i.e., classes whose instances are either numbers or types trivially convertible into numbers). This tuple matches booleans, integers, rational numbers, irrational numbers, real numbers, and complex numbers. Booleans are trivially convertible into integers. While details differ by implementation, common implementations in lower-level languages (e.g., C, C++, Perl) typically implicitly convert: * ``False`` to ``0`` and vice versa. * ``True`` to ``1`` and vice versa. ''' # ....................{ TUPLES ~ post-init : container }.................... # Tuples of types assuming the above initialization to have been performed. MappingOrSequenceTypes = (MappingType, SequenceType) ''' Tuple of all container base classes conforming to (but *not* necessarily subclassing) the canonical :class:`collections.abc.Mapping` *or* :class:`collections.abc.Sequence` APIs. ''' ModuleOrSequenceTypes = (ModuleType, SequenceType) ''' Tuple of the module type *and* all container base classes conforming to (but *not* necessarily subclassing) the canonical :class:`collections.abc.Sequence` API. ''' NumberOrIterableTypes = (NumberType, IterableType,) ''' Tuple of all numeric types *and* all container base classes conforming to (but *not* necessarily subclassing) the canonical :class:`collections.abc.Iterable` API. ''' NumberOrSequenceTypes = (NumberType, SequenceType,) ''' Tuple of all numeric types *and* all container base classes conforming to (but *not* necessarily subclassing) the canonical :class:`collections.abc.Sequence` API. ''' # ....................{ TUPLES ~ post-init : scalar }.................... ScalarTypes = BoolOrNumberTypes + (StrType,) ''' Tuple of all **scalar types** (i.e., classes whose instances are atomic scalar primitives). This tuple matches all: * **Boolean types** (i.e., types satisfying the :class:`BoolType` protocol). * **Numeric types** (i.e., types satisfying the :class:`NumberType` protocol). * **Textual types** (i.e., types contained in the :class:`StrTypes` tuple). ''' # ....................{ TUPLES ~ stdlib }.................... RegexTypes = (RegexCompiledType, StrType) ''' Tuple of all **regular expression-like types** (i.e., types either defining regular expressions or losslessly convertible to such types). This tuple matches: * The **compiled regular expression type** (i.e., type of all objects created and returned by the stdlib :func:`re.compile` function). * All **textual types** (i.e., types contained in the :class:`StrTypes` tuple). ''' beartype-0.18.5/beartype/_cave/_cavemap.py000066400000000000000000000217261461113517100204450ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype cave-specific abstract base classes (ABCs).** ''' # ....................{ TODO }.................... #FIXME: As with the parallel "beartype._cave.abc" submodule, refactor the #contents of this private submodule into the newly proposed public #"beartype.caver" submodule. To do so: # #* In the "beartype.caver" submodule: # * Define a new make_type() function copied from the # betse.util.type.classes.define_class() function (but renamed, obviously). # * Define a new make_type_defaultdict() function copied from the # betse.util.type.iterable.mapping.mapcls.DefaultDict() function, but with # signature resembling: # def make_type_defaultdict( # name: str, # missing_key_maker: CallableTypes, # items: (Iterable, type(None)), # ) -> type: # Internally, this function should call make_type() to do so. # ....................{ IMPORTS }.................... from beartype.roar import ( BeartypeCaveNoneTypeOrKeyException, BeartypeCaveNoneTypeOrMutabilityException, ) from beartype.typing import ( Any, Tuple, Union, ) from beartype._util.hint.nonpep.utilnonpeptest import die_unless_hint_nonpep # ....................{ CONSTANTS }.................... _NoneType: type = type(None) ''' Type of the :data:`None` singleton, duplicated from the :mod:`beartype.cave` submodule to prevent cyclic import dependencies. ''' _NoneTypes: Tuple[type, ...] = (_NoneType,) ''' Tuple of only the type of the :data:`None` singleton. ''' # ....................{ HINTS }.................... _TypeTuple = Tuple[Union[type, str], ...] ''' PEP-compliant type hint matching a **type tuple** (i.e., tuple containing only types and forward references to deferred types specified as the fully-qualified names of those types). ''' # ....................{ CLASSES }.................... class _NoneTypeOrType(dict): ''' :class:`NoneType` **tuple factory type** (i.e., :class:`dict` subclass, instances of which are dictionaries mapping from arbitrary types or tuples of types to the same types or tuples of types concatenated with the type of the :data:`None` singleton). ''' # ..................{ DUNDERS }.................. def __missing__(self, hint: Union[type, str, _TypeTuple]) -> _TypeTuple: ''' Dunder method explicitly called by the superclass :meth:`dict.__getitem__` method implicitly called on getting the passed missing key with ``[``- and ``]``-delimited syntax. Specifically, this method: * If a single type or string is passed: #. Creates a new 2-tuple containing only that object and the type of the :data:`None` singleton. #. Maps the passed type to that 2-tuple. #. Returns that 2-tuple. * Else if a tuple of one or more types and/or strings is passed: #. Creates a new tuple appending the type of the :data:`None` singleton to the passed tuple. #. Maps the passed type to the new tuple. #. Returns the new tuple. * Else, raises an exception. Parameters ---------- hint : Union[type, str, _TypeTuple] Type, string, or tuple of one or more types and/or strings *not* currently cached by this factory. Returns ------- _TypeTuple Tuple of types appending the type of the :data:`None` singleton to the passed type, string, or tuple of types and/or strings. Raises ------ BeartypeCaveNoneTypeOrKeyException If this key is neither: * A **string** (i.e., forward reference specified as either a fully-qualified or unqualified classname). * A **type** (i.e., class). * A **non-empty tuple** (i.e., semantic union of types) containing only strings and types. ''' # If this missing key is *NOT* a PEP-noncompliant type hint, raise an # exception. die_unless_hint_nonpep( hint=hint, exception_prefix='"NoneTypeOr" key', exception_cls=BeartypeCaveNoneTypeOrKeyException, ) # Tuple of types to be cached and returned by this call. hint_or_none: _TypeTuple = None # type: ignore[assignment] # If this key is a type... if isinstance(hint, type): # If this type is "NoneType", reuse the existing "_NoneTypes" tuple # containing only this type. if hint is _NoneType: hint_or_none = _NoneTypes # Else, this type is *NOT* "NoneType". In this case, instantiate a # new tuple of types concatenating this type with "NoneType". else: hint_or_none = (hint, _NoneType) # Else if this key is a non-empty tuple... elif isinstance(hint, tuple): # If "NoneType" is already in this tuple, reuse this tuple as is. if _NoneType in hint: hint_or_none = hint # Else, "NoneType" is *NOT* already in this tuple. In this case, # instantiate a new tuple of types concatenating this tuple with # "NoneType". else: hint_or_none = hint + _NoneTypes # Else, this key is invalid. Thanks to the above call to the # die_unless_hint_nonpep() function, this should *NEVER* occur. # Nonetheless, raise a human-readable exception for sanity. else: raise BeartypeCaveNoneTypeOrKeyException( f'"NoneTypeOr" key {repr(hint)} unsupported.') # Cache this tuple under this key. self[hint] = hint_or_none # Return this tuple. return hint_or_none # ....................{ SINGLETONS }.................... NoneTypeOr: Any = _NoneTypeOrType() ''' **:class:``NoneType`` tuple factory** (i.e., dictionary mapping from arbitrary types or tuples of types to the same types or tuples of types concatenated with the type of the :data:`None` singleton). This factory efficiently generates and caches tuples of types containing :class:`NoneType` from arbitrary user-specified types and tuples of types. To do so, simply index this factory with any desired type *or* tuple of types; the corresponding value will then be another tuple containing :class:`NoneType` and that type *or* those types. Motivation ---------- This factory is commonly used to type-hint **optional callable parameters** (i.e., parameters defaulting to :data:`None` when *not* explicitly passed by the caller). Although such parameters may also be type-hinted with a tuple manually containing :class:`NoneType`, doing so inefficiently recreates these tuples for each optional callable parameter across the entire codebase. This factory avoids such inefficient recreation. Instead, when indexed with any arbitrary key: * If that key has already been successfully accessed on this factory, this factory returns the existing value (i.e., tuple containing :class:`NoneType` and that key if that key is a type *or* the items of that key if that key is a tuple) previously mapped and cached to that key. * Else, if that key is: * A type, this factory: #. Creates a new tuple containing that type and :class:`NoneType`. #. Associates that key with that tuple. #. Returns that tuple. * A tuple of types, this factory: #. Creates a new tuple containing these types and :class:`NoneType`. #. Associates that key with that tuple. #. Returns that tuple. * Any other object, raises a human-readable :class:`beartype.roar.BeartypeCaveNoneTypeOrKeyException` exception. This factory is analogous to the :pep:`484`_-compliant :class:`typing.Optional` type despite otherwise *not* complying with :pep:`484`_. Examples -------- .. code-block:: pycon # Function accepting an optional parameter with neither # "beartype.cave" nor "typing". >>> def to_autumn(season_of_mists: (str, type(None)) = None) -> str ... return season_of_mists if season_of_mists is not None else ( ... 'While barred clouds bloom the soft-dying day,') # Function accepting an optional parameter with "beartype.cave". >>> from beartype.cave import NoneTypeOr >>> def to_autumn(season_of_mists: NoneTypeOr[str] = None) -> str ... return season_of_mists if season_of_mists is not None else ( ... 'Then in a wailful choir the small gnats mourn') # Function accepting an optional parameter with "typing". >>> from typing import Optional >>> def to_autumn(season_of_mists: Optional[str] = None) -> str ... return season_of_mists if season_of_mists is not None else ( ... 'Or sinking as the light wind lives or dies;') ''' beartype-0.18.5/beartype/_check/000077500000000000000000000000001461113517100164475ustar00rootroot00000000000000beartype-0.18.5/beartype/_check/__init__.py000066400000000000000000000000001461113517100205460ustar00rootroot00000000000000beartype-0.18.5/beartype/_check/_checksnip.py000066400000000000000000000155171461113517100211400ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **type-checking function code snippets** (i.e., triple-quoted pure-Python string constants formatted and concatenated together to dynamically generate the implementations of functions type-checking arbitrary objects against arbitrary PEP-compliant type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._check.checkmagic import ( ARG_NAME_CONF, ARG_NAME_CLS_STACK, ARG_NAME_FUNC, ARG_NAME_EXCEPTION_PREFIX, ARG_NAME_GET_VIOLATION, ARG_NAME_HINT, ARG_NAME_WARN, VAR_NAME_PITH_ROOT, VAR_NAME_RANDOM_INT, VAR_NAME_VIOLATION, ) # ....................{ CODE ~ signature }.................... CODE_CHECKER_SIGNATURE = f'''{{code_signature_prefix}}def {{func_name}}( {VAR_NAME_PITH_ROOT}, {{code_signature_args}} ):''' ''' Code snippet declaring the signature of all type-checking tester functions created by the :func:`beartype._check.checkmagic.make_func_tester` factory. Note that: * This signature intentionally: * Avoids annotating its parameters or return by type hints. Doing so would be: * Pointless, as the type-checking functions dynamically created and returned by factory functions defined by the "beartype._check.checkmake" submodule are only privately called by the public beartype.door.is_bearable() and beartype.door.die_if_unbearable() runtime type-checkers. * Harmful, as doing so would prevent this common signature from being generically reused as the signature for both raisers and testers. * Names the single public parameter accepted by this tester function ``{VAR_NAME_PITH_ROOT}``. Doing so trivially ensures that the memoized type-checking boolean expression generated by the :func:`beartype._check.code.codemake.make_check_expr` code factory implicitly type-checks the passed object *without* further modification (e.g., global search-and-replacement), ensuring that memoized expression may be efficiently reused as is *without* subsequent unmemoization. Clever, huh? * ``code_signature_prefix`` is usually either: * For synchronous callables, the empty string. * For asynchronous callables (e.g., asynchronous generators, coroutines), the space-suffixed keyword ``"async "``. ''' # ....................{ CODE ~ check }.................... CODE_TESTER_CHECK_PREFIX = ''' # Return true only if the passed object satisfies this type hint. return ''' ''' Code snippet prefixing the type-check of an arbitrary object passed to a type-checking tester function against an arbitrary type hint passed to the same function. ''' # ....................{ CODE ~ check }.................... CODE_RAISER_HINT_OBJECT_CHECK_PREFIX = ''' # Type-check this object against this type hint. if not ''' ''' Code snippet prefixing the type-check of an arbitrary object passed to a type-checking raiser function against an arbitrary type hint passed to the same function. ''' CODE_RAISER_FUNC_PITH_CHECK_PREFIX = ''' # Type-check this parameter or return against this type hint. if not ''' ''' Code snippet prefixing the type-check of a parameter or return of a decorated callable against the type hint annotating that parameter or return. ''' # ....................{ CODE ~ violation : get }.................... CODE_GET_HINT_OBJECT_VIOLATION = f''': {VAR_NAME_VIOLATION} = {ARG_NAME_GET_VIOLATION}( obj={VAR_NAME_PITH_ROOT}, hint={ARG_NAME_HINT}, conf={ARG_NAME_CONF}, exception_prefix={ARG_NAME_EXCEPTION_PREFIX},{{arg_random_int}} ) ''' ''' Code snippet suffixing all code type-checking the **root pith** (i.e., arbitrary object) against the root type hint annotating that pith by either raising a fatal exception or emitting a non-fatal warning. This snippet expects to be formatted with these named interpolations: * ``{arg_random_int}``, whose value is either: * If type-checking for the current type hint requires a pseudo-random integer, :data:`.CODE_HINT_ROOT_SUFFIX_RANDOM_INT`. * Else, the empty substring. ''' CODE_GET_FUNC_PITH_VIOLATION = f''': {VAR_NAME_VIOLATION} = {ARG_NAME_GET_VIOLATION}( func={ARG_NAME_FUNC}, conf={ARG_NAME_CONF}, pith_name={{pith_name}}, pith_value={VAR_NAME_PITH_ROOT},{{arg_cls_stack}}{{arg_random_int}} ) ''' ''' Code snippet suffixing all code type-checking the **root pith** (i.e., value of the current parameter or return value) against the root type hint annotating that pith by either raising a fatal exception or emitting a non-fatal warning. This snippet expects to be formatted with these named interpolations: * ``{arg_cls_stack}``, whose value is either: * If type-checking for the current type hint requires the type stack, :data:`.CODE_HINT_ROOT_SUFFIX_CLS_STACK`. * Else, the empty substring. * ``{arg_random_int}``, whose value is either: * If type-checking for the current type hint requires a pseudo-random integer, :data:`.CODE_HINT_ROOT_SUFFIX_RANDOM_INT`. * Else, the empty substring. ''' CODE_GET_VIOLATION_CLS_STACK = f''' cls_stack={ARG_NAME_CLS_STACK},''' ''' Code snippet passing the value of the **type stack** (i.e., either a tuple of the one or more :func:`beartype.beartype`-decorated classes lexically containing the class variable or method annotated by a type hint type-checked by the larger code snippet embedding this snippet *or* :data:`None`) required by the current call to the exception-handling function call embedded in the :data:`.CODE_HINT_ROOT_SUFFIX` snippet. ''' CODE_GET_VIOLATION_RANDOM_INT = f''' random_int={VAR_NAME_RANDOM_INT},''' ''' Code snippet passing the value of the random integer previously generated for the current call to the exception-handling function call embedded in the :data:`.CODE_HINT_ROOT_SUFFIX` snippet. ''' # ....................{ CODE ~ violation }.................... CODE_RAISE_VIOLATION = f''' raise {VAR_NAME_VIOLATION}''' ''' Code snippet raising the type-checking violation previously generated by the :data:`.CODE_HINT_ROOT_SUFFIX` or :data:`.PEP484_CODE_CHECK_NORETURN` code snippets as a fatal exception. ''' CODE_WARN_VIOLATION = f''' {ARG_NAME_WARN}(str({VAR_NAME_VIOLATION}), type({VAR_NAME_VIOLATION}))''' ''' Code snippet emitting the type-checking violation previously generated by the :data:`.CODE_HINT_ROOT_SUFFIX` or :data:`.PEP484_CODE_CHECK_NORETURN` code snippets as a non-fatal warning. ''' beartype-0.18.5/beartype/_check/checkcache.py000066400000000000000000000040371461113517100210660ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **type-checking cache utilities** (i.e., low-level callables manipulating global dictionaries distributed throughout the :mod:`beartype._check` subpackage). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._check.code.codescope import _tuple_union_to_tuple_union from beartype._check.convert.convcoerce import _hint_repr_to_hint from beartype._check.forward.reference.fwdrefmake import ( _forwardref_args_to_forwardref) from beartype._check.forward.reference.fwdrefmeta import ( _forwardref_to_referee) # ....................{ CLEARERS }.................... def clear_checker_caches() -> None: ''' Clear (i.e., empty) *all* internal caches specifically leveraged by the :mod:`beartype._check` subpackage, enabling callers to reset this subpackage to its initial state. Notably, this function clears: * The **forward reference proxy cache** (i.e., private :data:`beartype._check.forward.reference.fwdrefmake._forwardref_args_to_forwardref` dictionary). * The **forward reference referee cache** (i.e., private :data:`beartype._check.forward.reference.fwdrefmeta._forwardref_to_referee` dictionary). * The **tuple union cache** (i.e., private :data:`beartype._check.code.codescope._tuple_union_to_tuple_union` dictionary). * The **type hint coercion cache** (i.e., private :data:`beartype._check.convert.convcoerce._hint_repr_to_hint` dictionary). ''' # print('Clearing all \"beartype._check\" caches...') # Clear all relevant caches used throughout this subpackage. _forwardref_to_referee.clear() _forwardref_args_to_forwardref.clear() _hint_repr_to_hint.clear() _tuple_union_to_tuple_union.clear() beartype-0.18.5/beartype/_check/checkcall.py000066400000000000000000001047171461113517100207440ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype dataclass** (i.e., class aggregating *all* metadata for the callable currently being decorated by the :func:`beartype.beartype` decorator).** This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeDecorWrappeeException from beartype.typing import ( Callable, Dict, FrozenSet, Optional, ) from beartype._cave._cavefast import CallableCodeObjectType from beartype._cave._cavemap import NoneTypeOr from beartype._check.forward.fwdscope import BeartypeForwardScope from beartype._conf.confcls import BeartypeConf from beartype._data.hint.datahinttyping import ( LexicalScope, TypeStack, ) from beartype._util.cache.pool.utilcachepoolobjecttyped import ( acquire_object_typed) from beartype._util.func.utilfunccodeobj import get_func_codeobj from beartype._util.func.utilfuncget import get_func_annotations from beartype._util.func.utilfunctest import ( is_func_coro, is_func_nested, ) from beartype._util.func.utilfuncwrap import unwrap_func_all_isomorphic # ....................{ CLASSES }.................... class BeartypeCall(object): ''' **Beartype call metadata** (i.e., object encapsulating *all* metadata for the user-defined callable currently being decorated by the :func:`beartype.beartype` decorator). Design ------ This the *only* object instantiated by that decorator for that callable, substantially reducing both space and time costs. That decorator then passes this object to most lower-level functions, which then: #. Access read-only instance variables of this object as input. #. Modify writable instance variables of this object as output. In particular, these lower-level functions typically accumulate pure-Python code comprising the generated wrapper function type-checking the decorated callable by setting various instance variables of this object. Caveats ------- **This object cannot be used to communicate state between low-level memoized callables** (e.g., :func:`beartype._check.code.codemake.make_func_pith_code`) **and higher-level callables** (e.g., :func:`beartype._decor.wrap.wrapmain.generate_code`). Instead, memoized callables *must* return that state as additional return values up the call stack to those higher-level callables. By definition, memoized callables are *not* recalled on subsequent calls passed the same parameters. Since only the first call to those callables passed those parameters would set the appropriate state on this object intended to be communicated to higher-level callables, *all* subsequent calls would subtly fail with difficult-to-diagnose issues. See also ``__, which exhibited this very complaint. .. _issue #5: https://github.com/beartype/beartype/issues/5 Attributes ---------- cls_stack : TypeStack **Type stack** (i.e., either tuple of zero or more arbitrary types *or* :data:`None`). See also the parameter of the same name accepted by the :func:`beartype._decor.decorcore.beartype_object` function for details. conf : BeartypeConf **Beartype configuration** (i.e., self-caching dataclass encapsulating all flags, options, settings, and other metadata configuring the current decoration of the decorated callable). func_arg_name_to_hint : dict[str, object] Dictionary mapping from the name of each annotated parameter accepted by the decorated callable to the type hint annotating that parameter. func_arg_name_to_hint_get : Callable[[str, object], object] :meth:`dict.get` method bound to the :attr:`func_arg_name_to_hint` dictionary, localized as a negligible microoptimization. Blame Guido. func_wrappee : Optional[Callable] Possibly wrapped **decorated callable** (i.e., high-level callable currently being decorated by the :func:`beartype.beartype` decorator) if the :meth:`reinit` method has been called *or* ``None`` otherwise. Note the lower-level :attr:`func_wrappee_wrappee` callable should *usually* be accessed instead; although higher-level, this callable may only be a wrapper function and hence yield inaccurate or even erroneous metadata (especially the code object) for the callable being wrapped. func_wrappee_codeobj : CallableCodeObjectType Possibly wrapped **decorated callable wrappee code object** (i.e., code object underlying the high-level :attr:`func_wrappee` callable currently being decorated by the :func:`beartype.beartype` decorator). For efficiency, this code object should *always* be accessed in lieu of inefficiently calling the comparatively slower :func:`beartype._util.func.utilfunccodeobj.get_func_codeobj` getter. func_wrappee_is_nested : bool Either: * If this wrappee callable is **nested** (i.e., declared in the body of another pure-Python callable or class), :data:`True`. * If this wrappee callable is **global** (i.e., declared at module scope in its submodule), :data:`False`. func_wrappee_scope_forward : Optional[BeartypeForwardScope] Either: * If this wrappee callable is annotated by at least one **stringified type hint** (i.e., declared as a :pep:`484`- or :pep:`563`-compliant forward reference referring to an actual type hint that has yet to be declared in the local and global scopes declaring this callable) that :mod:`beartype` has already resolved to its referent, this wrappee callable's **forward scope** (i.e., dictionary mapping from the name to value of each locally and globally accessible attribute in the local and global scope of this wrappee callable as well as deferring the resolution of each currently undeclared attribute in that scope by replacing that attribute with a forward reference proxy resolved only when that attribute is passed as the second parameter to an :func:`isinstance`-based runtime type-check). * Else, :data:`None`. Note that: * The reconstruction of this scope is computationally expensive and thus deferred until needed to resolve the first stringified type hint annotating this wrappee callable. * All callables have local scopes *except* global functions, whose local scopes are by definition the empty dictionary. func_wrappee_scope_nested_names : Optional[frozenset[str]] Either: * If this wrappee callable is annotated by at least one stringified type hint that :mod:`beartype` has already resolved to its referent, either: * If this wrappee callable is **nested** (i.e., declared in the body of another pure-Python callable or class), the non-empty frozen set of the unqualified names of all parent callables lexically containing this nested wrappee callable (including this nested wrappee callable itself). * Else, this wrappee callable is declared at global scope in its submodule. In this case, the empty frozen set. * Else, :data:`None`. func_wrappee_wrappee : Optional[Callable] Possibly unwrapped **decorated callable wrappee** (i.e., low-level callable wrapped by the high-level :attr:`func_wrappee` callable currently being decorated by the :func:`beartype.beartype` decorator) if the :meth:`reinit` method has been called *or* ``None`` otherwise. If the higher-level :attr:`func_wrappee` callable does *not* actually wrap another callable, this callable is identical to that callable. func_wrappee_wrappee_codeobj : CallableCodeObjectType Possibly unwrapped **decorated callable wrappee code object** (i.e., code object underlying the low-level :attr:`func_wrappee_wrappee` callable wrapped by the high-level :attr:`func_wrappee` callable currently being decorated by the :func:`beartype.beartype` decorator). For efficiency, this code object should *always* be accessed in lieu of inefficiently calling the comparatively slower :func:`beartype._util.func.utilfunccodeobj.get_func_codeobj` getter. func_wrapper_code_call_prefix : Optional[str] Code snippet prefixing all calls to the decorated callable in the body of the wrapper function wrapping that callable with type checking if the :meth:`reinit` method has been called *or* ``None`` otherwise. If non-``None``, this string is guaranteed to be either: * If the decorated callable is synchronous (i.e., neither a coroutine nor asynchronous generator), the empty string. * If the decorated callable is asynchronous (i.e., either a coroutine nor asynchronous generator), the ``"await "`` keyword. func_wrapper_code_signature_prefix : Optional[str] Code snippet prefixing the signature declaring the wrapper function wrapping the decorated callable with type checking. Specifically, this string is guaranteed to be either: * If the decorated callable is synchronous (i.e., neither a coroutine nor asynchronous generator), the empty string. * If the decorated callable is asynchronous (i.e., either a coroutine or asynchronous generator), the ``"async "`` keyword. func_wrapper_scope : LexicalScope **Local scope** (i.e., dictionary mapping from the name to value of each attribute referenced in the signature) of this wrapper function. func_wrapper_name : Optional[str] Machine-readable name of the wrapper function to be generated and returned by this decorator. ''' # ..................{ CLASS VARIABLES }.................. # Slot all instance variables defined on this object to minimize the time # complexity of both reading and writing variables across frequently # called @beartype decorations. Slotting has been shown to reduce read and # write costs by approximately ~10%, which is non-trivial. __slots__ = ( 'cls_stack', 'conf', 'func_arg_name_to_hint', 'func_arg_name_to_hint_get', 'func_wrappee', 'func_wrappee_codeobj', 'func_wrappee_is_nested', 'func_wrappee_scope_forward', 'func_wrappee_scope_nested_names', 'func_wrappee_wrappee', 'func_wrappee_wrappee_codeobj', 'func_wrapper_code_call_prefix', 'func_wrapper_code_signature_prefix', 'func_wrapper_scope', 'func_wrapper_name', ) # Coerce instances of this class to be unhashable, preventing spurious # issues when accidentally passing these instances to memoized callables by # implicitly raising a "TypeError" exception on the first call to those # callables. There exists no tangible benefit to permitting these instances # to be hashed (and thus also cached), since these instances are: # * Specific to the decorated callable and thus *NOT* safely cacheable # across functions applying to different decorated callables. # * Already cached via the acquire_object_typed() function called by the # "beartype._decor.decormain" submodule. # # See also: # https://docs.python.org/3/reference/datamodel.html#object.__hash__ __hash__ = None # type: ignore[assignment] # ..................{ INITIALIZERS }.................. def __init__(self) -> None: ''' Initialize this metadata by nullifying all instance variables. Caveats ------- **This class is not intended to be explicitly instantiated.** Instead, callers are expected to (in order): #. Acquire cached instances of this class via the :mod:`beartype._util.cache.pool.utilcachepoolobjecttyped` submodule. #. Call the :meth:`reinit` method on these instances to properly initialize these instances. ''' # Nullify instance variables for safety. self.cls_stack: TypeStack = None self.conf: BeartypeConf = None # type: ignore[assignment] self.func_arg_name_to_hint: Dict[str, object] = None # type: ignore[assignment] self.func_arg_name_to_hint_get: Callable[[str, object], object] = None # type: ignore[assignment] self.func_wrappee: Callable = None # type: ignore[assignment] self.func_wrappee_codeobj: CallableCodeObjectType = None # type: ignore[assignment] self.func_wrappee_is_nested: bool = None # type: ignore[assignment] self.func_wrappee_scope_forward: Optional[BeartypeForwardScope] = None self.func_wrappee_scope_nested_names: Optional[FrozenSet[str]] = None self.func_wrappee_wrappee: Callable = None # type: ignore[assignment] self.func_wrappee_wrappee_codeobj: CallableCodeObjectType = None # type: ignore[assignment] self.func_wrapper_code_call_prefix: str = None # type: ignore[assignment] self.func_wrapper_code_signature_prefix: str = None # type: ignore[assignment] self.func_wrapper_scope: LexicalScope = {} self.func_wrapper_name: str = None # type: ignore[assignment] def reinit( self, # Mandatory parameters. func: Callable, conf: BeartypeConf, # Optional parameters. cls_stack: TypeStack = None, ) -> None: ''' Reinitialize this metadata from the passed callable, typically after acquisition of a previously cached instance of this class from the :mod:`beartype._util.cache.pool.utilcachepoolobject` submodule. If :pep:`563` is conditionally active for this callable, this function additionally resolves all postponed annotations on this callable to their referents (i.e., the intended annotations to which those postponed annotations refer). Parameters ---------- func : Callable Callable currently being decorated by :func:`beartype.beartype`. conf : BeartypeConf Beartype configuration configuring :func:`beartype.beartype` specific to this callable. cls_stack : TypeStack **Type stack** (i.e., either tuple of zero or more arbitrary types *or* :data:`None`). See also the parameter of the same name accepted by the :func:`beartype._decor.decorcore.beartype_object` function. Raises ------ BeartypePep563Exception If evaluating a postponed annotation on this callable raises an exception (e.g., due to that annotation referring to local state no longer accessible from this deferred evaluation). BeartypeDecorWrappeeException If either: * This callable is uncallable. * This callable is neither a pure-Python function *nor* method; equivalently, if this callable is either C-based *or* a class or object defining the ``__call__()`` dunder method. * This configuration is *not* actually a configuration. * ``cls_owner`` is neither a class *nor* ``None``. ''' #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Note this method intentionally avoids creating and passing an # "exception_prefix" substring to callables called below. Why? Because # exhaustive profiling has shown that creating that substring consumes a # non-trivial slice of decoration time. In other words, efficiency. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # If this callable is uncallable, raise an exception. if not callable(func): raise BeartypeDecorWrappeeException(f'{repr(func)} uncallable.') # Else, this callable is callable. # # If this configuration is *NOT* a configuration, raise an exception. elif not isinstance(conf, BeartypeConf): raise BeartypeDecorWrappeeException( f'"conf" {repr(conf)} not beartype configuration.') # Else, this configuration is a configuration. # # If this class stack is neither a tuple *NOR* "None", raise an # exception. elif not isinstance(cls_stack, _TypeStackOrNone): raise BeartypeDecorWrappeeException( f'"cls_stack" {repr(cls_stack)} neither tuple nor "None".') # Else, this class stack is either a tuple *OR* "None". # If this class stack is *NOT* "None", this class stack is a tuple. In # this case, for each item of this class stack tuple... if cls_stack: for cls_stack_item in cls_stack: # If this item is *NOT* a type, raise an exception. if not isinstance(cls_stack_item, type): raise BeartypeDecorWrappeeException( f'"cls_stack" item {repr(cls_stack_item)} not type.') # Else, this class stack is "None". # Classify all passed parameters. self.cls_stack = cls_stack self.conf = conf # Wrappee callable currently being decorated. self.func_wrappee = func # Possibly unwrapped callable unwrapped from this wrappee callable. self.func_wrappee_wrappee = unwrap_func_all_isomorphic(func) # self.func_wrappee_wrappee = unwrap_func_all(func) # print(f'func_wrappee: {self.func_wrappee}') # print(f'func_wrappee_wrappee: {self.func_wrappee_wrappee}') # True only if this wrappee callable is nested. As a minor efficiency # gain, we can avoid the slightly expensive call to is_func_nested() by # noting that: # * If the class stack is non-empty, then this wrappee callable is # necessarily nested in one or more classes. # * Else, defer to the is_func_nested() tester. self.func_wrappee_is_nested = bool(cls_stack) or is_func_nested(func) # Defer the resolution of both global and local scopes for this wrappee # callable until needed to subsequently resolve stringified type hints. self.func_wrappee_scope_forward = None self.func_wrappee_scope_nested_names = None # Possibly wrapped callable code object. self.func_wrappee_codeobj = get_func_codeobj( func=func, exception_cls=BeartypeDecorWrappeeException, ) # Possibly unwrapped callable code object. self.func_wrappee_wrappee_codeobj = get_func_codeobj( func=self.func_wrappee_wrappee, exception_cls=BeartypeDecorWrappeeException, ) # Efficiently reduce this local scope back to the dictionary of all # parameters unconditionally required by *ALL* wrapper functions. self.func_wrapper_scope.clear() # Machine-readable name of the wrapper function to be generated. self.func_wrapper_name = func.__name__ #FIXME: Globally replace all references to "__annotations__" throughout #the "beartype._decor" subpackage with references to this instead. #Since doing so is a negligible optimization, this is fine... for now. #FIXME: *WOOPS.* Due to PEP 649, we absolutely need to get out ahead of #this before Python 3.13 and catastrophe strike. Notably: #* Define a new "beartype._data.hint.datahinttyping.Hintable" type hint # resembling: # from beartype._cave._cavefast import ( # FunctionType, # MethodBoundInstanceOrClassType, # ) # # Hintable = Union[ # FunctionType, # <-- pure-Python function # MethodBoundInstanceOrClassType, # <-- pure-Python method # ModuleType, # <-- C-based *OR* pure-Python module # type, # <-- C-based *OR* pure-Python class # ] # ''' # PEP-compliant type hint matching any **hintable** (i.e., ideally # pure-Python object defining the ``__annotations__`` dunder # attribute as well as the ``__annotate__`` dunder callable if the # active Python interpreter targets Python >= 3.13). # ''' #* Define a new "beartype._data.cls.datacls.HintableTypes" tuple union # resembling: # from beartype._cave._cavefast import ( # FunctionType, # MethodBoundInstanceOrClassType, # ) # # HintableTypes = ( # FunctionType, # <-- pure-Python function # MethodBoundInstanceOrClassType, # <-- pure-Python method # ModuleType, # <-- C-based *OR* pure-Python module # type, # <-- C-based *OR* pure-Python class # ) #* Rename "HintAnnotations" to "HintableAnnotations" in that same # submodule. #* Define a new "beartype._util.hintable" subpackage. #* Define a new "beartype._util.hintable.utilhintableget" submodule. #* Define a new get_hintable_hint_name_to_hint() getter in that # submodule whose signature resembles: # from beartype._data.hint.datahinttyping import ( # Hintable, # HintableAnnotations, # ) # # def get_hintable_hint_name_to_hint(hintable: Hintable) -> ( # HintableAnnotations): #* Define a new "BeartypePep649Exception" exception type, please. #* Implement this getter conditionally depending on the active Python # interpreter as follows: # from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_13 # # # If the active Python interpreter targets Python >= 3.13, defer # # to the PEP 649-compliant __annotate__() dunder callable rather # # than the PEP 484-compliant __annotations__() dunder attribute. # # Why? Because the latter simply reduces to calling # # "self.__annotate__(inspect.VALUE)", which raises a "NameError" # # exception if the passed hintable is annotated by one or more # # unquoted forward references. Unacceptable! # # # # Note that this getter is memoized *ONLY* under Python >= 3.13. # # Why? Because __annotate__() *ONLY* memoizes the annotations # # dictionary it creates and returns when passed "inspect.VALUE". # # When passed *ANY* other "format" value, __annotate__() avoids # # avoids caching its return value. Creating this return value is # # algorithmically non-trivial and computationally expensive. So, # # we are effectively required to memoize this return value here. # if IS_PYTHON_AT_LEAST_3_13: # # Defer version-specific imports. # from inspect import FORWARDREF # from beartype.roar import BeartypePep649Exception # from beartype._data.cls.datacls import HintableTypes # # @callable_cached # def get_hintable_hint_name_to_hint(hintable: Hintable) -> ( # HintableAnnotations): # # if not isinstance(hintable, HintableTypes): # raise BeartypePep649( # f'Object {repr(hintable)} not hintable ' # f'(i.e., defines no PEP 649 __annotate__() ' # f'data descriptor).' # ) # # Note that this will probably become a shockingly common # # occurrence. PEP 649 openly encourages users to destroy # # the "__annotate__" dunder method by setting that method # # to "None": e.g., # # Failing that, it’s best to overwrite the object’s # # __annotate__ method with None to prevent # # inspect.get_annotations from generating stale # # results for SOURCE and FORWARDREF formats. # elif hintable.__annotate__ is None: # raise BeartypePep649( # f'Object {repr(hintable)} annotations destroyed ' # f'(i.e., PEP 649 __annotate__() data descriptor ' # f'externally set to "None").' # ) # # # Return the annotations dictionary for this hintable, # # implicitly replacing all unquoted forward references to # # undefined attributes in type hints described by this # # dictionary with "typing.ForwardRef" proxies. # return hintable.__annotate__(FORWARDREF) # else: # def get_hintable_hint_name_to_hint(hintable: Hintable) -> ( # HintableAnnotations): # return hintable.__annotations__ #* Grep the codebase for all references to "__annotations__". All such # references *MUST* immediately be refactored as calls to # get_hintable_hint_name_to_hint(). #FIXME: *SUPERB.* However, note that even the following might not #suffice for Python >= 3.13. Why? Because, to quote PEP 649: # Initially, inspect.get_annotations will call the object’s # __annotate__ method requesting the desired format. If that raises # NotImplementedError, inspect.get_annotations will construct a “fake # globals” environment, then call the object’s __annotate__ method. # inspect.get_annotations produces FORWARDREF format by creating a # new empty “fake globals” dict, pre-populating it with the current # contents of the __annotate__ method’s globals dict, binding the # “fake globals” dict to the object’s __annotate__ method, calling # that requesting VALUE format, and returning the result. # #...heh. So, our get_hintable_hint_name_to_hint() implementation needs #to either: #* Just defer to inspect.get_annotations(). I'm *NOT* particularly keen # on that. That implementation is likely to be suboptimal and, more # importantly, outside our control. Moreover, there's really *NO* need # to defer to somebody else's implementation. Why? Because we basically # already implemented the entirety of inspect.get_annotations() without # knowing it as our "beartype._check.forward" subpackage. Ergo... #* Derive mild inspiration from inspect.get_annotations(), but otherwise # substitute stock CPython stuff like "typing.ForwardRef" with # equivalent functionality from our own "beartype._check.forward" # subpackage. # #Control is pivotal here. We can iterate on underlying issues #significantly faster than CPython. We can also optimize and #microoptimize this as we see fit to generate even better yields. # #Note, however, that PEP 649-compliant stringizers are pretty #phenomenal. It's unlikely that @beartype could or even should implement #a better internal alternative. Ideally, our #"beartype._check.forward.reference" API could be mildly refactored to #internally support PEP 649-compliant stringizers under Python >= 3.13. #Is that even feasible? Who knows. But... it's certainly desirable. #FIXME: Actually... let's just defer to inspect.get_annotations(). Look. #I know. I know. It's a shame. We threw a veritable ton of volunteer #hours at "beartype._check.forward". But inspect.get_annotations() is #strictly better than anything we've done or could reasonably do. #Seriously. That train is moving on. Honestly, reproducing #inspect.get_annotations() in @beartype is probably *NOT* feasible. #inspect.get_annotations() does all sorts of intense and crazy stuff: # The “fake globals” environment will also have to create a fake # “closure”, a tuple of ForwardRef objects pre-created with the names # of the free variables referenced by the __annotate__ method. # #There's just no way we're dynamically constructing fake closures. So, #inspect.get_annotations() is it -- at least, initially. I mean, if #significant issues end up arising from our usage of #inspect.get_annotations()... well, that's fine. At that point, we'd #obviously begin deriving our alternative heavily inspired by #inspect.get_annotations(). Until then, we genuinely do need to assume #that CPython devs know what they are talking about. Call that getter. # Annotations dictionary *AFTER* resolving all postponed hints. # # Note that the functools.update_wrapper() function underlying the # @functools.wrap decorator underlying all sane decorators propagates # this dictionary from lower-level wrappees to higher-level wrappers by # default. We intentionally classify the annotations dictionary of this # higher-level wrapper, which *SHOULD* be the superset of that of this # lower-level wrappee (and thus more reflective of reality). self.func_arg_name_to_hint = get_func_annotations(func) # dict.get() method bound to this dictionary. self.func_arg_name_to_hint_get = self.func_arg_name_to_hint.get # If this callable is an asynchronous coroutine callable (i.e., # callable declared with "async def" rather than merely "def" keywords # containing *NO* "yield" expressions)... # # Note that: # * The code object of the higher-level wrapper rather than lower-level # wrappee is passed. Why? Because @beartype directly decorates *ONLY* # the former, whose asynchronicity has *NO* relation to that of the # latter. Notably, it is both feasible and (relatively) commonplace # for third-party decorators to enable: # * Synchronous callables to be called asynchronously by wrapping # synchronous callables with asynchronous closures. # * Asynchronous callables to be called synchronously by wrapping # asynchronous callables with synchronous closures. Indeed, our # top-level "conftest.py" pytest plugin does exactly this -- # enabling asynchronous tests to be safely called by pytest's # currently synchronous framework. # * The higher-level is_func_async() tester is intentionally *NOT* # called here, as doing so would also implicitly prefix all calls to # asynchronous generator callables (i.e., callables also declared # with the "async def" rather than merely "def" keywords but # containing one or more "yield" expressions) with the "await" # keyword. Whereas asynchronous coroutine objects implicitly returned # by all asynchronous coroutine callables return a single awaitable # value, asynchronous generator objects implicitly returned by all # asynchronous generator callables *NEVER* return any awaitable # value; they instead yield one or more values to external "async # for" loops. if is_func_coro(self.func_wrappee_codeobj): # Code snippet prefixing all calls to this callable. self.func_wrapper_code_call_prefix = 'await ' # Code snippet prefixing the declaration of the wrapper function # wrapping this callable with type-checking. self.func_wrapper_code_signature_prefix = 'async ' # Else, this callable is synchronous (i.e., callable declared with # "def" rather than "async def"). In this case, reduce these code # snippets to the empty string. else: self.func_wrapper_code_call_prefix = '' self.func_wrapper_code_signature_prefix = '' # ....................{ FACTORIES }.................... #FIXME: Unit test us up, please. def make_beartype_call( # Mandatory parameters. func: Callable, conf: BeartypeConf, # Variadic keyword parameters. **kwargs ) -> BeartypeCall: ''' **Beartype call metadata** (i.e., object encapsulating *all* metadata for the passed user-defined callable, typically currently being decorated by the :func:`beartype.beartype` decorator). Caveats ------- **This higher-level factory function should always be called in lieu of instantiating the** :class:`.BeartypeCall` **class directly.** Why? Brute-force efficiency. This factory efficiently reuses previously instantiated :class:`.BeartypeCall` objects rather than inefficiently instantiating new :class:`.BeartypeCall` objects. **The caller must pass the metadata returned by this factory back to the** :func:`beartype._util.cache.pool.utilcachepoolobjecttyped.release_object_typed` **function.** If accidentally omitted, this metadata will simply be garbage-collected rather than available for efficient reuse by this factory. Although hardly a worst-case outcome, omitting that explicit call largely defeats the purpose of calling this factory in the first place. Parameters ---------- func : Callable Callable to be described. conf : BeartypeConf Beartype configuration configuring :func:`beartype.beartype` uniquely specific to this callable. All remaining keyword parameters are passed as is to the :meth:`.BeartypeCall.reinit` method. Returns ------- BeartypeCall Beartype call metadata describing this callable. ''' # Previously cached callable metadata reinitialized from that callable. bear_call = acquire_object_typed(BeartypeCall) bear_call.reinit(func, conf, **kwargs) # Return this metadata. return bear_call # ....................{ GLOBALS ~ private }.................... _TypeStackOrNone = NoneTypeOr[tuple] ''' 2-tuple ``(type, type(None)``, globally cached for negligible space and time efficiency gains on validating passed parameters below. ''' beartype-0.18.5/beartype/_check/checkmagic.py000066400000000000000000000155451461113517100211110ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype decorator **type-checking function code magic** (i.e., global string constants embedded in the implementations of functions type-checking arbitrary objects against arbitrary PEP-compliant type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ NAMES }.................... NAME_PREFIX = '__beartype_' ''' Substring prefixing the names of all *other* global string constants declared by this submodule. ''' # ....................{ NAMES ~ func }.................... FUNC_CHECKER_NAME_PREFIX = f'{NAME_PREFIX}checker_' ''' Substring prefixing the unqualified basenames of all type-checking raiser and tester functions created by the :func:`beartype._check.checkmake._make_func_checker` factory function. ''' # ....................{ NAMES ~ parameter }.................... # To avoid colliding with the names of arbitrary caller-defined parameters, the # beartype-specific parameter names *MUST* be prefixed by "__beartype_". ARG_NAME_CONF = f'{NAME_PREFIX}conf' ''' Name of the **private beartype configuration parameter** (i.e., :mod:`beartype`-specific parameter whose default value is the :class:`beartype.BeartypeConf` instance configuring each wrapper function generated by the :func:`beartype.beartype` decorator). ''' ARG_NAME_CLS_STACK = f'{NAME_PREFIX}cls_stack' ''' Name of the **private decorated type stack parameter** (i.e., :mod:`beartype`-specific parameter whose default value is the type stack conditionally passed to wrappers generated by the :func:`beartype.beartype` decorator whose type-checking logic requires one or more of the classes lexically containing the decorated methods wrapped by these wrappers). ''' ARG_NAME_EXCEPTION_PREFIX = f'{NAME_PREFIX}exception_prefix' ''' Name of the **private exception prefix parameter** (i.e., :mod:`beartype`-specific parameter whose default value is the human-readable label prefixing the representation of the currently type-checked object in exception messages raised when this object violates its type hint, conditionally passed to wrappers generated by the :func:`beartype.door.die_if_unbearable` type-checker injected for :pep:`526`-compliant annotated variable assignments by :mod:`beartype.claw`-published import hooks). ''' ARG_NAME_FUNC = f'{NAME_PREFIX}func' ''' Name of the **private decorated callable parameter** (i.e., :mod:`beartype`-specific parameter whose default value is the decorated callable passed to each wrapper function generated by the :func:`beartype.beartype` decorator). ''' ARG_NAME_GETRANDBITS = f'{NAME_PREFIX}getrandbits' ''' Name of the **private getrandbits parameter** (i.e., :mod:`beartype`-specific parameter whose default value is the highly performant C-based :func:`random.getrandbits` function conditionally passed to wrappers generated by the :func:`beartype.beartype` decorator whose type-checking logic requires one or more random integers). ''' ARG_NAME_GET_VIOLATION = f'{NAME_PREFIX}get_violation' ''' Name of the **private exception raising parameter** (i.e., :mod:`beartype`-specific parameter whose default value is the :func:`beartype._check.error.errorget.get_func_pith_violation` function raising human-readable exceptions on call-time type-checking failures passed to each wrapper function generated by the :func:`beartype.beartype` decorator). ''' ARG_NAME_HINT = f'{NAME_PREFIX}hint' ''' Name of the **private type hint parameter** (i.e., :mod:`beartype`-specific parameter whose default value is the user-defined type hint unconditionally passed to the current wrapper function generated by the :func:`beartype.door.die_if_unbearable` type-checker receiving that hint). ''' #FIXME: Excise us up, pleas. This should no longer be required. ARG_NAME_TYPISTRY = f'{NAME_PREFIX}typistry' ''' Name of the **private beartypistry parameter** (i.e., :mod:`beartype`-specific parameter whose default value is the beartypistry singleton conditionally passed to every wrapper function generated by the :func:`beartype.beartype` decorator requiring one or more types or tuples of types cached by this singleton). ''' ARG_NAME_WARN = f'{NAME_PREFIX}warn' ''' Name of the **standard warn function** (i.e., :mod:`beartype`-specific parameter whose default value is the :func:`warnings.warn` function conditionally passed to every wrapper function generated by the :func:`beartype.beartype` decorator configured by either the :attr:`beartype.BeartypeConf.violation_param_type` or :attr:`beartype.BeartypeConf.violation_return_type` options to emit non-fatal warnings rather than raise fatal exceptions). ''' # ....................{ NAMES ~ var }.................... VAR_NAME_ARGS_LEN = f'{NAME_PREFIX}args_len' ''' Name of the local variable providing the **positional argument count** (i.e., number of positional arguments passed to the current call). ''' VAR_NAME_RANDOM_INT = f'{NAME_PREFIX}random_int' ''' Name of the local variable providing a **pseudo-random integer** (i.e., unsigned 32-bit integer pseudo-randomly generated for subsequent use in type-checking randomly indexed container items by the current call). ''' VAR_NAME_VIOLATION = f'{NAME_PREFIX}violation' ''' Name of the local variable providing the **violation exception** (i.e., exception describing a type-checking violation to be either raised as a fatal exception or emitted as a non-fatal warning by the current call as configured by the :attr:`beartype.BeartypeConf.violation_param_type` and :attr:`beartype.BeartypeConf.violation_return_type` options). ''' # ....................{ NAMES ~ var : pith }.................... VAR_NAME_PITH_PREFIX = f'{NAME_PREFIX}pith_' ''' Substring prefixing all local variables providing a **pith** (i.e., either the current parameter or return value *or* item contained in the current parameter or return value type-checked by the current call). ''' VAR_NAME_PITH_ROOT = f'{VAR_NAME_PITH_PREFIX}0' ''' Name of the local variable providing the **root pith** (i.e., value of the current parameter or return value being type-checked by the current call). ''' # ....................{ CODE ~ pith }.................... CODE_PITH_ROOT_NAME_PLACEHOLDER = '?|PITH_ROOT_NAME`^' ''' Placeholder source substring to be globally replaced by the **root pith name** (i.e., name of the current parameter if called by the :func:`pep_code_check_param` function *or* ``return`` if called by the :func:`pep_code_check_return` function) in the parameter- and return-agnostic code generated by the memoized :func:`beartype._check.checkmake.make_code_raiser_func_pith_check` function. See Also -------- :attr:`beartype._data.error.dataerrmagic.EXCEPTION_PLACEHOLDER` Related commentary. ''' beartype-0.18.5/beartype/_check/checkmake.py000066400000000000000000001113741461113517100207430ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype type-checking function code factories** (i.e., low-level callables dynamically generating pure-Python code snippets type-checking arbitrary objects passed to arbitrary callables against PEP-compliant type hints passed to those same callables). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import ( Callable, Optional, ) from beartype._cave._cavemap import NoneTypeOr from beartype._check.checkmagic import ( ARG_NAME_CONF, ARG_NAME_EXCEPTION_PREFIX, ARG_NAME_GETRANDBITS, ARG_NAME_GET_VIOLATION, ARG_NAME_HINT, ARG_NAME_WARN, CODE_PITH_ROOT_NAME_PLACEHOLDER, FUNC_CHECKER_NAME_PREFIX, ) from beartype._check.convert.convsanify import sanify_hint_root_statement from beartype._check.code.codemake import make_check_expr from beartype._check.error.errorget import ( get_func_pith_violation, get_hint_object_violation, ) from beartype._check.util.checkutilmake import make_func_signature from beartype._check._checksnip import ( CODE_CHECKER_SIGNATURE, CODE_RAISER_FUNC_PITH_CHECK_PREFIX, CODE_RAISER_HINT_OBJECT_CHECK_PREFIX, CODE_TESTER_CHECK_PREFIX, CODE_GET_FUNC_PITH_VIOLATION, CODE_GET_HINT_OBJECT_VIOLATION, CODE_GET_VIOLATION_CLS_STACK, CODE_GET_VIOLATION_RANDOM_INT, CODE_RAISE_VIOLATION, CODE_WARN_VIOLATION, ) from beartype._conf.confcls import ( BEARTYPE_CONF_DEFAULT, BeartypeConf, ) from beartype._conf.conftest import die_unless_conf from beartype._data.error.dataerrmagic import EXCEPTION_PLACEHOLDER from beartype._data.func.datafuncarg import ARG_NAME_RETURN_REPR from beartype._data.hint.datahinttyping import ( CallableRaiser, CallableRaiserOrTester, CallableTester, CodeGenerated, LexicalScope, TypeStack, ) from beartype._util.cache.utilcachecall import callable_cached from beartype._util.error.utilerrraise import reraise_exception_placeholder from beartype._util.error.utilerrwarn import reissue_warnings_placeholder from beartype._util.func.utilfuncmake import make_func from beartype._util.hint.pep.proposal.pep484585.utilpep484585ref import ( get_hint_pep484585_ref_names_relative_to) from beartype._util.hint.utilhinttest import is_hint_ignorable from itertools import count from warnings import ( catch_warnings, warn, ) # ....................{ FACTORIES ~ func }.................... @callable_cached def make_func_raiser( #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: All calls to this memoized factory pass parameters *POSITIONALLY* # rather than by keyword. Care should be taken when refactoring parameters, # particularly with respect to parameter position. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! hint: object, conf: BeartypeConf, exception_prefix: str, ) -> CallableRaiser: ''' **Type-checking raiser function factory** (i.e., low-level callable dynamically generating a pure-Python raiser function testing whether an arbitrary object passed to that raiser satisfies the type hint passed to this factory and either raising an exception or emitting a warning when that object violates that hint). This factory is memoized for efficiency. Caveats ------- **This factory intentionally accepts no** ``exception_cls`` **parameter.** Instead, simply set the :attr:`.BeartypeConf.violation_door_type` option of the passed ``conf`` parameter accordingly. Parameters ---------- hint : object Type hint to be type-checked. conf : BeartypeConf **Beartype configuration** (i.e., self-caching dataclass encapsulating all settings configuring type-checking for the passed object). exception_prefix : str Human-readable label prefixing the representation of this object in the exception message. Returns ------- CallableRaiser Type-checking raiser function generated by this factory for this hint. See Also -------- :func:`._make_func_checker` Further details. ''' # Defer to this lower-level factory function for ultimate lols. return _make_func_checker( # type: ignore[return-value] hint=hint, conf=conf, make_code_check=make_code_raiser_hint_object_check, exception_prefix=exception_prefix, ) @callable_cached def make_func_tester( #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: All calls to this memoized factory pass parameters *POSITIONALLY* # rather than by keyword. Care should be taken when refactoring parameters, # particularly with respect to parameter position. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Mandatory parameters. hint: object, # Optional parameters. conf: BeartypeConf = BEARTYPE_CONF_DEFAULT, exception_prefix: str = 'is_bearable() ', ) -> CallableTester: ''' **Type-checking tester function factory** (i.e., low-level callable dynamically generating a pure-Python tester function testing whether an arbitrary object passed to that tester satisfies the type hint passed to this factory and returning that result as its boolean return). This factory is memoized for efficiency. Parameters ---------- hint : object Type hint to be type-checked. conf : BeartypeConf, optional **Beartype configuration** (i.e., self-caching dataclass encapsulating all settings configuring type-checking for the passed object). Defaults to ``BeartypeConf()``, the default :math:`O(1)` configuration. Returns ------- CallableTester Type-checking tester function generated by this factory for this hint. See Also -------- :func:`._make_func_checker` Further details. ''' # Defer to this lower-level factory function for great convenience. return _make_func_checker( # type: ignore[return-value] hint=hint, conf=conf, make_code_check=make_code_tester_check, exception_prefix=exception_prefix, ) # ....................{ FACTORIES ~ code }.................... #FIXME: Unit test us up, please. @callable_cached def make_code_tester_check( hint: object, conf: BeartypeConf, exception_prefix : str, ) -> CodeGenerated: ''' Pure-Python code snippet of a type-checking tester function type-checking an arbitrary object against the passed type hint under the passed beartype configuration by returning whether that object satisfies this hint or not. This factory is memoized for efficiency. Parameters ---------- hint : object Type hint to be type-checked. conf : BeartypeConf **Beartype configuration** (i.e., self-caching dataclass encapsulating all settings configuring type-checking for the passed object). exception_prefix : str Human-readable label prefixing the representation of this object in the exception message. Returns ------- CodeGenerated Tuple containing the Python code snippet dynamically generated by this code factory and metadata describing that code. See the :attr:`beartype._data.hint.datahinttyping.CodeGenerated` type hint. See Also -------- :func:`.make_check_expr` Further details. ''' # Python code snippet comprising a single boolean expression type-checking # an arbitrary object against this hint. ( code_expr, func_scope, hint_refs_type_basename, ) = make_check_expr(hint, conf) # Code snippet type-checking the root pith against the root hint. func_code = f'{CODE_TESTER_CHECK_PREFIX}{code_expr}' # Return all metadata required by higher-level callers. return ( func_code, func_scope, hint_refs_type_basename, ) # ....................{ FACTORIES ~ code : raiser }.................... #FIXME: Unit test us up, please. @callable_cached def make_code_raiser_func_pith_check( hint: object, conf: BeartypeConf, cls_stack: Optional[TypeStack], is_param: Optional[bool], ) -> CodeGenerated: ''' Pure-Python code snippet of a type-checking raiser function type-checking a parameter or return of a decorated callable against the passed type hint under the passed beartype configuration by either raising a fatal exception *or* emitting a non-fatal warning when that parameter or return violates this hint. This factory is memoized for efficiency. Parameters ---------- hint : object Type hint to be type-checked. conf : BeartypeConf **Beartype configuration** (i.e., self-caching dataclass encapsulating all settings configuring type-checking for the passed object). cls_stack : Optional[TypeStack] **Type stack** (i.e., either a tuple of the one or more :func:`beartype.beartype`-decorated classes lexically containing the class variable or method annotated by this hint *or* :data:`None`). Defaults to :data:`None`. is_param : Optional[bool] **Tri-state pith boolean.** Although it would be simpler for this factory to accept a pith name, doing so would also effectively unmemoize this factory as well as all higher-level factories calling this factory. If the code snippet generated and returned by this factory is type-checking a previously localized: * Parameter of a decorated callable, :data:`True`. * Return of a decorated callable, :data:`False`. * Arbitrary object passed to the :func:`beartype.door.die_if_unbearable` type-checker, :data:`None`. Defaults to :data:`None`. Returns ------- CodeGenerated Tuple containing the Python code snippet dynamically generated by this code factory and metadata describing that code. See the :attr:`beartype._data.hint.datahinttyping.CodeGenerated` type hint. See Also -------- :func:`.make_check_expr` Further details. ''' #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Synchronize with the make_code_hint_object_check() factory. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Python code snippet comprising a single boolean expression type-checking # an arbitrary object against this hint. ( code_expr, func_scope, hint_refs_type_basename, ) = make_check_expr(hint, conf, cls_stack) # Code snippet passing the value of the random integer previously generated # for the current call to the exception-handling function call embedded in # the "CODE_HINT_ROOT_SUFFIX" snippet, defaulting to *NOT* passing this. arg_random_int = ( CODE_GET_VIOLATION_RANDOM_INT if ARG_NAME_GETRANDBITS in func_scope else '' ) # Code snippet passing the current class stack if needed to type-check this # type hint, defaulting to *NOT* passing this. arg_cls_stack = CODE_GET_VIOLATION_CLS_STACK if cls_stack else '' # Pass hidden parameters to this raiser function exposing the # get_func_pith_violation() getter called by the # "CODE_GET_FUNC_PITH_VIOLATION" snippet. func_scope[ARG_NAME_GET_VIOLATION] = get_func_pith_violation # Code snippet generating a human-readable violation exception or warning # when the root pith violates the root type hint. code_get_violation = CODE_GET_FUNC_PITH_VIOLATION.format( arg_cls_stack=arg_cls_stack, arg_random_int=arg_random_int, pith_name=CODE_PITH_ROOT_NAME_PLACEHOLDER, ) # Code snippet handling the previously generated violation by either raising # that violation as a fatal exception or emitting that violation as a # non-fatal warning. code_handle_violation = _make_code_raiser_violation( conf=conf, func_scope=func_scope, is_param=is_param) # Code snippet type-checking the root pith against the root hint. func_code = ( f'{CODE_RAISER_FUNC_PITH_CHECK_PREFIX}' f'{code_expr}' f'{code_get_violation}' f'{code_handle_violation}' ) # Return all metadata required by higher-level callers. return ( func_code, func_scope, hint_refs_type_basename, ) @callable_cached def make_code_raiser_func_pep484_noreturn_check( conf: BeartypeConf) -> CodeGenerated: ''' Pure-Python code snippet of a type-checking raiser function type-checking a return of a decorated callable against the :obj:`typing.NoReturn` type hint annotating that return under the passed beartype configuration by either raising a fatal exception *or* emitting a non-fatal warning when that callable violates this hint by itself failing to raise an exception. This factory is memoized for efficiency. Parameters ---------- conf : BeartypeConf **Beartype configuration** (i.e., self-caching dataclass encapsulating all settings configuring type-checking for the passed object). Returns ------- CodeGenerated Tuple containing the Python code snippet dynamically generated by this code factory and metadata describing that code. See the :attr:`beartype._data.hint.datahinttyping.CodeGenerated` type hint. ''' # Lexical scope to be returned, initialized to the empty dictionary. func_scope = {} # Pass hidden parameters to this raiser function exposing the # get_func_pith_violation() getter called by the # "CODE_GET_FUNC_PITH_VIOLATION" snippet. func_scope[ARG_NAME_GET_VIOLATION] = get_func_pith_violation # Code snippet generating a human-readable violation exception or warning # when the root pith violates the root type hint. code_get_violation = CODE_GET_FUNC_PITH_VIOLATION.format( arg_cls_stack='', arg_random_int='', pith_name=ARG_NAME_RETURN_REPR, ) # Code snippet handling the previously generated violation by either raising # that violation as a fatal exception or emitting that violation as a # non-fatal warning. code_handle_violation = _make_code_raiser_violation( conf=conf, func_scope=func_scope, is_param=False) # Code snippet type-checking the root pith against the root hint. func_code = f'{code_get_violation}{code_handle_violation}' # Return all metadata required by higher-level callers. return ( func_code, func_scope, (), # Irrelevant "hint_refs_type_basename" tuple item. Chug it! ) #FIXME: Unit test us up, please. @callable_cached def make_code_raiser_hint_object_check( hint: object, conf: BeartypeConf, exception_prefix: str, ) -> CodeGenerated: ''' Pure-Python code snippet of a type-checking raiser function type-checking an arbitrary object against the passed type hint under the passed beartype configuration by either raising a fatal exception *or* emitting a non-fatal warning when that object violates this hint. This factory is memoized for efficiency. Parameters ---------- hint : object Type hint to be type-checked. conf : BeartypeConf **Beartype configuration** (i.e., self-caching dataclass encapsulating all settings configuring type-checking for the passed object). exception_prefix : str Human-readable label prefixing the representation of this object in the exception message. Returns ------- CodeGenerated Tuple containing the Python code snippet dynamically generated by this code factory and metadata describing that code. See the :attr:`beartype._data.hint.datahinttyping.CodeGenerated` type hint. See Also -------- :func:`.make_check_expr` Further details. ''' #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Synchronize with the make_code_raiser_func_pith_check() factory. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Python code snippet comprising a single boolean expression type-checking # an arbitrary object against this hint. ( code_expr, func_scope, hint_refs_type_basename, ) = make_check_expr(hint, conf) # Code snippet passing the value of the random integer previously generated # for the current call to the exception-handling function call embedded in # the "CODE_HINT_ROOT_SUFFIX" snippet, defaulting to *NOT* passing this. arg_random_int = ( CODE_GET_VIOLATION_RANDOM_INT if ARG_NAME_GETRANDBITS in func_scope else '' ) # Pass hidden parameters to this raiser function exposing: # * The passed exception prefix accessed by this snippet. # * The get_hint_object_violation() getter called by the # "CODE_GET_HINT_OBJECT_VIOLATION" snippet. # * The passed type hint accessed by this snippet. func_scope[ARG_NAME_EXCEPTION_PREFIX] = exception_prefix func_scope[ARG_NAME_GET_VIOLATION] = get_hint_object_violation func_scope[ARG_NAME_HINT] = hint # Code snippet generating a human-readable violation exception or warning # when the root pith violates the root type hint. code_get_violation = CODE_GET_HINT_OBJECT_VIOLATION.format( arg_random_int=arg_random_int) # Code snippet handling the previously generated violation by either raising # that violation as a fatal exception or emitting that violation as a # non-fatal warning. code_handle_violation = _make_code_raiser_violation( conf=conf, func_scope=func_scope, is_param=None) # Code snippet type-checking the root pith against the root hint. func_code = ( f'{CODE_RAISER_HINT_OBJECT_CHECK_PREFIX}' f'{code_expr}' f'{code_get_violation}' f'{code_handle_violation}' ) # Return all metadata required by higher-level callers. return ( func_code, func_scope, hint_refs_type_basename, ) # ....................{ PRIVATE ~ globals }.................... _func_checker_name_counter = count(start=0, step=1) ''' **Type-checking function name uniquifier** (i.e., iterator yielding the next integer incrementation starting at 0, leveraged by the :func:`._make_func_checker` factory to uniquify the names of the type-checking functions dynamically generated by that factory). ''' # ....................{ PRIVATE ~ testers }.................... def _func_checker_ignorable(obj: object) -> bool: ''' **Ignorable type-checking tester function singleton** (i.e., function unconditionally returning :data:`True`, semantically equivalent to a tester testing whether an arbitrary object passed to this tester satisfies an ignorable type hint). The :func:`make_func_tester` factory efficiently returns this singleton when passed an ignorable type hint rather than inefficiently regenerating a unique ignorable type-checking tester function for that hint. ''' return True # ....................{ PRIVATE ~ factories : func }.................... #FIXME: Unit test us up, please. def _make_func_checker( # Mandatory parameters. hint: object, conf: BeartypeConf, make_code_check: Callable[..., CodeGenerated], # Optional parameters. exception_prefix: str = 'die_if_unbearable() or is_bearable() ', ) -> CallableRaiserOrTester: ''' **Type-checking function factory** (i.e., low-level callable dynamically generating a pure-Python tester function testing whether an arbitrary object passed to that tester satisfies the type hint passed to this factory and either returning that result as its boolean return *or* raising a fatal exception or emitting a non-fatal warning if that result is :data:`False`). This factory is intentionally *not* memoized (e.g., by the ``@callable_cached`` decorator), as this factory is only called by higher-level memoized factories. Caveats ------- **This factory intentionally accepts no** ``exception_cls`` **parameter.** Doing so would only ambiguously obscure context-sensitive exceptions raised by lower-level utility functions called by this higher-level factory. Parameters ---------- hint : object Type hint to be type-checked. conf : BeartypeConf **Beartype configuration** (i.e., self-caching dataclass encapsulating all settings configuring type-checking for the passed object). make_code_check : Callable[..., CodeGenerated] **Type-checking code factory** (i.e., function dynamically generating a code snippet of a function type-checking an arbitrary object against the passed type hint under the passed beartype configuration). exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to a reasonably sensible string. Returns ------- CallableTester Type-checking tester function generated by this factory for this hint. Raises ------ All exceptions raised by the lower-level :func:`.make_check_expr` factory. Additionally, this factory also raises: BeartypeConfException If this configuration is *not* a :class:`.BeartypeConf` instance. BeartypeDecorHintForwardRefException If this hint contains one or more relative forward references, which this factory explicitly prohibits to improve both the efficiency and portability of calls by users to the resulting type-checker. _BeartypeUtilCallableException If this function erroneously generates a syntactically invalid type-checking function. That should *never* happen, but let's admit that you're still reading this for a reason. Warns ----- All warnings emitted by the lower-level :func:`.make_check_expr` factory. ''' assert callable(make_code_check), f'{repr(make_code_check)} uncallable.' # Attempt to... try: # With a context manager "catching" *ALL* non-fatal warnings emitted # during this logic for subsequent "playback" below... with catch_warnings(record=True) as warnings_issued: # ....................{ VALIDATION }.................... # If "conf" is *NOT* a configuration, raise an exception. die_unless_conf(conf) # Else, "conf" is a configuration. # Either: # * If this hint is PEP-noncompliant, the PEP-compliant type hint # converted from this PEP-noncompliant type hint. # * If this hint is PEP-compliant and supported, this hint as is. # * Else, raise an exception (i.e., if this hint is neither # PEP-noncompliant nor a supported PEP-compliant hint). # # Do this first *BEFORE* passing this hint to any further callables. hint = sanify_hint_root_statement( hint=hint, conf=conf, exception_prefix=EXCEPTION_PLACEHOLDER) # If this hint is ignorable, all objects satisfy this hint. In this # case, return a trivial function unconditionally returning true. if is_hint_ignorable(hint): return _func_checker_ignorable # Else, this hint is unignorable. # ....................{ CODE }.................... # Python code snippet comprising a single boolean expression # type-checking an arbitrary object against this hint. ( code_check, func_scope, hint_refs_type_basename, ) = make_code_check(hint, conf, exception_prefix) #FIXME: Actually, nothing below is particularly significant. Users #now basically require this. So, let's find a way to do this. The #only genuinely significant blocker here from @beartype's #perspective is *MEMOIZATION.* Currently, the parent factories #(e.g., make_func_raiser()) transitively calling this factory are #memoized by @callable_cached. Clearly, memoization breaks down in #the face of relative forward references... *OR DOES IT!?* We now #need to probably: #* Figure out a way of replacing all relative forward references # with corresponding "ForwardRefRelativeProxy" objects. #* This is a fundamentally new type of thing we currently do *NOT* # have. The idea here is that these objects should dynamically # introspect up the call stack for the first stack frame residing # in a non-"beartype" module, which these objects then resolve each # relative forward reference against. #* Consider refactoring our "codemake" algorthm to unconditionally # do this for *ALL* relative forward references. Doing so would # (probably) be a lot faster than the current global string # replacement approach... maybe. Okay, maybe not. But maybe. # #Sounds fun! Sounds like a lot of non-trivial work, too. But that's #where all the fun resides, doesn't it? *DOESN'T IT!?* #FIXME: *WAIT.* That doesn't quite work. The issue, of course, that #the scope in which a callable is called may no longer have access #to the scope in which a callable was defined, which is where the #class referred to by relative forward references actually lives. #So, we absolutely should *NOT* "Consider refactoring our..." No. #Don't do that. That said, the above idea *SHOULD* still behave #itself for if_bearable() and die_if_unbearable(), because these #statement-level type-checkers actually do run in the same scopes #that their type hints are defined in. Huh. Pretty nifty, eh? This #then suggests that: #* We'll need to generalize our "codemake" function to accept a new # optional "is_refs_relative_proxy: bool = False" parameter. When: # * "True", code generation replaces all relative forward # references with corresponding "ForwardRefRelativeProxy" objects # as detailed above. # * "False", code generation simply returns relative forward # references as it currently does. # If this hint contains one or more relative forward references, # this hint is non-portable across lexical scopes. In this case, # raise an exception. Why? Because this hint is relative to and thus # valid only with respect to the caller's current lexical scope. # However, there is *NO* guarantee that the type-checking function # created and returned by this factory resides in the same lexical # scope. # # Suppose that type-checking function does, however. Even in that # best case, *ALL* calls to that tester would still be non-portable. # Why? Because those calls would now tacitly assume the original # lexical scope that they were called in. Those calls are now # lexically-dependent and thus could *NOT* be trivially # copy-and-pasted into different lexical scopes (e.g., submodules, # classes, or callables); doing so would raise exceptions at call # time, due to being unable to resolve those references. Preventing # users from doing something that will blow up in their test suites # commits after the fact is not simply a good thing; it's really the # only sane thing left. # # Suppose that we didn't particularly care about end user sanity, # however. Even in that worst case, resolving these references would # still be non-trivial, non-portable, and (perhaps most importantly) # incredibly slow. Why? Because doing so would require iteratively # introspecting the call stack for the first callable *NOT* residing # in the "beartype" codebase. These references would then be # resolved against the global and local lexical scope of that # callable. While technically feasible, doing so would render # higher-level "beartype" functions calling this lower-level factory # (e.g., our increasingly popular public beartype.door.is_bearable() # and die_if_unbearable() type-checkers) sufficiently slow as to be # pragmatically infeasible. if hint_refs_type_basename: # Defer to a low-level getter to raise a reasonable exception. get_hint_pep484585_ref_names_relative_to( # First relative forward reference in this type hint, # arbitrarily chosen for convenience. hint=hint_refs_type_basename[0], exception_prefix=( f'{EXCEPTION_PLACEHOLDER}type hint {repr(hint)} '), ) # Else, this hint contains *NO* relative forward references. # Unqualified basename of this type-checking function, uniquified by # suffixing an arbitrary integer unique to this function. func_checker_name = ( f'{FUNC_CHECKER_NAME_PREFIX}{next(_func_checker_name_counter)}') # Python code snippet declaring the signature of the type-checking # function function to be defined and returned by this factory. code_signature = make_func_signature( func_name=func_checker_name, func_scope=func_scope, code_signature_format=CODE_CHECKER_SIGNATURE, conf=conf, ) # Python code snippet defining this type-checking function in full. func_checker_code = f'{code_signature}{code_check}' # ....................{ FUNCTION }.................... # Type-checking tester function to be returned. func_tester = make_func( func_name=func_checker_name, func_code=func_checker_code, func_locals=func_scope, func_label='die_if_unbearable() or is_bearable() type-checker', is_debug=conf.is_debug, ) # If one or more warnings were issued, reissue these warnings with each # placeholder substring (i.e., "EXCEPTION_PLACEHOLDER" instance) # replaced by a human-readable description of this callable and # annotated return. if warnings_issued: reissue_warnings_placeholder( warnings=warnings_issued, target_str=exception_prefix) # Else, *NO* warnings were issued. # If doing so raises *ANY* exception, reraise this exception with each # placeholder substring (i.e., "EXCEPTION_PLACEHOLDER" instance) replaced by # an explanatory prefix. except Exception as exception: reraise_exception_placeholder( exception=exception, target_str=exception_prefix) # Return this tester function. return func_tester # type: ignore[return-value] # ....................{ PRIVATE ~ factories : code }.................... def _make_code_raiser_violation( # Mandatory parameters. conf: BeartypeConf, func_scope: LexicalScope, # Optional parameters. is_param: Optional[bool] = None, ) -> str: ''' Pure-Python code snippet of a **type-checking raiser function** (i.e., dynamically generated by the :func:`.make_raiser_func` factory) either raising a fatal exception or emitting a non-fatal warning when an arbitrary object violates an arbitrary type hint under the passed beartype configuration in the body of that raiser. This factory is intentionally *not* memoized (e.g., by the ``@callable_cached`` decorator), as this factory is only called by higher-level memoized factories. Parameters ---------- conf : BeartypeConf **Beartype configuration** (i.e., self-caching dataclass encapsulating all settings configuring type-checking for the passed object). func_scope : LexicalScope **Lexical scope** (i.e., dictionary mapping from the relative unqualified name to value of each locally or globally scoped attribute accessible to a callable or class). is_param : Optional[bool] **Tri-state pith boolean.** Although it would be simpler for this factory to accept a pith name, doing so would also effectively unmemoize this factory as well as all higher-level factories calling this factory. If the code snippet generated and returned by this factory is type-checking a previously localized: * Parameter of a decorated callable, :data:`True`. * Return of a decorated callable, :data:`False`. * Arbitrary object passed to the :func:`beartype.door.die_if_uncallable` type-checker, :data:`None`. Defaults to :data:`None`. Returns ------- CodeGenerated Tuple containing the Python code snippet dynamically generated by this code factory and metadata describing that code. See the :attr:`beartype._data.hint.datahinttyping.CodeGenerated` type hint for details. Raises ------ All exceptions raised by the lower-level :func:`make_check_expr` factory. Warns ----- All warnings emitted by the lower-level :func:`make_check_expr` factory. See Also -------- :func:`.make_check_expr` Further details. ''' assert isinstance(conf, BeartypeConf), f'{repr(conf)} not configuration.' assert isinstance(func_scope, dict), ( f'{repr(func_scope)} not dictionary.') assert isinstance(is_param, NoneTypeOr[bool]), ( f'{repr(is_param)} neither boolean nor "None".') # Pass a hidden parameter to this raiser function exposing the passed # beartype configuration accessed by this snippet. func_scope[ARG_NAME_CONF] = conf # Code snippet handling the previously generated violation by either raising # that violation as a fatal exception or emitting that violation as a # non-fatal warning, contextually initialized below. code_violation = '' # type: ignore[assignment] # If this code snippet produces this violation by emitting a non-fatal # warning (rather than raising an exception), detected as either... if ( # If this object is neither a parameter nor return of a decorated # callable, this object was directly passed to either the # beartype.door.is_bearable() or beartype.door.die_if_unbearable() # functions. In either case, set this boolean to this previously # computed DOOR-specific boolean. conf._is_violation_door_warn if is_param is None else # Else, this object is either a parameter or return of a decorated # callable. # # If this object is be a parameter of a decorated callable, set this # boolean to this previously computed parameter-specific boolean. conf._is_violation_param_warn if is_param else # Else, this object is *NOT* a parameter of a decorated callable. In this # case, this object *MUST* be a return of a decorated callable. Set # this boolean to this previously computed return-specific boolean. conf._is_violation_return_warn ): # Emit a non-fatal warning. code_violation = CODE_WARN_VIOLATION # Pass the warnings.warn() function required to emit this warning to # this wrapper function as an optional hidden parameter. # # Note that we intentionally do *NOT* pass the higher-level # issue_warning() function. Why? Efficiency, mostly. Recall that # issue_warning() is *ONLY* called to pretend that warnings generated by # callables both defined by and residing in this codebase are actually # generated by external third-party code. Although this wrapper function # is also generated by callables defined by this codebase (including # this callable, of course), this wrapper function does *NOT* reside # inside this codebase but instead effectively resides inside the # external third-party module defining the original function this # wrapper function wraps. Needlessly passing issue_warning() rather than # warn() here would only consume CPU cycles for *NO* tangible gain. func_scope[ARG_NAME_WARN] = warn # Else... else: # Raise a fatal exception. code_violation = CODE_RAISE_VIOLATION # Return this code snippet. return code_violation beartype-0.18.5/beartype/_check/code/000077500000000000000000000000001461113517100173615ustar00rootroot00000000000000beartype-0.18.5/beartype/_check/code/__init__.py000066400000000000000000003257451461113517100215120ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. # ....................{ TODO }.................... #FIXME: [SPEED] There exists a significant optimization that we *ABSOLUTELY* #should implement. Currently, the "hints_meta" data structure is represented as #a FixedList of size j, each item of which is a k-length tuple. If you briefly #consider it, however, that structure could equivalently be represented as a #FixedList of size j * k, where we simply store the items previously stored in #each k-length tuple directly in that FixedList itself. # #Iterating forward and backward by single hints over that FixedList is still #trivial. Rather than incrementing or decrementing an index by 1, we instead #increment or decrement an index by k. # #The resulting structure is guaranteed to be considerably more space-efficient, #due to being both contiguous in memory and requiring only a single object #(and thus object dictionary) to maintain. Cue painless forehead slap. #FIXME: [PEP] Add support for Python 3.10-specific PEPs and thus: #* PEP 612-compliance. Since we don't currently support callable annotations, # we probably can't extend that non-existent support to PEP 612. Nonetheless, # we *ABSOLUTELY* should ensure that we do *NOT* raise exceptions when passed # the two new "typing" singletons introduced by this: # * "typing.ParamSpec", documented at: # https://docs.python.org/3.10/library/typing.html#typing.ParamSpec # * "typing.Concatenate", documented at: # https://docs.python.org/3.10/library/typing.html#typing.Concatenate # Ideally, we should simply ignore these singletons for now in a similar # manner to how we currently ignore type variables. After all, these # singletons are actually a new unique category of callable-specific type # variables. See also: # https://www.python.org/dev/peps/pep-0612 #FIXME: [O(n)] Ah-ha! We now know how to implement O(log n) and O(n) #type-checking in a scaleable manner that preserves @beartype's strong #performance guarantees. How? With a timed deadline cutoff. Specifically: #* Define a new "BeartypeConf.check_time_max_multiplier" instance variable, # which we've already conveniently documented. #* Note this critical formula in the documentation for that variable (with the # equality flipped so as to express the condition under which @beartype # continues to perform type-checks): # b * check_time_max_multiplier < T # # Naturally, real life isn't quite that convenient. We don't actually have # either "b" or "T". We need to dynamically compute them on each check. What we # *DO* have is: # * "B", the total time consumed by all *PRIOR* @beartype type-checks. # * "t", the current time. # * "s", the initial time at which the current round of @beartype type-checks # was started (e.g., for the current call to a @beartype-decorated callable). # * "Z", the initial time at which the active Python interpreter was started. # # For convenience, additionally let: # * "K" be "check_time_max_multiplier" for readability. # # Note that "t - s" yields the total time consumed by the *CURRENT* round of # @beartype type-checks. Ergo, "B + t - s" yields the total time consumed by # *ALL* @beartype type-checks. Given that, "b" and "T" can both then be # expressed in terms of these known quantities: # b = B + t - s # T = t - Z # # Then the above formula expands to: # (B + t - s) * K < t - Z (4 total arithmetic operations) # # Note that "t" *MUST* be localized as a local instance variable (e.g., as # "time_curr = time()) to avoid erroneous recomputation of that time. Also, "K" # and "Z" are constants. Ideally, we would be able to simplify away some of # this. Superficially, simplification yields *NO* significant joys: # Bk + Kt - Ks - t + Z < 0 # Bk + Z + t(K - 1) < Ks (6 total arithmetic operations) # # However, "K - 1" is clearly also a constant. Let "L = K - 1". Then: # KB + Z + Lt < Ks (5 total arithmetic operations) # KB - Ks + Z + Lt < 0 # K(B - s) + Lt < -Z (5 total arithmetic operations) # # Ah-ha! But "-Z" is also a constant. Let "Y = -Z". Then we have: # K(B - s) + Lt < Y (4 total arithmetic operations) # # This is considerably better. Since "t" only appears once, we no longer need # to localize the result of calling the time() function and can instead simply # embed that call directly -- a significant savings in both time and code # complexity. # # Can we do better? Possibly. Note that "s ~= t" for most intents and purposes. # In particular, "s - Z ~= t - Z" (with respect to orders of magnitude, which # is all the right side of that equation is attempting to capture, anyway). # We can then resimplify as follows: # (B + t - s) * K < s - Z (4 total arithmetic operations) # KB + Kt - Ks - s < -Z # KB + Kt - Ks - s < -Z # K(B + t) - s(K + 1) < Y # K(B + t) - Ls < Y (4 total arithmetic operations) # # Amusingly, that fails to help (and in fact reduces accuracy). Gah! The best # we can do from here is to eliminate the need for "Y" from above as follows: # K(B - s) + Lt < -Z (5 total arithmetic operations) # K(B - s) < -Lt - Z # K(B - s) < -(Lt + Z) # -K(B - s) > Lt + Z # K(s - B) > Lt + Z (4 total arithmetic operations) # K(s - B) - Lt > Z (4 total arithmetic operations) # # Furthermore, note that: # * "s" is internally localized at the start of the body of each # @beartype-decorated callable. # * "t" is simply a call to the time() function. # * "K" and "L = K - 1" don't actually need to be passed anywhere. They're # *NOT* variables; they're just hard-coded in at code generation time. # * "B and Z", on the other hand, *DO* need to passed to each # @beartype-decorated callable under the O(n) strategy. # * Moreover, "B" *MUST* be updated by each @beartype-decorated callable under # the O(n) strategy. # # Ah-ha! With respect to each call to a @beartype-decorated callable under the # O(n) strategy, the quantity "K(s - B)" is actually a constant. Since "Z" is # also a constant, we simply localize these two constants at the start of the # body of each such callable into a new constant "J = K(s - B) - Z". This then # yields: # K(s - B) - Lt > Z (4 total arithmetic operations) # K(s - B) - Z > Lt # J > Lt # Lt < J (1 total arithmetic operation) # # And... that's all she wrote, folks. While that condition holds, @beartype # continues performing O(n) type-checks. It should be noted that this # simplified conditional trivially follows from only a few steps of the # original conditional like so: # (B + t - s) * K < t - Z # KB + Kt - Ks < t - Z # Kt - t < Ks - KB - Z # t(K - 1) < K(s - B) - Z #* Altogether, the above implies that each @beartype-decorated callable under # the O(n) strategy should be passed a new hidden "__beartype_times" parameter. # This parameter is a 2-list "(process_time_start, beartype_time_total)", # where: # * "process_time_start" is simply "Z". # * "beartype_time_total" is simply "B". # # This 2-list is a global list exposed to each @beartype-decorated callable via # this "__beartype_times" parameter. Declare this global list in a separate # submodule -- say, a new "exprtime" submodule -- as follows: # from time import monotonic # # CHECK_TIMES = [monotonic(), 0.0] # ''' # **Type-checking wall-clock time accumulator** (i.e., list whose items # allow :mod:`beartype`-generated type-checkers under non-constant # strategies like :attr:`beartype.BeartypeStrategy.On` to record how much # time the active Python process has devoted to :mod:`beartype`, enabling # :mod:`beartype` to prematurely halt type-checking when those # type-checkers exceed scheduled deadlines). # # Specifically, this global is a 2-list # ``(process_time_start, beartype_time_total)``, where: # # * ``process_time_start`` is the initial time at which the active Python # process was started, denominated in fractional seconds. # * ``beartype_time_total`` is the total time consumed by all *prior* # :mod:`beartype`-generated type-checks, denominated in fractional # seconds. # ''' #* Now, note the naive implementation of an O(n) type-check for a callable # accepting a parameter annotated by the type hint "list[str]": # @beartype(conf=BeartypeConf(strategy=BeartypeStrategy.On)) # def muh_callable(muh_param: list[str]) -> None: # ... # # if not ( # isinstance(muh_param, list) and # all(isinstance(muh_item, str) for muh_item in list) # ): # raise get_func_pith_violation(...) # ... # # Trivial. Now, note the non-naive implementation of the same O(n) type-check # respecting the "check_time_max_multiplier" configuration setting: # from beartype._check._expr.exprtime import CHECK_TIMES # from time import monotonic # # @beartype(conf=BeartypeConf(strategy=BeartypeStrategy.On)) # def muh_callable( # muh_param: list[str], # __beartype_check_times = CHECK_TIMES, # __beartype_get_time_monotonic = monotonic, # ) -> None: # CHECK_TIME_START = __beartype_get_time_monotonic() # # # Constant "J" in the inequality "Lt < J" governing @beartype's # # deadline scheduler for non-constant type-checking, denominated in # # fractional seconds. # CHECK_TIME_MAX = ( # {check_time_max_multiplier}( # CHECK_TIME_START - __beartype_check_times[1] # ) - __beartype_check_times[0] # ) # # ... # # if not ( # isinstance(muh_param, list) and # # For each item of this iterable... # all( # ( # # If @beartype has yet to exceed its scheduled deadline # # for non-constant type-checks *AND*... # ( # {check_time_max_multiplier - 1} * # __beartype_get_time_monotonic() < CHECK_TIME_MAX # ) and # isinstance(muh_item, str) # ) # for muh_item in list # ) # # *AND* @beartype has still yet to exceed its scheduled deadline for # # non-constant type-checks... # ) and ( # {check_time_max_multiplier - 1} * # __beartype_get_time_monotonic() < CHECK_TIME_MAX # ): # raise get_func_pith_violation(...) # # Else, this pith either satisfies this hint *OR* @beartype has # # exceeded its scheduled deadline for non-constant type-checks. # # # Update the total time consumed by @beartype type-checks. # __beartype_check_times[1] += ( # __beartype_get_time_monotonic() - CHECK_TIME_START) # ... # #Seriously trivial, everybody. \o/ #FIXME: [DFS] The "LRUDuffleCacheStrong" class designed below assumes that #calculating the semantic height of a type hint (e.g., 3 for the complex hint #Optional[int, dict[Union[bool, tuple[int, ...], Sequence[set]], list[str]]) #is largely trivial. It isn't -- at all. Computing that without a context-free #recursion-esque algorithm of some sort is literally infeasible. We absolutely #*MUST* get that height right, since we'll be exponentiating that height to #estimate space consumption of arbitrary objects. Off-by-one errors are #unacceptable when the difference between a height of 2 and a height of 3 means #tens of thousands in additional estimated space consumption. # #So. How do we do this, then? *SIMPLE.* Okay, not simple -- but absolutely #beneficial for a medley of unrelated pragmatic reasons and thus something we #need to pursue anyway regardless of the above concerns. # #The solution is to make the breadth-first search (BFS) internally performed #by the make_func_pith_code() function below more recursion-esque. We will #*NOT* be refactoring that function to leverage: # #* Recursion rather than iteration for all of the obvious reasons. #* A stack-like depth-first search (DFS) approach. While implementing a DFS # with iteration can technically be done, doing so imposes non-trivial # technical constraints because you then need to store interim results (which # in a proper recursive function would simply be local variables) as you # iteratively complete each non-leaf node. That's horrifying. So, we'll be # preserving our breadth-first search (BFS) approach. The reason why a BFS is # often avoided in the real world are space concerns: a BFS consumes # significantly more space than a comparable DFS, because: # * The BFS constructs the entire tree before operating on that tree. # * The DFS only constructs a vertical slice of the entire tree before # operating only on that slice. # In our case, however, space consumption of a BFS versus DFS is irrelevant. # Why? Because type hints *CANNOT* be deeply nested without raising recursion # limit errors from deep within the CPython interpreter, as we well know. # Ergo, a BFS will only consume slightly more temporary space than a DFS. This # means a "FixedList" of the same size trivially supports both. # #First, let's recap what we're currently doing: # #* In a single "while ...:" loop, we simultaneously construct the BFS tree # (stored in a "FixedList" of tuples) *AND* output results from that tree as # we are dynamically constructing it. # #The "simultaneously" is the problem there. We're disappointed we didn't #realize it sooner, but our attempt to do *EVERYTHING* in a single pass is why #we had such extraordinary difficulties correctly situating code generated by #child type hints into the code generated for parent type hints. We #circumvented the issue by repeatedly performing a global search-and-replace on #the code being generated, which is horrifyingly inefficient *AND* error-prone. #We should have known then that something was wrong. Sadly, we proceeded. # #Fortunately, this is the perfect moment to correct our wrongs -- before we #proceed any deeper into a harmful path dependency. How? By splitting our #current monolithic BFS algorithm into two disparate BFS phases -- each #mirroring the behaviour of a recursive algorithm: # #1. In the first phase, a "while ...:" loop constructs the BFS tree by # beginning at the root hint, iteratively visiting all child hints, and # inserting metadata describing those hints into our "hints_meta" list as we # currently do. That's it. That's all. But that's enough. This construction # then gives us efficient random access over the entire type hinting # landscape, which then permits us to implement the next phase -- which does # the bulk of the work. To do so, we'll add additional metadata to our # current "hint_meta" tuple: e.g., # * "_HINT_META_INDEX_CHILD_FIRST_INDEX", the 0-based index into the # "hints_meta" FixedList of the first child hint of the current hint if any # *OR* "None" otherwise. Since this is a BFS, that child hint could appear # at any 0-based index following the current hint; finding that child hint # during the second phase thus requires persisting the index of that hint. # Note that the corresponding index of the last child hint of the current # hint need *NOT* be stored, as adding the length of the argument list of # the current hint to the index of the first child hint trivially gives the # index of the last child hint. # * "_HINT_META_INDEX_CODE", the Python code snippet type-checking the # current hint to be generated by the second phase. # * "_HINT_META_INDEX_HEIGHT", the 1-based height of the current hint in this # BFS tree. Leaf nodes have a height of 1. All non-leaf nodes have a height # greater than 1. This height *CANNOT* be defined during the first phase # but *MUST* instead be deferred to the second phase. # * ...probably loads more stuff, but that's fine. #2. In the second phase, another "while ...:" loop generates a Python code # snippet type-checking the root hint and all child hints visitable from that # hint in full by beginning *AT THE LAST CHILD HINT ADDED TO THE* # "hints_meta" FixedList, generating code type-checking that hint, # iteratively visiting all hints *IN THE REVERSE DIRECTION BACK UP THE TREE*, # and so on. # #That's insanely swag. It shames us that we only thought of it now. *sigh* #FIXME: Note that this new approach will probably (hopefully only slightly) #reduce decoration efficiency. This means that we should revert to optimizing #the common case of PEP-noncompliant classes. Currently, we uselessly iterate #over these classes with the same BFS below as we do PEP-compliant classes -- #which is extreme overkill. This will be trivial (albeit irksome) to revert, #but it really is fairly crucial. *sigh* #FIXME: Now that we actually have an audience (yay!), we *REALLY* need to avoid #breaking anything. But implementing the above refactoring would absolutely #break everything for an indeterminate period of time. So how do we do this? #*SIMPLE*. We leave this submodule as is *UNTIL* our refactoring passes tests. #In the meanwhile, we safely isolate our refactoring work to the following new #submodules: #* "_pephinttree.py", implementing the first phase detailed above. #* "_pephintgene.py", implementing the second phase detailed above. # #To test, we locally change a simple "import" statement in the parent #"_pepcode" submodule and then revert that import before committing. Rinse #until tests pass, which will presumably take several weeks at least. #FIXME: See additional commentary at this front-facing issue: # https://github.com/beartype/beartype/issues/31#issuecomment-799938621 #FIXME: Actually, *FORGET EVERYTHING ABOVE.* We actually do want to #fundamentally refactor this iterative BFS into an iterative DFS. Why? Because #we increasingly need to guard against both combinatorial explosion *AND* #recursion. That's imperative -- and we basically *CANNOT* do that with the #current naive BFS approach. Yes, implementing a DFS is somewhat more work. But #it's *NOT* infeasible. It's very feasible. More importantly, it's necessary. #Since @beartype should eventually handle recursive type hints, we'll need #recursion guards anyway. Specifically: #* Guarding against recursion would be trivial if we were actually using a # depth-first algorithm. While delving, you'd just maintain a local set of the # IDs of all type hints previously visited. You'd then avoid delving into # anything if the ID of that thing is already in that set. Likewise, after # delving into that thing, you'd then pop the ID of that thing off that set. #* Likewise, handling combinatorial explosion would *ALSO* be trivial if we were # actually using a depth-first algorithm. By "combinatorial explosion," we are # referring to what happens if we try to type-check dataclass and # "typing.NamedTuple" instances that are *NOT* decorated by @beartype. # Type-checking those instances has to happen at @beartype call time, clearly. # There are actually two kinds of combinatorial explosion at play here: # * Combinatorial explosion while type-checking at @beartype call time. This is # avoidable by simply type-checking *EXACTLY ONE* random field of each # "NamedTuple" instance on each call. Simple. "NamedTuple" instances are # literally just tuples, so random access is trivial. (Type-checking random # fields of dataclass instances is less trivial but still feasible; just pass # a list whose values are dataclass field names as a private # @beartype-specific parameter to type-checking @beartype wrapper functions. # That list then effectively maps from 0-based indices to dataclass field # names. We then perform random access on that list to generate random field # names, which can then be accessed with reasonable efficiency.) # * Combinatorial explosion while generating type-checking code at @beartype # decoration time. This is the problem, folks. Why? Because we currently # employ a breadth-first search (BFS), which requires generating the entire # tree of all type hints to be visited. Currently, that's fine, because type # hints are typically compact; exhausting memory is unlikely. But as soon as # we start generating type-checking code for "NamedTuple" instances *NOT* # decorated by @beartype, we have to begin visiting *ALL* type hints # annotating *ALL* fields of those type hints by adding those hints to our # BFS tree. Suddenly, combinatorial explosion becomes a very real thing. # #The solution is to radically transform our existing BFS search into a DFS #search. Again, this is something we would need to do eventually anyway to #handle recursive type hints, because how can you guard against recursion in an #iterative BFS anyway? And... anyway, DFS is simply the right approach. It's #what we should have done all along, clearly. It's also non-trivial, which is #why we didn't do it all along. # #For example, for each type hint visited by a DFS, we'll need to additionally #record metadata like: #* "_HINT_META_INDEX_ARGS_INDEX_NEXT", the 0-based index into the # "hint.__args__" tuple (listing all child type hints for the currently visited # type hint "hint") of the next child type hint of the associated parent type # hint to be visited. When "_HINT_META_INDEX_ARGS_INDEX_NEXT == # len(hint.__args__)", the DFS has successfully visited all child type hints of # the currently visited type hint "hint" and should now iteratively recurse up # (rather than down) the DFS stack. #* "_HINT_META_INDEX_CODE", the Python code snippet type-checking the currently # visited hint. This code snippet will be gradually filled in as child type # hints of the currently visited type hint are themselves visited. Indeed, this # implies that the currently visited parent type hint *MUST* always be able to # access the "_HINT_META_INDEX_CODE" entry of the most recently visited child # type hint of that parent -- which, in turn, implies that the entire # "hints_meta" FixedList of each child type hint must be temporarily preserved. # Specifically, when recursing up the DFS stack, each parent type hint will: # 1. Access the "hints_meta" FixedList of its most recently visited child type # to fill in its own "_HINT_META_INDEX_CODE". # 2. Pop that "hints_meta" FixedList of its most recently visited child type # hint off the DFS stack. # #Some type hints like unions will additionally require hint-specific entries in #their "hints_meta" FixedList. The code for a union *CANNOT* be efficiently #generated until *ALL* child type hints of that union have been. Although #hint-specific entries could be appended to the "hints_meta" FixedList #structure, doing so would rapidly increase the memory consumption of all other #types of hints for no particularly good reason. Instead, a single new #hint-specific entry should be added: #* "_HINT_META_INDEX_DATA", an arbitrary object required by this kind of hint. # In the case of unions, this will be an instance of a dataclass resembling: # @dataclass # def _HintMetaDataUnion(object): # HINTS_CHILD_NONPEP = set() # ''' # Set of all previously visited PEP-noncompliant child type hints # (e.g., isinstanceable classes) of this parent union type hint. # ''' # # HINTS_CHILD_PEP = set() # ''' # Set of all previously visited PEP-compliant child type hints of this # parent union type hint. # ''' # # Naturally, "_HintMetaDataUnion" instances should be cached with the standard # acquire_object() and release_object() approach. *shrug* # #Oh! Wait. Nevermind. We don't actually need "_HINT_META_INDEX_DATA" or #"_HintMetaDataUnion". It's true that we would need both if we needed to handle #unions strictly with a classical DFS approach -- but there's *NO* pragmatic #reason to do so. Instead, we'll just continue handling unions as we currently #do: by iterating over child type hints of unions and separating them into #PEP-compliant and PEP-noncompliant sets. So, basically a mini-BFS over unions #*BEFORE* we then delve into their PEP-compliant child type hints in the #standard DFS way. That's fine, because we're *NOT* purists here. Whatever is #fastest and simplest (in that order) is what wins. # #Note that a DFS still needs to expensively interpolate code snippets into #format templates. There's *NO* way around that; since dynamic code generation #is what we've gotten ourselves into here, string munging is a necessary "good." #FIXME: *OKAY.* It's time to contemplate the unthinkable, folks. Note that our #existing make_check_expr() code generation function internally calls the #_enqueue_hint_child() closure on each child. You should now be thinking: "Why #didn't we simply implement this recursively?" Because that is what I am now #thinking, people. Specifically, since we're *ALREADY* paying function call #costs, there's little reason not to simply go full-blown recursion and #implement this accordingly. # #Note that this is *NOT* to say that we should go FULL-FULL-BLOWN #object-oriented via the DOOR API. That would impose substantial slowdown. #Instead, rather, we should begin considering a family of low-level #sign-specific functions that generate code for each sign category: e.g., #* _make_check_expr_union() for "HintSignUnion" hints. #* _make_check_expr_sequence_1_arg() for "HintSignSequence" hints. #* And so on. # #Then define a private dictionary mapping from signs to those functions. Viola! #Misery has been substantially reduced. # #That said, the current approach still offers benefits. Since all state is #centralized into a single function, sharing state between different #code-generating "subfunctions" is trivial; they're just local variables. This #suggests that we should initially: #* Attempt to generalize the current single-function approach from BFS to DFS. #* If that fails, do *NOT* hesitate to generalize to a multi-function recursive # approach. However we can implement this is how we must implement this. #FIXME: Note that there exist four possible approaches to random item selection #for arbitrary containers depending on container type. Either the actual pith #object (in descending order of desirability): #* Satisfies "collections.abc.Sequence" (*NOTE: NOT* "typing.Sequence", as we # don't particularly care how the pith is type-hinted for this purpose), in # which case the above approach trivially applies. #* Else is *NOT* a one-shot container (e.g., generator and... are there any # other one-shot container types?) and is *NOT* slotted (i.e., has no # "__slots__" attribute), then generalize the mapping-specific # _get_dict_nonempty_random_key() approach delineated below. #* Else is *NOT* a one-shot container (e.g., generator and... are there any # other one-shot container types?) but is slotted (i.e., has a "__slots__" # attribute), then the best we can do is the trivial O(1) approach by # calling "{hint_child_pith} := next({hint_curr_pith})" to unconditionally # check the first item of this container. What you goin' do? *shrug* (Note # that we could try getting around this with a global cache of weak references # to iterators mapped on object ID, but... ain't nobody got time or interest # for that. Also, prolly plenty dangerous.) #* Else is a one-shot container, in which case *DO ABSOLUTELY NUTHIN'.* #FIXME: We should ultimately make this user-configurable (e.g., as a global #configuration setting). Some users might simply prefer to *ALWAYS* look up a #fixed 0-based index (e.g., "0", "-1"). For the moment, however, the above #probably makes the most sense as a reasonably general-purpose default. #FIXME: [THIS-IS-BOSS] *AH-HA.* First, note that the above #_get_dict_nonempty_random_key() concept, while clever, is largely useless. Why? #Because *ALL* builtin C-based reiterables (e.g., dict, set) are slotted. We'd #might as well just ignore that and leap straight to a general-purpose answer. # #Indeed, we've *FINALLY* realized how to genuinely perform iterative access to #arbitrary non-sequence containers in an O(1) manner *WITHOUT* introducing #memory leaks or requiring asynchronous background shenanigans. The core conceit #is quite simple, really. Internally: # #* @beartype maintains two global dictionaries: # * A global "_REITERABLE_ID_TO_WEAKPROXY" dictionary mapping from the object # ID of each reiterable that has been previously type-checked by @beartype to # at least once to a strong reference to a "weakref.proxy" instance safely # proxying that reiterable. # * A global "_REITERABLE_ID_TO_ITER" dictionary mapping from the object ID of # each reiterable that has been previously type-checked by @beartype to # at least once to a strong reference to an iterator over that # "weakref.proxy" instance safely proxying that reiterable. # # This approach substantially reduces the negative harms associated with memory # leaks -- although one worst-case memory leak *DOES* still remain. Notably, # since these proxies are themselves discrete objects, storing strong # references to both these proxies and these iterators could under worst-case # behaviour consume all available space. Ergo, this dictionary will need to be # efficiently maintained as a large (but still limited) LRU cache. Ideally, the # real-world size of this cache should be bound to a maximum of (say) 1MB space # consumption. Since only proxy shim objects and iterators over those objects # are stored (and we expect the size of these proxies and iterators to be quite # small), this cache *SHOULD* be able to support an exceedingly large number of # proxies before becoming full. # # Since this dictionary is only leveraged for type-checking, thread # synchronization is irrelevant (although, of course, care should be taken to # ensure that this dictionary remains internally consistent regardless of # thread preemption). #* @beartype provides a trivial _get_reiterable_item_next() getter for use in # dynamically generated type-checking code, which will then call that getter # rather than iter() on reiterables to retrieve an effectively random item from # those reiterables. Internally, this getter leverages the above global # dictionaries as follows: # # Note that this getter assumes the passed reiterable to be *NON-EMPTY.* # # It is the caller's responsibility to ensure that (e.g., by explicitly # # calling "len(reiterable)" or "bool(reiterable)" *BEFORE* calling this # # getter). Trivial, but worth noting. For efficiency, this getter # # intentionally does *NOT* explicitly validate that constraint. # def _get_reiterable_item_next( # reiterable: _BeartypeReiterableTypes) -> object: # # #FIXME: Curiously, some C-based containers *DO* support weak # #references. Crucially, this includes sets, frozensets, arrays, and # #deques. Dicts, however, do *NOT*. Are dicts the only notable # #exceptions? Not quite. *ANY* user-defined reiterable defining # #"__slots__" that does *NOT* contain the string '__weakref__' also # #does *NOT* support weak references. In short, we can really only # #perform the following for a small subset of type hints: e.g., # #* "typing.Deque". # #* "typing.FrozenSet". # #* "typing.Set". # # # #Aaaaaaand... we're pretty sure that's it. Three is better than # #nothing, of course. But... that's still not that great. We could try # #to dynamically test for weak-referenceability -- except we're pretty # #sure that that *CANNOT* be done efficiently in the general case. # #We'd need to either: # #* Call dir(), which dynamically creates and returns a new dict. # # That's right out. # #* Access "__dict__" directly, which is only defined for pure-Python # # instances. That attribute does *NOT* exist for C-based instances. # # # #Actually, the most efficient detection heuristic would probably be: # #* Define yet another global # # "_REITERABLE_TYPE_TO_IS_WEAKREFFABLE" dictionary mapping from # # reiterable types to boolean "True" only if those types can be # # weakly referenced. This dictionary can be pre-initialized for # # efficiency with the most common builtin C-based reiterable types # # in a global context as follows: # # _REITERABLE_TYPE_TO_IS_WEAKREFFABLE = { # # DefaultDict: False, # # dict: False, # # deque: True, # # frozenset: True, # # set: True, # # } # # We don't bother LRU-bounding that. Size is irrelevant here. # #* Likewise, define yet another globl # # "_REITERABLE_TYPE_TO_IS_LENGTHHINTED" dictionary mapping from # # reiterable types to boolean "True" only if those types define # # iterators defining semantically meaningful __length_hint__() # # dunder methods. Since detecting that at runtime is infeasible, we # # simply preallocate that to those we do know about: # # _REITERABLE_TYPE_TO_IS_LENGTHHINTED = { # # deque: True, # <-- unsure if true, actually *shrug* # # frozenset: True, # # set: True, # # } # #* Given that, we then efficiently detect weak-referenceability: # # REITERABLE_TYPE = reiterable.__class__ # # #FIXME: Again, optimize ...get() method access, please. # reiterable_is_weakreffable = ( # _REITERABLE_TYPE_TO_IS_WEAKREFFABLE.get(REITERABLE_TYPE)) # # if reiterable_is_weakreffable is None: # reiterable_dict = getattr(reiterable, '__dict__') # #FIXME: Alternately, we could try just taking a weak proxy # #of this reiterable and catching exceptions. Although # #slower, this caching operation only occurs once per type. # #For now, let's run with this faster heuristic. # if not reiterable_dict: # reiterable_is_weakreffable = False # else: # reiterable_slots = reiterable_dict.get('__slots__') # reiterable_is_weakreffable = ( # reiterable_slots and # '__weakref__' not in reiterable_slots # ) # _REITERABLE_TYPE_TO_IS_WEAKREFFABLE[REITERABLE_TYPE] = ( # reiterable_is_weakreffable) # # # If this reiterable is a C-based container that does *NOT* support # # weak references, reduce to simply returning the first item of this # # reiterable. # if not reiterable_is_weakreffable: # return next(iter(reiterable)) # # REITERABLE_ID = id(reiterable) # # #FIXME: Optimize by storing and calling a bound # #"_REITERABLE_ID_TO_ITER_get" method instead, please. # reiterable_iter = _REITERABLE_ID_TO_ITER.get(REITERABLE_ID) # # if reiterable_iter: # #FIXME: Note that this can be conditionally optimized for # #iterators that define the PEP 424-compliant __length_hint__() # #dunder method -- which thankfully appears to be *ALL* iterators # #over C-based reiterables (e.g., dicts, sets). For these objects, # #__length_hint__() provides the number of remaining items in the # #iterator. Ergo, this can be optimized as follows: # # if reiterable_iter.__length_hint__(): # # return next(reiterable_iter) # # else: # # ... # # # #The issue, of course, is that that *ONLY* works for strict type # #hints constrained to C-based iterables (e.g., # #"typing.Dict[...]"). General-purpose type hints like # #"typing.Mapping[...]" would be inapplicable, sadly. For the # #latter, dynamically testing for the existence of a semantically # #meaningful __length_hint__() getter would consume far more time # #than calling that getter would actually save. *shrug* # try: # return next(reiterable_iter) # except StopIteration: # #FIXME: Violates DRY a bit, but more efficient. *shrug* # reiterable_weakproxy = _REITERABLE_ID_TO_WEAKPROXY[ # REITERABLE_ID] # _REITERABLE_ID_TO_ITER[REITERABLE_ID] = iter( # reiterable_weakproxy) # return next(reiterable_iter) # # if len(_REITERABLE_ID_TO_WEAKPROXY) >= _REITERABLE_CACHE_MAX_LEN: # #FIXME: Efficiently kick out the least-used item from both the # #"_REITERABLE_ID_TO_WEAKPROXY" and "_REITERABLE_ID_TO_ITER" # #dictionaries here. Research exactly how to do that. Didn't we # #already implement an efficient LRU somewhere in @beartype? # # reiterable_weakproxy = _REITERABLE_ID_TO_WEAKPROXY[REITERABLE_ID] = ( # proxy(reiterable)) # _REITERABLE_ID_TO_ITER[REITERABLE_ID] = iter(reiterable_weakproxy) # return next(reiterable_iter) #FIXME: Pretty boss, if we do say so. And we do. There are also considerable #opportunities for both macro- and microoptimization. The biggest #macrooptimization would be doing away entirely with the #"_REITERABLE_ID_TO_WEAKPROXY" cache. Strictly speaking, we only actually need #the "_REITERABLE_ID_TO_ITER" cache. Doing away with the #"_REITERABLE_ID_TO_WEAKPROXY" cache is *PROBABLY* the right thing to do in most #cases. Why? Because we don't necessarily expect that @beartype type-checkers #will exhaust all available items for most reiterables. But we *DO* know that #all reiterables that will be type-checked will be type-checked at least once. #In other words, the trailing code in _get_reiterable_item_next() is guaranteed #to *ALWAYS* happen at least once per reiterable (so we should optimize that); #conversely, the leading code that restarts iteration from the beginning only #happens in edge cases for smaller reiterables passed or returned frequently #between @beartype-decorated callables (so we shouldn't bother optimizing that). #Optimizing away "_REITERABLE_ID_TO_WEAKPROXY" then yields: # # #FIXME: Don't even bother calling this getter with "dict" objects. The # #caller should explicitly perform an "pith.__class__ is dict" check to # #switch to more efficient "next(iter(pith.keys())" and # #"next(iter(pith.values())" logic when the pith is a "dict" object. Note # #that user-defined "dict" subclasses are fine, however. *facepalm* # def _get_reiterable_item_next( # reiterable: _BeartypeReiterableTypes) -> object: # # REITERABLE_TYPE = reiterable.__class__ # # #FIXME: Again, optimize ...get() method access, please. # reiterable_is_weakreffable = ( # _REITERABLE_TYPE_TO_IS_WEAKREFFABLE.get(REITERABLE_TYPE)) # # if reiterable_is_weakreffable is None: # reiterable_dict = getattr(reiterable, '__dict__') # #FIXME: Alternately, we could try just taking a weak proxy # #of this reiterable and catching exceptions. Although # #slower, this caching operation only occurs once per type. # #For now, let's run with this faster heuristic. # if not reiterable_dict: # reiterable_is_weakreffable = False # else: # reiterable_slots = reiterable_dict.get('__slots__') # reiterable_is_weakreffable = ( # reiterable_slots and # '__weakref__' not in reiterable_slots # ) # _REITERABLE_TYPE_TO_IS_WEAKREFFABLE[REITERABLE_TYPE] = ( # reiterable_is_weakreffable) # # # If this reiterable is a C-based container that does *NOT* support # # weak references, reduce to simply returning the first item of this # # reiterable. # if not reiterable_is_weakreffable: # return next(iter(reiterable)) # # REITERABLE_ID = id(reiterable) # # #FIXME: Optimize by storing and calling a bound # #"_REITERABLE_ID_TO_ITER_get" method instead, please. # reiterable_iter = _REITERABLE_ID_TO_ITER.get(REITERABLE_ID) # # if reiterable_iter: # #FIXME: Optimize us up, yo! # if _REITERABLE_TYPE_TO_IS_LENGTHHINTED.get(REITERABLE_TYPE): # if reiterable_iter.__length_hint__(): # return next(reiterable_iter) # else: # try: # return next(reiterable_iter) # except StopIteration: # pass # elif len(_REITERABLE_ID_TO_ITER) >= _REITERABLE_CACHE_MAX_LEN: # #FIXME: Efficiently kick out the least-used item from the # #"_REITERABLE_ID_TO_ITER" dictionary here. Research exactly how # #to do that. Didn't we already implement an efficient LRU # #somewhere in @beartype? # #FIXME: *AH-HA!* Forget LRU. Seriously. LRU would impose too # #much overhead here, as we'd need to update the LRU on each # #access. Instead, let's just friggin *CLEAR* the entire cache # #here. Yes, that's right! Nuke it from orbit, bois! So, what? # #Right? Who cares if we start over from zero? Nobody! It's # #minimal overhead to just start iterating things all over again. # #And if the cache is full up, that's a good indication that the # #caller has gone off the rails a bit, anyway. # _REITERABLE_ID_TO_ITER.clear() # <-- *BOOM STICK* # # reiterable_iter = _REITERABLE_ID_TO_ITER[REITERABLE_ID] = iter( # proxy(reiterable)) # return next(reiterable_iter) # #Seems legitimately boss, yes? Everything above is feasible and *REASONABLY* #efficient -- but is that efficient enough? Honestly, that's probably fast #enough for *MOST* use cases. If users justifiably complain about performance #degradations, we could always provide a new "BeartypeConf" parameter defaulting #to enabled to control this behaviour. *shrug* #FIXME: Note that randomly checking mapping (e.g., "dict") keys and/or values #will be non-trivial, as there exists no out-of-the-box O(1) approach in either #the general case or the specific case of a "dict". Actually, there does -- but #we'll need to either internally or externally maintain one dict.items() #iterator for each passed mapping. We should probably investigate the space #costs of that *BEFORE* doing so. Assuming minimal costs, one solution under #Python >= 3.8 might resemble: #* Define a new _get_dict_random_key() function resembling: # def _get_dict_nonempty_random_key(mapping: MappingType) -> object: # ''' # Caveats # ---------- # **This mapping is assumed to be non-empty.** If this is *not* the # case, this function raises a :class:`StopIteration` exception. # ''' # items_iter = getattr(mapping, '__beartype_items_iter', None) # if items_iter is None: # #FIXME: This should probably be a weak reference to prevent # #unwanted reference cycles and hence memory leaks. # #FIXME: We need to protect this both here and below with a # #"try: ... except Exception: ..." block, where the body of the # #"except Exception:" condition should probably just return # #"beartype._util.utilobject.SENTINEL", as the only type hints # #that would ever satisfy are type hints *ALL* objects satisfy # #(e.g., "Any", "object"). # mapping.__beartype_items_iter = iter(mapping.items()) # try: # return next(mapping.__beartype_items_iter) # # If we get to the end (i.e., the prior call to next() raises a # # "StopIteration" exception) *OR* anything else happens (i.e., the # # prior call to next() raises a "RuntimeError" exception due to the # # underlying mapping having since been externally mutated), just # # start over. :p # except Exception: # mapping.__beartype_items_iter = None # # # We could also recursively call ourselves: e.g., # # return _get_dict_random_key(mapping) # # However, that would be both inefficient and dangerous. # mapping.__beartype_items_iter = iter(mapping.items()) # return next(mapping.__beartype_items_iter) #* In "beartype._decor._main": # import _get_dict_nonempty_random_key as __beartype_get_dict_nonempty_random_key #* In code generated by this submodule, internally call that helper when # checking keys of non-empty mappings *THAT ARE UNSLOTTED* (for obvious # reasons) ala: # ( # {hint_curr_pith} and # not hasattr({hint_curr_pith}, '__slots__') and # {!INSERT_CHILD_TEST_HERE@?( # {hint_child_pith} := __beartype_get_dict_nonempty_random_key({hint_curr_pith})) # ) # Obviously not quite right, but gives one the general gist of the thing. # #We could get around the slots limitation by using an external LRU cache #mapping from "dict" object ID to items iterator, and maybe that *IS* what we #should do. Actually... *NO.* We absolutely should *NOT* do that sort of thing #anywhere in the codebase, as doing so would guaranteeably induce memory leaks #by preventing "dict" objects cached in that LRU from being garbage collected. # #Note that we basically can't do this under Python < 3.8, due to the lack of #assignment expressions there. Since _get_dict_nonempty_random_key() returns a #new random key each call, we can't repeatedly call that for each child pith #and expect the same random key to be returned. So, Python >= 3.8 only. *shrug* # #Note that the above applies to both immutable mappings (i.e., objects #satisfying "Mapping" but *NOT* "MutableMapping"), which is basically none of #them, and mutable mappings. Why? Because we don't particularly care if the #caller externally modifies the underlying mapping between type-checks, even #though the result is the above call to "next(mapping.__beartype_items_iter)" #raising a "RuntimeError". Who cares? Whenever an exception occurs, we just #restart iteration over from the beginning and carry on. *GOOD 'NUFF.* #FIXME: *YIKES.* So, as expected, the above approach fundamentally fails on #builtin dicts and sets. Why? Because *ALL* builtin types prohibit #monkey-patching, which the above technically is. Instead, we need a #fundamentally different approach. # #That approach is to globally (but thread-safely, obviously) cache *STRONG* #references to iterators over dictionary "ItemsView" objects. Note that we #can't cache weak references, as the garbage collector would almost certainly #immediately dispose of them, entirely defeating the point. Of course, these #references implicitly prevent garbage collection of the underlying #dictionaries, which means we *ALSO* need a means of routinely removing these #references from our global cache when these references are the only remaining #references to the underlying dictionaries. Can we do any of this? We can. # #First, note that we can trivially obtain the number of live references to any #arbitrary object by calling "sys.getrefcount(obj)". Note, however, that the #count returned by this function is mildly non-deterministic. In particular, #off-by-one issues are not merely edge cases but commonplace. Ergo: # # from sys import getrefcount # # def is_obj_nearly_dead(obj: object) -> bool: # ''' # ``True`` only if there only exists one external strong reference to # the passed object. # ''' # # # Note that the integer returned by this getter is intentionally *NOT* # # tested for equality with "1". Why? Because: # # * The "obj" parameter passed to this tester is an ignorable strong # # reference to this object. # # * The "obj" parameter passed to the getrefcount() getter is yet # # another strong reference to this object. # return getrefcount(obj) <= 3 # #Second, note that neither the iterator API nor the "ItemsView" API provide a #public means of obtaining a strong reference to the underlying dictionary. #This means we *MUST* necessarily maintain for each dictionary a 2-tuple #"(mapping, mapping_iter)", where: #* "mapping" is a strong reference to that dictionary. #* "mapping_iter" is an iterator over that dictionary's "ItemsView" object. # #This implies that we want to: #* Define a new "beartype._util.cache.utilcachemapiter" submodule. #* In that submodule: # * Define a new global variable resembling: # # Note that this is unbounded. There's probably no reasonable reason to # # use an LRU-style bounded cache here... or maybe there is for safety to # # avoid exhausting memory. Right. # # # # So, this should obviously be LRU-bounded at some point. Since Python's # # standard @lru decorator is inefficient, we'll need to build that our # # ourselves, which means this is *NOT* an immediate priority. # _MAP_ITER_CACHE = {} # ''' # Mapping from mapping identifiers to 2-tuples # ``(mapping: Mapping, mapping_iter: Iterator)``, # where ``mapping`` is a strong reference to the mapping whose key is that # mapping's identifier and ``mapping_iter`` is an iterator over that # mapping's ``ItemsView`` object. # ''' # * Define a new asynchronous cleanup_cache() function. See the # cleanup_beartype() function defined below for inspiration. #* Extensively unit test that submodule. # #Third, note that this means the above is_obj_nearly_dead() fails to apply to #this edge case. In our case, a cached dictionary is nearly dead if and only if #the following condition applies: # # def is_cached_mapping_nearly_dead(mapping: Mapping) -> bool: # ''' # ``True`` only if there only exists one external strong reference to # the passed mapping internally cached by the :mod:`beartype.beartype` # decorator. # ''' # # # Note that the integer returned by this getter is intentionally *NOT* # # tested for equality with "1". Why? Because ignorable strong # # references to this mapping include: # # * The "mapping" parameter passed to this tester. # # * The "mapping" parameter passed to the getrefcount() getter. # # * This mapping cached by the beartype-specific global container # # caching these mappings. # # * The iterator over this mapping cached by the same container. # return getrefcount(mapping) <= 5 # <--- yikes! # #Fourth, note that there are many different means of routinely removing these #stale references from our global cache (i.e., references that are the only #remaining references to the underlying dictionaries). For example, we could #routinely iterate over our entire cache, find all stale references, and remove #them. This is the brute-force approach. Of course, this approach is both slow #and invites needlessly repeated work across repeated routine iterations. Ergo, #rather than routinely iterating *ALL* cache entries, we instead only want to #routinely inspect a single *RANDOM* cache entry on each scheduled callback of #our cleanup routine. This is the O(1) beartype approach and still eventually #gets us where we want to go (i.e., complete cleanup of all stale references) #with minimal costs. A random walk wins yet again. # #Fifth, note that there are many different means of routinely scheduling work. #We ignore the existence of the GIL throughout the following discussion, both #because we have no choice *AND* because the randomized cleanup we need to #perform on each scheduled callback is an O(1) operation with negligible #constant factors and thus effectively instantaneous rather than CPU- or #IO-bound. The antiquated approach is "threading.Timer". The issue with the #entire "threading" module is that it is implemented with OS-level threads, #which are ludicrously expensive and thus fail to scale. Our usage of the #"threading" module in beartype would impose undue costs on downstream apps by #needlessly consuming a precious thread, preventing apps from doing so. That's #bad. Instead, we *MUST* use coroutines, which are implemented in Python itself #rather than exposed to the OS and thus suffer no such scalability concerns, #declared as either: #* Old-school coroutines via the @asyncio.coroutine decorator. Yielding under # this approach is trivial (and possibly more efficient): e.g., # yield #* New-school coroutines via the builtin "async def" syntax. Yielding under # this approach is non-trivial (and possibly less efficient): e.g., # await asyncio.sleep_ms(0) # #In general, the "async def" approach is strongly favoured by the community. #Note that yielding control in the "async def" approach is somewhat more #cumbersome and possibly less efficient than simply performing a "yield". #Clearly, a bit of research here is warranted. Note this online commentary: # In performance-critical code yield does offer a small advantage. There are # other tricks such as yielding an integer (number of milliseconds to # pause). In the great majority of cases code clarity trumps the small # performance gain achieved by these hacks. In my opinion, of course. # #In either case, we declare an asynchronous coroutine. We then need to schedule #that coroutine with the global event loop (if any). The canonical way of doing #this is to: #* Pass our "async def" function to the asyncio.create_task() function. # Although alternatives exist (e.g., futures), this function is officially # documented as being the preferred approach: # create_task() (added in Python 3.7) is the preferable way for spawning new # tasks. # Of course, note this requires Python >= 3.7. We could care less. *shrug* #* Pass that task to the asyncio.run() function... or something, something. # Clearly, we still need to research how to routinely schedule that task with # "asyncio" rather than running it only once. In theory, that'll be trivial. # #Here's a simple example: # # async def cleanup_beartype(event_loop): # # Disregard how simple this is, it's just for example # s = await asyncio.create_subprocess_exec("ls", loop=event_loop) # # def schedule_beartype_cleanup(): # event_loop = asyncio.get_event_loop() # event_loop.run_until_complete(asyncio.wait_for( # cleanup_beartype(event_loop), 1000)) # #The above example was culled from this StackOverflow post: # https://stackoverflow.com/questions/45010178/how-to-use-asyncio-event-loop-in-library-function #Unlike the asyncio.create_task() approach, that works on Python >= 3.6. #Anyway, extensive research is warranted here. # #Sixthly, note that the schedule_beartype_cleanup() function should be called #only *ONCE* per active Python process by the first call to the @beartype #decorator passed a callable annotated by one or more "dict" or #"typing.Mapping" type hints. We don't pay these costs unless we have to. In #particular, do *NOT* unconditionally call the schedule_beartype_cleanup() #function on the first importation of the "beartype" package. # #Lastly, note there technically exists a trivial alternative to the above #asynchronous approach: the "gc.callbacks" list, which allows us to schedule #arbitrary user-defined standard non-asynchronous callback functions routinely #called by the garbage collector either immediately before or after each #collection. So what's the issue? Simple: end users are free to either #explicitly disable the garbage collector *OR* compile or interpreter their #apps under a non-CPython executable that does not perform garbage collection. #Ergo, this alternative fails to generalize and is thus largely useless. #FIXME: Actually... let's not do the "asyncio" approach -- at least not #initially. Why? The simplest reason is that absolutely no one expects a #low-level decorator to start adding scheduled asynchronous tasks to the global #event loop. The less simple reason is that doing so would probably have #negative side effects to at least one downstream consumer, the likes of which #we could never possibly predict. # #So, what can we do instead? Simple. We do this by: #* If garbage collection is enabled, registering a new cleanup callback with # "gc.callbacks". #* Else, we get creative. First, note that garbage collection is really only # ever disabled in the real world when compiling Python to a lower-level # language (typically, C). Ergo, efficiency isn't nearly as much of a concern # in this currently uncommon edge case. So, here's what we do: # * After the first call to the @beartype decorator passed a callable # annotated by one or more mapping or set type hints, globally set a private # "beartype" boolean -- say, "WAS_HINT_CLEANABLE" -- noting this to have # been the case. # * In the _code_check_params() function generating code type-checking *ALL* # annotated non-ignorable parameters: # * If "WAS_HINT_CLEANABLE" is True, conditionally append code calling our # cleanup routine *AFTER* code type-checking these parameters. While # mildly inefficient, function calls incur considerably less overhead # when compiled away from interpreted Python bytecode. #FIXME: Note that the above scheme by definition *REQUIRES* assignment #expressions and thus Python >= 3.8 for general-purpose O(1) type-checking of #arbitrarily nested dictionaries and sets. Why? Because each time we iterate an #iterator over those data structures we lose access to the previously iterated #value, which means there is *NO* sane means of type-checking nested #dictionaries or sets without assignment expressions. But that's unavoidable #and Python <= 3.7 is the past, so that's largely fine. # #What we can do under Python <= 3.7, however, is the following: #* If the (possibly nested) type hint is of the form # "{checkable}[...,{dict_or_set}[{class},{class}],...]" where # "{checkable}" is an arbitrary parent type hint safely checkable under Python # <= 3.7 (e.g., lists, unions), "{dict_or_set}" is (wait for it) either "dict" # or "set", and "{class}" is an arbitrary type, then that hint *IS* safely # checkable under Python <= 3.7. Note that items (i.e., keys and values) can # both be checked in O(1) time under Python <= 3.7 by just validating the key # and value of a different key-value pair (e.g., by iterating once for the key # and then again for the value). That does have the disadvantage of then # requiring O(n) iteration to raise a human-readable exception if a dictionary # value fails a type-check, but we're largely okay with that. Again, this only # applies to an edge case under obsolete Python versions, so... *shrug* #* Else, a non-fatal warning should be emitted and the portion of that type # hint that *CANNOT* be safely checked under Python <= 3.7 should be ignored. #FIXME: Note that mapping views now provide a "mapping" attribute enabling #direct access of the mapping mapped by that view under Python >= 3.10: # The views returned by dict.keys(), dict.values() and dict.items() now all # have a mapping attribute that gives a types.MappingProxyType object # wrapping the original dictionary. #This means that we do *NOT* need to explicitly cache the "mapping" object #mapped by any cached view under Python >= 3.10, reducing space consumption. #FIXME: *WOOPS.* The "CacheLruStrong" class is absolutely awesome and we'll #absolutely be reusing that for various supplementary purposes across the #codebase (e.g., for perfect O(1) tuple type-checking below). However, this #class sadly doesn't get us where we need to be for full O(1) dictionary and #set type-checking. Why? Two main reasons: #* *ITERATIVE ACCESS.* Our routinely scheduled cleanup function needs to # iteratively or randomly access arbitrary cache items for inspection to # decide whether they need to be harvested or not. #* *VARIABLE OBJECT SIZES.* We gradually realized, given the plethora of # related "FIXME:" comments below, that we'll eventually want to cache a # variety of possible types of objects across different caches -- each cache # caching a unique type of object. This makes less and less sense the more one # considers, however. For example, why have an LRU cache of default size 256 # specific to iterators for a downstream consumer that only passes one # iterator to a single @beartype-decorated callable? # #The solution to both is simple, but not: we define a new derivative #"LRUDuffleCacheStrong" class. The motivation for using the term "duffle" is #that, just like a duffle bag, a duffle cache: #* Provides random access. #* Elegantly stretches to contains a variable number of arbitrary objects of # variable size. # #The "LRUDuffleCacheStrong" class satisfies both concerns by caching to a #maximum *OBJECT SIZE CAPACITY* rather than merely to an *OBJECT NUMBER #CAPACITY.* Whereas the "CacheLruStrong" class treats all cached objects as #having a uniform size of 1, the "LRUDuffleCacheStrong" class instead assigns #each cached object an estimated abstract size (EAS) as a strictly positive #integer intended to reflect its actual transitive in-memory size -- where a #cached object of EAS 1 is likely to be the smallest object in that cache. #While estimating EAS will depend on object type, the following should apply: #* EAS estimators *MUST* run in O(1) time. That is, estimating the abstract # size of an object *MUST* be implementable in constant time with negligible # constant factors. This means that the standard approach of recursively # inspecting the physical in-memory sizes of all objects visitable from the # target object should *NOT* be employed. #* For containers: # * Note that type hints provide us the expected height # "sizeable_height" of any data structure, where "sizeable_height" is # defined as the number of "[" braces in a type hint ignoring those that do # *NOT* connote semantic depth (e.g., "Optional", "Union", "Annotated"). So: # * The "sizeable_height" for a type hint "list[list[list[int]]]" is 3. # * Since any unsubscripted type hint (e.g., "list") is implicitly # subscripted by "[Any]", the "sizeable_height" for the type hints "list" # and "list[int]" is both 1. # * Note also that most containers satisfy the "collections.abc.Sizeable" ABC. # * Given that, we can trivially estimate the EAS "sizeable_bigo_size" of any # type-hinted sizeable object "sizeable" as follows: # sizeable_bigo_size = len(sizeable) ** sizeable_height # Ergo, a list of length 100 type-hinted as "list[list[int]]" has a size of: # sizeable_bigo_size = 100 ** 2 = 10,000 #* For dictionaries, the "sizeable_bigo_size" provided by the equation above # should be multiplied by two to account for the increased space consumption # due to storing key-value pairs. # #Here's then how the "LRUDuffleCacheStrong" class is implemented: #* The "LRUDuffleCacheStrong" class should *NOT* subclass the # "CacheLruStrong" class but copy-and-paste from the latter into the former. # This is both for efficiency and maintainability; it's likely their # implementations will mildly diverge. #* The LRUDuffleCacheStrong.__init__() method should be implemented like this: # def __init__( # self, # bigo_size_max: int, # value_metadata_len: 'Optional[int]' = 0, # ) # assert bigo_size_max > 0 # assert value_metadata_len >= 0 # # # Classify all passed parameters as instance variables. # self._EAS_MAX = bigo_size_max # self._FIXED_LIST_SIZE = value_metadata_len + 2 # # # Initialize all remaining instance variables. # self._bigo_size_cur = 0 # self._iter = None #* Note the above assignment of these new instance variables: # * "_EAS_MAX", the maximum capacity of this LRU cache in EAS units. Note that # this capacity should ideally default to something that *DYNAMICALLY SCALES # WITH THE RAM OF THE LOCAL MACHINE.* Ergo, "_bigo_size_max" should be # significantly larger in a standard desktop system with 32GB RAM than it is # on a Raspberry Pi 2 with 1GB RAM: specifically, 32 times larger. # * "_bigo_size_cur", the current capacity of this LRU cache in EAS units. # * "_FIXED_LIST_SIZE", the number of additional supplementary objects to # be cached with each associated value of this LRU cache. The idea here is # that each key-value pair of this cache is an arbitrary hashable object # (the key) mapping to a "FixedList(size=self._FIXED_LIST_SIZE)" # (the value) whose 0-based indices provide (in order): # 1. The EAS of that object. For completeness, we should also add to the # "sizeable_bigo_size" estimate given above the additional estimated cost # of this "FixedList". Since the length of this "FixedList" is guaranteed # to be exactly "self._value_metadata_len + 2", this then gives a final # EAS of that object as: # sizeable_bigo_size = ( # self._value_metadata_len + 2 + len(sizeable) ** sizeable_height) # 2. A strong reference to the primary object being cached under this key. # For dictionaries and sets, this is an iterator over those dictionaries # and sets. # 3...self._value_metadata_len + 2: Additional supplementary objects to be # cached along with that object. For dictionaries and sets, exactly one # supplementary object must be cached, so this is: # 3. The underlying dictionary or set being iterated over, so we can # lookup the number of existing strong references to that dictionary # or set during cleanup and decide whether to uncache that or not. # * "_iter", an iterator over this dictionary. Yes, we *COULD* implement # random access (e.g., with a linked list or list), but doing so introduces # extreme complications and inefficiencies in both space and time. Instead, # persisting a simple iterator over this dictionary suffices. #* Allow any "LRUDuffleCacheStrong" instance to be trivially incremented # (e.g., during garbage collection cleanup) as an iterator by also defining: # def get_pair_next_or_none( # self, # __dict_len = dict.__len__, # ) -> 'Optional[Tuple[Hashable, FixedList]]': # ''' # Next most recently used key-value pair of this cache if this cache # is non-empty *or* ``None`` otherwise (i.e., if this cache is empty). # # The first call to this method returns the least recently used # key-value pair of this cache. Each successive call returns the next # least recently used key-value pair of this cache until finally # returning the most recently used key-value pair of this cache, at # which time the call following that call rewinds time by again # returning the least recently used key-value pair of this cache. # ''' # # #FIXME: Probably nest this in a "with self._thread_lock:" block. # # # If this cache is empty, return None. # if not __dict_len(self): # return None # # Else, this cache is non-empty. # # # Attempt to... # try: # # Return the next recent key-value pair of this cache. # return self._iter.__next__() # # If doing so raises *ANY* exception, this iterator has become # # desynchronized from this cache. In this case... # # # # Note this implicitly handles the initial edge case in which this # # cache has yet to be iterated (i.e., "self._iter == None"). Since # # this is *ONLY* the case for the first call to this method for the # # entire lifetime of the active Python process, the negligible # # overhead of handling this exception is preferable to violating DRY # # by duplicating this logic with an explicit # # "if self._iter == None:" block. # except: # # Reinitialize this iterator. # self._iter = self.items() # # # Return the least recent such pair. # return self._iter.__next__() #* Refactor the __setitem__() method. Specifically, when caching a new # key-value pair with EAS "bigo_size_item" such that: # while bigo_size_item + self._bigo_size_cur > self._bigo_size_max: # ...we need to iteratively remove the least recently used key-value pair of # this cache (which, yes, technically has O(n) worst-case time, which is # non-ideal, which may be why nobody does this, but that's sort-of okay here, # since we're doing something monstrously productive each iteration by freeing # up critical space and avoiding memory leaks, which seems more than worth the # cost of iteration, especially as we expect O(1) average-case time) until # this cache can fit that pair into itself. Once it does, we: # # Bump the current EAS of this cache by the EAS of this pair. # self._bigo_size_cur += bigo_size_item # Oh, and there's an obvious edge case here: if "bigo_size_item > # self._bigo_size_max", we do *NOT* attempt to do anything with that object. # We don't cache it or an iterator over it. It's too bid. Instead, we just # type-check the first item of that object in O(1) time. *shrug* # #Seems sweet to us. We can store arbitrarily large nested containers in our #duffle cache without exhausting memory, which is actually more than the #brute-force LRU cache can say. We get trivial iteration persistence. We also #avoid a proliferation of different LRU caches, because a single #"LRUDuffleCacheStrong" instance can flexibly store heterogeneous types. #FIXME: *RIGHT.* So, "LRUDuffleCacheStrong" is mostly awesome as defined above. #We'd just like to make a few minor tweaks for improved robustness: # #* Drop the "value_metadata_len" parameter from the # LRUDuffleCacheStrong.__init__() method. We'd prefer to have that parameter # individually passed to each cache_item() call (see below) rather than # globally, as the former enables different types of cached objects to have a # different quantity of metadata cached with those objects. #* Drop the __setitem__() implementation borrow from "CacheLruStrong". Instead, # defer to the existing dict.__setitem__() implementation. Why? Because we # need to pass additional cache-specific parameters to our own # __setitem__()-like non-dunder method, which __setitem__() doesn't support. #* Define a new cache_obj() method resembling CacheLruStrong.__setitem__() but # even more virile and awesome with signature resembling: # def cache_value( # self, # # # Mandatory parameters. # key: 'Hashable', # value: object, # *metadata: object, # # # Optional parameters. # value_height: 'Optional[int]' = 1, # ) -> None: #FIXME: Here's a reasonably clever idea for perfect O(1) tuple type-checking #guaranteed to check all n items of an arbitrary tuple in exactly n calls, with #each subsequent call performing *NO* type-checking by reducing to a noop. How? #Simple! We: #* Augment our existing "CacheLruStrong" data structure to optionally accept a # new initialization-time "value_maker" factory function defaulting to "None". # If non-"None", "CacheLruStrong" will implicitly call that function on each # attempt to access a missing key by assigning the object returned by that # call as the key of a new key-value pair -- or, in other words, by behaving # exactly like "collections.defaultdict". #* Globally define a new "_LRU_CACHE_TUPLE_TO_COUNTER" cache somewhere as an # instance of "CacheLruStrong" whose "value_maker" factory function is # initialized to a lambda function simply returning a new # "collections.Counter" object that starts counting at 0. Since tuples # themselves are hashable and thus permissible for direct use as dictionary # keys, this cache maps from tuples (recently passed to or returned from # @beartype-decorated callables) to either: # * If that tuple has been type-checked to completion, "True" or any other # arbitrary sentinel placeholder, really. "True" is simpler, however, # because the resulting object needs to be accessible from dynamically # generated wrapper functions. # * Else, a counter such that the non-negative integer returned by # "next(counter)" is the 0-based index of the next item of that tuple to be # type-checked. # #Given that low-level infrastructure, the make_func_pith_code() function below #then generates code perfectly type-checking arbitrary tuples in O(1) time that #should ideally resemble (where "__beartype_pith_j" is the current pith #referring to this tuple): # ( # _LRU_CACHE_TUPLE_TO_COUNTER[__beartype_pith_j] is True or # {INSERT_CHILD_TYPE_CHECK_HERE}( # __beartype_pith_k := __beartype_pith_j[ # next(_LRU_CACHE_TUPLE_TO_COUNTER[__beartype_pith_j])] # ) # ) # #Awesome, eh? The same concept trivially generalizes to immutable sequences #(i.e., "Sequence" type hints that are *NOT* "MutableSequence" type hints). #Sadly, since many users use "Sequence" to interchangeably denote both #immutable and mutable sequences, we probably have no means of reliably #distinguishing the two. So it goes! So, just tuples then in practice. *sigh* #FIXME: Huzzah! We finally invented a reasonably clever means of (more or less) #safely type-checking one-shot iterables like generators and iterators in O(1) #time without destroying those iterables. Yes, doing so requires proxying those #iterables with iterables of our own. Yes, this is non-ideal but not nearly as #bad as you might think. Why? Because *NO ONE CARES ABOUT ONE-SHOT ITERABLES.* #They're one-shot. By definition, you can't really care about them, because #they don't last long enough. You certainly can't cache them or stash them in #data structures or really do anything with them beside pass or return them #between callables until they inevitably get exhausted. # #This means that proxying one-shot iterables is almost always safe. Moreover, #we devised a clever means of proxying that introduces negligible overhead #while preserving our O(1) guarantee. First, let's examine the standard #brute-force approach to proxying one-shot iterables: # # class BeartypeIteratorProxy(object): # def __init__(self, iterator: 'Iterator') -> None: # self._iterator = iterator # # def __next__(self) -> object: # item_next = next(self._iterator) # # if not {INSERT_TYPE_CHECKS_HERE}(item_next): # raise SomeBeartypeException(f'Iterator {item_next} bad!') # # return item_next # #That's bad, because that's an O(n) type-check run on every single iteration. #Instead, we do this: # # class BeartypeIteratorProxy(object): # def __init__(self, iterator: 'Iterator') -> None: # self._iterator = iterator # # def __next__(self) -> object: # # Here is where the magic happens, folks. # self.__next__ = self._iterator.__next__ # # item_next = self.__next__(self._iterator) # # if not {INSERT_TYPE_CHECKS_HERE}(item_next): # raise SomeBeartypeException(f'Iterator {item_next} bad!') # # return item_next # #See what we did there? We dynamically monkey-patch away the #BeartypeIteratorProxy.__next__() method by replacing that method with the #underlying __next__() method of the proxied iterator immediately after #type-checking one and only one item of that iterator. # #The devil, of course, is in that details. Assuming a method can monkey-patch #itself away (we're pretty sure it can, as that's the basis of most efficient #decorators that cache property method results, *BUT WE SHOULD ABSOLUTELY #VERIFY THAT THIS IS THE CASE), the trick is then to gracefully handle #reentrancy. That is to say, although we have technically monkey-patched away #the BeartypeIteratorProxy.__next__() method, that object is still a live #object that *WILL BE RECREATED ON EACH CALL TO THE SAME* @beartype-decorated #callable. Yikes! So, clearly we yet again cache with an "CacheLruStrong" cache #specific to iterators... or perhaps something like "CacheLruStrong" that #provides a callback mechanism to enable arbitrary objects to remove themselves #from the cache. Yes! Perhaps just augment our existing "CacheLruStrong" strong #with some sort of callback or hook support? # #In any case, the idea here is that the "BeartypeIteratorProxy" class defined #above should internally: #* Store a weak rather than strong reference to the underlying iterator. #* Register a callback with that weak reference such that: # * When the underlying iterator is garbage-collected, the wrapping # "BeartypeIteratorProxy" proxy removes itself from its "CacheLruStrong" # proxy. # #Of course, we're still not quite done yet. Why? Because we want to avoid #unnecessarily wrapping "BeartypeIteratorProxy" instances in #"BeartypeIteratorProxy" instances. This will happen whenever such an instance #is passed to a @beartype-decorated callable annotated as accepting or #returning an iterator. How can we avoid that? Simple. Whenever we detect that #an iterator to be type-checked is already a "BeartypeIteratorProxy" instance, #we just efficiently restore the __next__() method of that instance to its #pre-monkey-patched version: e.g., # ( # isinstance(__beartype_pith_n, BeartypeIteratorProxy) and # # Unsure if this sort of assignment expression hack actually works. # # It probably doesn't. So, this may need to be sealed away into a # # utility function performing the same operation. *shrug* # __beartype_pith_n.__next__ = BeartypeIteratorProxy.__next__ # ) #FIXME: Huzzah! The prior commentary on type-checking iterators in O(1) time #also generalizes to most of the other non-trivial objects we had no idea how #to type-check -- notably, callables. How? Simple. *WE PROXY CALLABLES WITH #OBJECTS WHOSE* __call__() methods: #* Type-check parameters to be passed to the underlying callable. #* Call the underlying callable. #* Type-check the return value. #* Monkey-patch themselves away by replacing themselves (i.e., the __call__() # methods of that object) with the underlying callable. The only issue here, # and it might be a deal-breaker, is whether or not a bound method can simply # be replaced with either an unbound function *OR* a bound method of another # object entirely. Maybe it can? We suspect it can in both cases, but research # will certainly be required here. # #Again, cache such objects to avoid reentrancy issues. That said, there is a #significant complication here that one-shot iterables do *NOT* suffer: #proxying. Unlike one-shot iterables, callables are often expected to retain #their object identities. Proxying disrupts that. I still believe that we #should enable proxying across the board by default despite that, because less #than 1% of our users will manually enable an option enabling proxying, simply #because they'll never think to go look for it and when they do find it will be #understandably hesitant to enable it when everything else is working. Users #(including myself) typically only enable options when they encounter issues #requiring they do so. Ergo, proxy by default. But we *ABSOLUTELY* need to #allow users to conditionally disable proxying on a per-decoration basis -- #especially when proxying callables. # #So we propose adding a new optional "is_proxying" parameter to the @beartype #decorator. Unfortunately, doing so in an efficient manner will prove highly #non-trivial. Why? Because the standard approach of doing so is *PROBABLY* #extremely inefficient. We need to test that hypothesis, of course, but the #standard approach to adding optional parameters to decorators is to nest a #closure in a closure in a function. We don't need the innermost closure, of #course, because we dynamically generate it at runtime. We would need the #outermost closure, though, to support optional decorator parameters under the #standard approach. That seems outrageously expensive, because each call to the #@beartype decorator would then internally generate and return a new closure! #Yikes. We can avoid that by instead, on each @beartype call: #* Create a new functools.partial()-based wrapper decorator passed our # @beartype decorator and all options passed to the current @beartype call. #* Cache that wrapper decorator into a new private "CacheLruStrong" instance. #* Return that decorator. #* Return the previously cached wrapper decorator on the next @beartype call # passed the same options (rather than recreating that decorator). # #Naturally, this requires these options to be hashable. Certainly, booleans #are, so this smart approach supports a new optional "is_proxying" parameter. #FIXME: Note that the above approach should only be employed as a last-ditch #fallback in the event that the passed callable both: #* Lacks a non-None "__annotations__" dictionary. #* Is *not* annotated by the third-party optional "typeshed" dependency. # #If the passed callable satisfies either of those two constraints, the existing #type hints annotating that callable should be trivially inspected instead in #O(1) time (e.g., by just performing a brute-force dictionary comparison from #that callable's "__annotations__" dictionary to a dictionary that we #internally construct and cache based on the type hints annotating the #currently decorated callable, except that doesn't quite work because the #"__annotations__" dictionary maps from parameter and return names whereas the #"typing.Callable" and "collections.abc.Callable" syntax omits those names, #which begs the question of how the latter syntax handles positional versus #keyword arguments anyway)... *OR SOMETHING.* # #Fascinatingly, "Callable" syntax supports *NO* distinction between mandatory, #optional, positional, or keyword arguments, because PEP 484 gonna PEP 484: # "There is no syntax to indicate optional or keyword arguments; such # function types are rarely used as callback types." # #Note that mapping from the return type hint given by "typing.Callable" syntax #into the "__annotations__" dictionary is trivial, because the return is always #unconditionally named "return" in that dictionary. So, we then just have to #resolve how to ignore parameter names. Actually, handling mandatory positional #parameters (i.e., positional parameters lacking defaults) on the passed #callable should also be trivial, because they *MUST* strictly correspond to #the first n child type hints of the first argument of the expected parent #"typing.Callable" type hint. It's optional positional parameters and keyword #arguments that are the rub. *shrug* # #Obviously, we'll want to dynamically generate the full test based on the #expected parent "typing.Callable" type hint. For sanity, do this iteratively #by generating code testing arbitrary "__annotations__" against a "Callable" #type hint (in increasing order of complexity): #* Passed *NO* parameters and returning something typed. #* Passed *ONE* parameter and returning something typed. #* Passed *TWO* parameters and returning something typed. #* Passed an arbitrary number of parameters and returning something typed. # #Note that test should ideally avoid iteration. We're fairly certain we can do #that by mapping various attributes from the code object of the passed callable #into something that enables us to produce a tuple of type hints matching the #first argument of the expected parent "Callable" type hint. # #*BINGO!* The value of the "func.__code__.co_varnames" attribute is a tuple of #both parameter names *AND* local variables. Fortunately, the parameter names #come first. Unfortunately, there are two types: standard and keyword-only. #Altogether, an expression yielding a tuple of the names of all parameters #(excluding local variables) is given by: # # #FIXME: Insufficient. Variadic parameters also exist. Also, note that this # #has already been efficiently implemented as get_func_arg_names()! # func_codeobj = get_func_unwrapped_codeobj(func) # # # Tuple of the names of all parameters accepted by this callable. # func_param_names = func_codeobj.co_varnames[ # :func_codeobj.co_argcount + func_codeobj.co_kwonlyargcount] # #Note that "func_param_names" probably excludes variadic positional and keyword #argument names, but that's probably fine, because "Callable" type hint syntax #doesn't appear to explicitly support that sort of thing anyway. I mean, how #would it? Probably using the "..." singleton ellipse object, I'm sure. But #that's completely undefined, so it seems doubtful anyone's actually doing it. # #We then need to use that tuple to slice "func.__annotations__". Of course, you #can't slice a dictionary in Python, because Python dictionaries are much less #useful than they should be. See also: # https://stackoverflow.com/questions/29216889/slicing-a-dictionary # #The simplest and fastest approach we can currently think of is given by: # func_param_name_to_hint = func.__annotations__ # # # Generator comprehension producing type hints for this callable's # # parameters in the same order expected by the first argument of the # # "Callable" type hint. # func_param_hints = ( # func_param_name_to_hint[func_param_name] # for func_param_name in func_param_names # ) # #Note that because we know the exact number of expected parameters up front #(i.e., as the len() of the first argument of the "Callable" type hint), we can #generate optimal code *WITHOUT* a generator or other comprehension and thus #*WITHOUT* iteration. Yes, this is literally loop unrolling in Python, which is #both hilarious and exactly what CPython developers get for failing to support #generally useful operations on dictionaries and sets: e.g., # # callable_type_hint = ... # Give this a name for reference below. # # # Number of non-variadic parameters expected to be accepted by this # # caller-passed callable. # FUNC_PARAM_LEN_EXPECTED = len(callable_type_hint[0]) # # # Generator comprehension producing type hints for this callable's # # parameters in the same order expected by the first argument of the # # "Callable" type hint. # func_param_hints = ( # func_param_name_to_hint[func_param_names[0]], # func_param_name_to_hint[func_param_names[1]], # ... # func_param_name_to_hint[func_param_names[FUNC_PARAM_LEN_EXPECTED]], # ) # #Clearly, there's *LOADS* of additional preliminary validation that needs to #happen here as well. Since "Callable" type hint syntax *REQUIRES* a return #type hint to be specified (yes, this is absolutely non-optional), we also need #to ensure that "func_param_name_to_hint" contains the 'return' key. # #Given all that, the final test would then resemble something like: # # ( # __beartype_pith_n_func_param_name_to_hint := ( # func.__annotations__ or LOOKUP_IN_TYPESHED_SOMEHOW) and # 'return' in __beartype_pith_n_func_param_name_to_hint and # __beartype_pith_n_func_codeobj := getattr( # __beartype_pith_n, '__code__', None) and # # Just ignore C-based callables and assume they're valid. Unsure what # # else we can do with them. Okay, we could also proxy them here, but # # that seems a bit lame. Just accept them as is for now, perhaps? # __beartype_pith_n_func_codeobj is None or ( # __beartype_pith_n_func_param_names := ( # __beartype_pith_n_func_codeobj.co_varnames) and # len(__beartype_pith_n_func_param_names) == {FUNC_PARAM_LEN_EXPECTED} and # ( # __beartype_pith_n_func_param_name_to_hint[__beartype_pith_n_func_param_names[0]], # __beartype_pith_n_func_param_name_to_hint[__beartype_pith_n_func_param_names[1]], # ... # __beartype_pith_n_func_param_name_to_hint[__beartype_pith_n_func_param_names[FUNC_PARAM_LEN_EXPECTED]], # __beartype_pith_n_func_param_name_to_hint['return'] # ) == {callable_type_hint} # ) # ) # #*YUP.* That's super hot, that is. We're sweating. # #Note this test is effectively O(1) but really O(FUNC_PARAM_LEN_EXPECTED) where #FUNC_PARAM_LEN_EXPECTED is sufficiently small that it's basically O(1). That #said, the constant factors are non-negligible. Fortunately, callables *NEVER* #change once declared. You should now be thinking what we're thinking: #*CACHING*. That's right. Just stuff the results of the above test (i.e., a #boolean) into our duffel LRU cache keyed on the fully-qualified name of that #callable. We only want to pay the above price once per callable, if we can #help it, which we absolutely can, so let's do that please. # #*NOTE THAT ASSIGNMENT EXPRESSIONS ARE EFFECTIVELY MANDATORY.* I mean, there's #basically no way we can avoid them, so let's just require them. By the time we #get here anyway, Python 3.6 will be obsolete, which just leaves Python 3.7. We #could just emit warnings when decorating callables annotated by "Callable" #type hints under Python 3.7. # #*NOTE THAT BUILTINS DO NOT HAVE CODE OBJECTS,* complicating matters. At this #point, we could care less, but we'll have to care sometime that is not now. #FIXME: *OH.* Note that things are slightly less trivial than detailed above. #It's not enough for a callable to be annotated, of course; that callable also #needs to be annotated *AND* type-checked by a runtime type checker like #@beartype or @typeguard. The same, of course, does *NOT* apply to "typeshed" #annotations, because we generally expect stdlib callables to do exactly what #they say and nothing more or less. This means the above approach should only #be employed as a last-ditch fallback in the event that the passed callable #does *NOT* satisfy any of the following: #* Is decorated by a runtime type checker *AND* has a non-None # "__annotations__" dictionary. #* Is annotated by the third-party optional "typeshed" dependency. # #Trivial, but worth noting. #FIXME: Lastly, note that everywhere we say "typeshed" above, we *REALLY* mean #a PEP 561-compliant search for stub files annotating that callable. #Unsurprisingly, the search algorithm is non-trivial, which will impact the #performance gains associated with type-checking annotations in the first #place. Ergo, we might consider omitting aspects of this search that are both #highly inefficient *AND* unlikely to yield positive hits. See also: # https://www.python.org/dev/peps/pep-0561/ #FIXME: *IT'S CONFIGURATION TIME.* So, let's talk about how we efficiently #handle @beartype configuration like the "is_proxying" boolean introduced #above. It's worth getting this right the first time. Happily, we got this #right the first time with a balls-crazy scheme that gives us O(1) #configurability that supports global defaults that can be both trivially #changed globally *AND* overridden by passed optional @beartype parameters. # #Note this scheme does *NOT* require us to litter the codebase with cumbersome #and inefficient logic like: # muh_setting = ( # beartype_params.muh_setting if beartype_params.muh_setting is not None else # beartype._global_config.muh_setting) # #What is this magic we speak of? *SIMPLE.* We twist class variable MRO lookup #in our favour. Since CPython already efficiently implements such lookup with a #fast C implementation, we can hijack that implementation for our own sordid #purposes to do something completely different. Note that only *CLASS* variable #MRO lookup suffices. Since classes are global singletons, all subclasses will #implicitly perform efficient lookups for undefined class variables in their #superclass -- which is exactly what we want and need here. # #Specifically: #* Define a new private "beartype._config" submodule. #* In that submodule: # * Define a new public "BeartypeConfigGlobal" class declaring all # configuration settings as class variables defaulting to their desired # arbitrary global defaults: e.g., # class BeartypeConfigGlobal(object): # ''' # **Global beartype configuration.** # ''' # # is_proxying = True # ... #* Publicly expose that class to external users as a new public # "beartype.config" *PSEUDO-MODULE.* In reality, that object will simply be an # alias of "beartype._config.BeartypeConfigGlobal". But users shouldn't know # that. They should just treat that object as if it was a module. To effect # this, just establish this alias in the "beartype.__init__" submodule: e.g., # from beartype._config import BeartypeConfigGlobal # # # It really is that simple, folks. Maybe. Gods, let it be that simple. # config = BeartypeConfigGlobal #* Privatize the existing public "beartype._decor.decormain" submodule to a new # "beartype._decor._template" submodule. #* In that submodule: # * Rename the existing @beartype decorator to beartype_template(). That # function will now only be called internally rather than externally. #* Define a new private "beartype._decor.decorcache" submodule. #* In that submodule: # * Define a new "BEARTYPE_PARAMS_TO_DECOR" dictionary mapping from a *TUPLE* # of positional arguments listed in the exact same order as the optional # parameters accepted by the new @beartype decorator discussed below to # subclasses to dynamically generated @beartype decorators configured by # those subclasses. This tuple should just literally be the argument tuple # passed to the @beartype decorator, which is probably easiest to achieve if # we force @beartype parameters to be passed as keyword-only arguments: # # # Keyword-only arguments require Python >= 3.8. Under older Pythons, # # just drop the "*". Under older Pythons, let's just *NOT ALLOW # # CONFIGURATION AT ALL.* So, this gives us: # if IS_PYTHON_AT_LEAST_3_8: # def beartype(*, is_proxying: bool = None, ...) -> Callable: # BEARTYPE_PARAMS = (is_proxying, ...) # # beartype_decor = BEARTYPE_PARAMS_TO_DECOR.get(BEARTYPE_PARAMS) # if beartype_decor: # return beartype_decor # # # Else, we need to make a new @beartype decorator passed # # these parameters, cache that decorator in # # "BEARTYPE_PARAMS_TO_DECOR", and return that decorator. # else: # # Probably not quite right, but close enough. # beartype = beartype_template # # We need a hashable tuple for lookup purposes. That's *ABSOLUTELY* the # fastest way, given that we expect keyword arguments. So, we're moving on. # Also, do *NOT* bother with LRU caching here, as the expected size of that # dictionary will almost certainly always be less than 10 and surely 100. #* Define a new private "beartype._decor.decormain" submodule. #* In that submodule: # * Define a new @beartype decorator accepting *ALL* of the *EXACT* same # class variables declared by the "BeartypeConfigGlobal" class as optional # parameters of the same name but *UNCONDITIONALLY* defaulting to "None". # That last bit is critical. Do *NOT* default them to what the # "BeartypeConfigGlobal" superclass defaults them to, as that would obstruct # our purposes, which is to have lookups punted upward to the # "BeartypeConfigGlobal" superclass only when undefined in a subclass. # * The purpose of this new @beartype decorator is to (in order): # * First lookup the passed parameters to get an existing decorator passed # those parameters, as already implemented above. (This is trivial.) # * If we need to make a new decorator, this is also mostly trivial. Just: # * Define a new local dictionary "BEARTYPE_PARAM_NAME_TO_VALUE" bundling # these optional parameters for efficient lookup: e.g., # BEARTYPE_PARAM_NAME_TO_VALUE = { # 'is_proxying': is_proxying, # ... # } # * Dynamically create a new "BeartypeConfigGlobal" subclass *SETTING THE # DESIRED CLASS VARIABLES* based on all of the passed optional # parameters whose values are *NOT* "None". For example, if the only # passed non-"None" optional parameter was "is_proxying", this would be: # class _BeartypeConfigDecor{ARBITRARY_NUMBER}(BeartypeConfigGlobal): # is_proxying = False # This will probably require a bit of iteration to filter out all # non-"None" optional parameters. Note that the simplest way to # implement this would probably be to just dynamically declare an empty # subclass and then monkey-patch that subclass' dictionary with the # desired non-"None" optional parameters: e.g., # # Pseudo-code, but close enough. # BeartypeConfigDecor = eval( # f'''class _BeartypeConfigDecor{ARBITRARY_NUMBER}(BeartypeConfigGlobal): pass''') # # # Yes, this is a bit lame, but it suffices for now. Remember, # # we're caching this class, so the logic constructing this class # # doesn't need to be lightning fast. It's *FAR* more critical that # # the logic looking up this class in this class be lightning fast. # # # # Do *NOT* try to embed this logic into the above evaluation # # (e.g., as f-expressions). Yes, that sort of hackery is trivial # # with booleans but rapidly gets hairy with containers. So, I # # *GUESS* we could do that for booleans. Just remember that that # # doesn't generalize to the general case. Actually, don't bother. # # The following suffices and doesn't violate DRY, which is the # # only important thing here. # BeartypeConfigDecor.__dict__.update({ # arg_name: arg_value # arg_name, arg_value in BEARTYPE_PARAM_NAME_TO_VALUE.items() # if arg_value is not None # }) # * Dynamically *COPY* the beartype_template() function into a new # function specific to that subclass, which means that function is # actually just a template. We'll never actually the original function # itself; we just use that function as the basis for dynamically # generating new decorators on-the-fly. Heh! Fortunately, we only need # a shallow function copy. That is to say, we want the code objects to # remain the same. Note that the most efficient means of implementing # this is given directly be this StackOverflow answer: # https://stackoverflow.com/a/13503277/2809027 # Note that that answer can be slightly improved to resemble: # WRAPPER_ASSIGNMENTS = functools.WRAPPER_ASSIGNMENTS + ('__kwdefaults__',) # def copy_func(f): # g = types.FunctionType(f.__code__, f.__globals__, name=f.__name__, # argdefs=f.__defaults__, # closure=f.__closure__) # g = functools.update_wrapper(g, f, WRAPPER_ASSIGNMENTS) # return g # That's the most general form. Of course, we don't particularly care # about copying metadata, since we don't expect anyone to care about # these dynamically generated decorators. That means we can reduce the # above to simply: # def copy_func(f): # return types.FunctionType( # f.__code__, # f.__globals__, # name=f.__name__, # argdefs=f.__defaults__, # closure=f.__closure__, # ) # * Monkey-patch the new decorator returned by # "copy_func(beartype_template)" with the new subclass: e.g., # beartype_decor = copy_func(beartype_template) # beartype_decor.__beartype_config = BeartypeConfigDecor # *HMMM.* Minor snag. That doesn't work, but the beartype_template() # template won't have access to that "__beartype_config". Instead, we'll # need to: # * Augment the signature of the beartype_template() template to accept # a new optional "config" parameter default to "None": e.g.,. # def beartype_template( # func: Callable, config: BeartypeConfigGlobal = None) -> Callable: # * Either refactor the copy_func() function defined above to accept a # caller-defined "argdefs" parameter *OR* (more reasonably) just # inline the body of that function in @beartype as: # beartype_decor = types.FunctionType( # f.__code__, # f.__globals__, # name=f.__name__, # # Yup. In theory, that should do it, if we recall the internal # # data structure of this parameter correctly. # argdefs=(BeartypeConfigDecor,), # closure=f.__closure__, # ) # * Cache and return that decorator: # BEARTYPE_PARAMS_TO_DECOR[BEARTYPE_PARAMS] = beartype_decor # return beartype_decor # #Pretty trivial, honestly. We've basically already implemented all of the hard #stuff above, which is nice. # #Note that the beartype_template() function will now accept an optional #"config" parameter -- which will, of course, *ALWAYS* be non-"None" by the #logic above. Assert this, of course. We can then trivially expose that #"config" to lower-level beartype functions by just stuffing it into the #existing "BeartypeCall" class: e.g., # # Welp, that was trivial. # func_data.config = config # #Since we pass "func_data" everywhere, we get configuration for free. Muhaha! #FIXME: Propagate generic subscriptions both to *AND* from pseudo-superclasses. #First, consider the simpler case of propagating a generic subscription to #pseudo-superclasses: e.g., # from typing import List # class MuhList(List): pass # # @beartype # def muh_lister(muh_list: MuhList[int]) -> None: pass # #During internal type hint visitation, @beartype should propagate the "int" #child type hint subscripting the "MuhList" type hint up to the "List" #pseudo-superclass under Python >= 3.9. Under older Python versions, leaving #"List" unsubscripted appears to raise exceptions at parse time. *shrug* # #Of the two cases, this first case is *SIGNIFICANTLY* more important than the #second case documented below. Why? Because mypy (probably) supports this first #but *NOT* second case, for which mypy explicitly raises an "error". Since #mypy has effectively defined the standard interpretation of type hints, #there's little profit in contravening that ad-hoc standard by supporting #something unsupported under mypy -- especially because doing so would then #expose end user codebases to mypy errors. Sure, that's "not our problem, man," #but it kind of is, because community standards exist for a reason -- even if #they're ad-hoc community standards we politely disagree with. # #Nonetheless, here's the second case. Consider the reverse case of propagating #a generic subscription from a pseudo-superclass down to its unsubscripted #generic: e.g., # from typing import Generic, TypeVar # # T = TypeVar('T') # class MuhGeneric(Generic[T]): # def __init__(self, muh_param: T): pass # # @beartype # def muh_genericizer(generic: MuhGeneric, T) -> None: pass # #During internal type hint visitation, @beartype should propagate the "T" #child type hint subscripting the "Generic" pseudo-superclass down to the #"MuhGeneric" type hint under Python >= 3.9 and possibly older versions. Doing #so would reduce DRY violations, because there's no tangible reason why users #should have to perpetually subscript "MuhGeneric" when its pseudo-superclass #already has been. Of course, mypy doesn't see it that way. *shrug* #FIXME: When time permits, we can augment the pretty lame approach by #publishing our own "BeartypeDict" class that supports efficient random access #of both keys and values. Note that: #* The existing third-party "randomdict" package provides baseline logic that # *MIGHT* be useful in getting "BeartypeDict" off the ground. The issue with # "randomdict", however, is that it internally leverages a "list", which # probably then constrains key-value pair deletions on the exterior # "randomdict" object to an O(n) rather than O(1) operation, which is # absolutely unacceptable. #* StackOverflow questions provide a number of solutions that appear to be # entirely O(1), but which require maintaining considerably more internal data # structures, which is also unacceptable (albeit less so), due to increased # space consumption that probably grows unacceptable fast and thus fails to # generally scale. #* Since we don't control "typing", we'll also need to augment "BeartypeDict" # with a "__class_getitem__" dunder method (or whatever that is called) to # enable that class to be subscripted with "typing"-style types ala: # def muh_func(muh_mapping: BeartypeDict[str, int]) -> None: pass #In short, we'll need to conduct considerably more research here. #FIXME: Actually, none of the above is necessary or desirable. Rather than #designing a random access "BeartypeDict" class, it would be *FAR* more useful #to design a series of beartype-specific container types in a new external #"beartypes" package, each of which performs O(1) type-checking *ON INSERTION #OF EACH CONTAINER ITEM.* This should be stupidly fast under standard use #cases, because we typically expect an item to be inserted only once but #accessed many, many times. By just checking on insertion, we avoid *ALL* of #the complications of trying to type-check after the fact during sequential #non-random iteration over items. # #Indeed, there appears to be a number of similar projects with the same idea, #with the caveat that these projects *ALL* leverage package-specific constructs #rather than PEP-compliant type hints -- a significant negative. The most #interesting of these are: #* "typed_python", a fascinating package with a variety of novel ideas at play. # In addition to providing package-specific container types that perform # PEP-noncompliant type-checking on item insertion *IMPLEMENTED THAT AT THE C # LEVEL* rather than in pure Python (which is both horrible and fascinating, # mainly because... why bother? I mean, PyPy, Nuitka, and Cython already # exist, so why go to all that trouble to work in C rather than Python?), # this package also offers: # * "typed_python.Entrypoint", which looks balls-cray-cray. This is probably # the most interesting aspect of this package, presuming it actually behaves # as advertised, which it almost certainly doesn't. Nonetheless, it appears # to be a bit of a cross between Nuitka and beartype. To quote: # "Simply stick the @typed_python.Entrypoint decorator around any function # that uses "typed_python" primitives to get a fast version of it: # @Entrypoint # def sum(someList, zero): # for x in someList: # zero += x # return x # ...will generate specialized code for different data types # ("ListOf(int)", say, or "ListOf(float)", or even "Dict(int)") that's not # only many times faster than the python equivalent, but that can operate # using multiple processors. Compilation occurs each time you call the # method with a new combination of types." The "that can operate using # multiple processors" part is particularly novel, as it implies # circumvention of the GIL. "typed_python" appears to implement this magic # by leveraging LLVM to compile Python down to C. Again, we strongly doubt # any of this actually works under real-world industrial constraints, but # it's still a fascinating thought experiment. # * "type_python.Class", a generic-style class one subclasses to generate # "strongly typed class with a packed memory layout." The "strongly typed" # part isn't terribly interesting, as it's PEP-noncompliant. The "packed # memory layout" part, however, *IS* interesting. Reducing space consumption # by presumably compiling to C is intriguing, if tangential to our concerns. beartype-0.18.5/beartype/_check/code/codecls.py000066400000000000000000000364611461113517100213610ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **type-checking code classes** (i.e., low-level classes storing metadata describing each iteration of the breadth-first search (BFS) dynamically generating pure-Python code snippets type-checking arbitrary objects against PEP-compliant type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... # All "FIXME:" comments for this submodule reside in this package's "__init__" # submodule to improve maintainability and readability here. # ....................{ IMPORTS }.................... from beartype.typing import ( TYPE_CHECKING, ) from beartype._check.code.snip.codesnipstr import ( CODE_HINT_CHILD_PLACEHOLDER_PREFIX, CODE_HINT_CHILD_PLACEHOLDER_SUFFIX, ) from beartype._util.cache.pool.utilcachepoollistfixed import ( FIXED_LIST_SIZE_MEDIUM, FixedList, ) # ....................{ CLASSES }.................... #FIXME: Unit test us up, please. class HintsMeta(FixedList): ''' **Type-checking metadata list** (i.e., low-level fixed list of all metadata describing all visitable hints currently discovered by the breadth-first search (BFS) dynamically generating pure-Python code snippets type-checking arbitrary objects against type hints). This list acts as a standard First In First Out (FILO) queue, enabling this BFS to be implemented as an efficient imperative algorithm rather than an inefficient -- and dangerous, due to both unavoidable stack exhaustion and avoidable infinite recursion -- recursive algorithm. Note that this list is guaranteed by the previously called ``_die_if_hint_repr_exceeds_child_limit()`` function to be larger than the number of hints transitively visitable from this root hint. Ergo, *all* indexation into this list performed by this BFS is guaranteed to be safe. Attributes ---------- index_last : int 0-based index of metadata describing the last visitable hint in this list. For efficiency, this integer also uniquely identifies the current child type hint of the currently visited parent type hint. ''' # ..................{ CLASS VARIABLES }.................. # Slot all instance variables defined on this object to minimize the time # complexity of both reading and writing variables across frequently # called @beartype decorations. Slotting has been shown to reduce read and # write costs by approximately ~10%, which is non-trivial. __slots__ = ( 'index_last', ) # Squelch false negatives from mypy. This is absurd. This is mypy. See: # https://github.com/python/mypy/issues/5941 if TYPE_CHECKING: index_last: int # ..................{ INITIALIZERS }.................. def __init__(self) -> None: ''' Initialize this type-checking metadata list. ''' # Initialize our superclass. super().__init__( size=FIXED_LIST_SIZE_MEDIUM, #FIXME: *NO*. We actually need these to be independent copies. Ergo, #there's *NO* alternative but to iteratively define one new instance #of this dataclass for each index of this list. Indeed, this is #actually a profound benefit. How? We can precompute the values of #* "hint_curr_placeholder" *AT PYTHON STARTUP*. Just iteratively # assign each "hint_curr_placeholder" according to its index. Do so # based on the current logic of the enqueue_hint_child() method. #* "pith_curr_var_name" *AT PYTHON STARTUP* in the exact same way. # #Indeed, this suggests we probably no longer need either: #* "pith_curr_var_name_index". #* The entire "codesnipcls" submodule. # #Well, isn't this turning out to be a significant facepalm. #FIXME: Actually, the default of "None" here is fine. Let's instead: #* Redefine the __getitem__() dunder method to dynamically inspect # the item at the passed index. If: # * "None", then replace this item with a new "HintMeta" instance # suitable for the passed index. # * Else, return the existing "HintMeta" instance at this index. #* Refactor the enqueue_hint_child() method to then reassign the # fields of this "HintMeta" instance to the desired values. # #This approach avoids expensive up-front computation at app startup, #instead amortizing these costs across the app lifetime. Heh. # obj_init=HintMeta(), ) # 0-based index of metadata describing the last visitable hint in this # list, initialized to "-1" to ensure that the initial incrementation of # this index by the enqueue_hint_child() method initializes index 0 of # this list. self.index_last = 0 # ..................{ METHODS }.................. def enqueue_hint_child(self, pith_child_expr: str) -> str: ''' **Enqueue** (i.e., append) a new tuple of metadata describing the currently iterated child type hint to the end of the ``hints_meta`` queue, enabling this hint to be visited by the ongoing breadth-first search (BFS) traversing over this queue. Parameters ---------- pith_child_expr : str Python code snippet evaluating to the child pith to be type-checked against the currently iterated child type hint. This closure also implicitly expects the following local variables of the outer scope to be set to relevant values: hint_child : object Currently iterated child type hint subscripting the currently visited type hint. Returns ------- str Placeholder string to be subsequently replaced by code type-checking this child pith against this child type hint. ''' # print(f'pith_child_expr: {pith_child_expr}') # Increment the 0-based index of metadata describing the last visitable # hint in this list (which also serves as the unique identifier of the # currently iterated child hint) *BEFORE* overwriting the existing # metadata at this index. # # Note this index is guaranteed to *NOT* exceed the fixed length of this # list, by prior validation. self.index_last += 1 # Placeholder string to be globally replaced by code type-checking the # child pith against this child hint, intentionally prefixed and # suffixed by characters that: # # * Are intentionally invalid as Python code, guaranteeing that the # top-level call to the exec() builtin performed by the @beartype # decorator will raise a "SyntaxError" exception if the caller fails # to replace all placeholder substrings generated by this method. # * Protect the identifier embedded in this substring against ambiguous # global replacements of larger identifiers containing this # identifier. If this identifier were *NOT* protected in this manner, # then the first substring "0" generated by this method would # ambiguously overlap with the subsequent substring "10" generated by # this method, which would then produce catastrophically erroneous and # undebuggable Python code. hint_child_placeholder = ( f'{CODE_HINT_CHILD_PLACEHOLDER_PREFIX}' f'{str(self.index_last)}' f'{CODE_HINT_CHILD_PLACEHOLDER_SUFFIX}' ) #FIXME: Implement us up, please. This will prove non-trivial. *sigh* # # Create and insert a new tuple of metadata describing this child hint # # at this index of this list. # # # # Note that this assignment is guaranteed to be safe, as # # "FIXED_LIST_SIZE_MEDIUM" is guaranteed to be substantially larger than # # "hints_meta_index_last". # self[self.index_last] = ( # hint_child, # hint_child_placeholder, # pith_child_expr, # pith_curr_var_name_index, # indent_level_child, # ) # Return this placeholder string. return hint_child_placeholder #FIXME: Unit test us up, please. class HintMeta(object): ''' **Type-checking metadata dataclass** (i.e., low-level class storing metadata describing each iteration of the breadth-first search (BFS) dynamically generating pure-Python code snippets type-checking arbitrary objects against type hints). Attributes ---------- hint_curr : object Type hint currently visited by this BFS. hint_curr_placeholder : str **Type-checking placeholder substring** to be globally replaced in the **returned Python code snippet** (i.e., the ``func_wrapper_code`` local) by a Python code snippet type-checking the **current pith expression** (i.e., the ``pith_curr_var_name`` local) against the currently visited hint (i.e., the :attr:`hint_curr` instance variable). This substring provides indirection enabling the currently visited parent hint to defer and delegate the generation of code type-checking each child argument of that hint to the later time at which that child argument is visited. Example ------- For example, the :func:`beartype._check.code.codemake.make_check_expr` factory might generate intermediary code resembling the following on visiting the :obj:`typing.Union` parent type hint of a subscripted ``Union[int, str]`` type hint *before* visiting either the :class:`int` or :class:`str` child type hints of that parent type hint: .. code-block:: python if not ( @{0}! or @{1}! ): raise get_func_pith_violation( func=__beartype_func, pith_name=$%PITH_ROOT_NAME/~, pith_value=__beartype_pith_root, ) Note the unique substrings ``"@{0}!"`` and ``"@{1}!"`` in that code, which that factory iteratively replaces with code type-checking each of the child type hints (e.g., :class:`int`, :class:`str`) subscripting that :obj:`typing.Union` parent type hint. The final code memoized by that factory might then resemble: .. code-block:: python if not ( isinstance(__beartype_pith_root, int) or isinstance(__beartype_pith_root, str) ): raise get_func_pith_violation( func=__beartype_func, pith_name=$%PITH_ROOT_NAME/~, pith_value=__beartype_pith_root, ) pith_curr_expr : str **Pith expression** (i.e., Python code snippet evaluating to the value of) the current **pith** (i.e., possibly nested object of the passed parameter or return to be type-checked against the currently visited type hint). Note that this expression is intentionally *not* an assignment expression but rather the original inefficient expression provided by the parent type hint of the currently visited type hint. pith_curr_var_name_index : int **Pith variable name index** (i.e., 0-based integer suffixing the name of each local variable assigned the value of the current pith in a assignment expression, thus uniquifying this variable in the body of the current wrapper function). Indexing the :obj:`beartype._check.code.snip.codesnipcls.PITH_INDEX_TO_VAR_NAME` dictionary singleton by this integer efficiently yields the current **pith variable name** locally storing the value of the current pith. Note that this integer is intentionally incremented as an efficient low-level scalar rather than as an inefficient high-level :class:`itertools.Counter` object. Since both are equally thread-safe in the internal context of a function body, the former is preferable. indent_level_curr : int **Indentation level** (i.e., 1-based positive integer providing the current level of indentation appropriate for the currently visited type hint). Indexing the :obj:`beartype._data.code.datacodeindent.INDENT_LEVEL_TO_CODE` dictionary singleton by this integer efficiently yields the current **indendation string** suitable for prefixing each line of code type-checking the current pith against the current type hint. ''' # ..................{ CLASS VARIABLES }.................. # Slot all instance variables defined on this object to minimize the time # complexity of both reading and writing variables across frequently called # cache dunder methods. Slotting has been shown to reduce read and write # costs by approximately ~10%, which is non-trivial. __slots__ = ( 'hint_curr', 'hint_curr_placeholder', 'pith_curr_expr', 'pith_curr_var_name_index', 'indent_level_curr', ) # Squelch false negatives from mypy. This is absurd. This is mypy. See: # https://github.com/python/mypy/issues/5941 if TYPE_CHECKING: hint_curr: object hint_curr_placeholder: str pith_curr_expr: str pith_curr_var_name_index: int indent_level_curr: int # ..................{ INITIALIZERS }.................. def __init__( self, # Mandatory parameters. hint_curr_placeholder: str, pith_curr_var_name_index: int, # Optional parameters. hint_curr: object = None, pith_curr_expr: str = '', indent_level_curr: int = 2, ) -> None: ''' Initialize this type-checking metadata dataclass. Parameters ---------- hint_curr : object Type hint currently visited by this BFS. hint_curr_placeholder : str Type-checking placeholder substring. See the class docstring. pith_curr_expr : str Pith expression. See the class docstring. pith_curr_var_name_index : int Pith variable name index. See the class docstring. indent_level_curr : int Indentation level. See the class docstring. ''' assert isinstance(hint_curr_placeholder, str) assert isinstance(pith_curr_expr, str) assert isinstance(pith_curr_var_name_index, int) assert isinstance(indent_level_curr, int) assert hint_curr_placeholder assert pith_curr_expr assert pith_curr_var_name_index >= 0 assert indent_level_curr > 1 # Classify all passed parameters. self.hint_curr = hint_curr self.hint_curr_placeholder = hint_curr_placeholder self.pith_curr_expr = pith_curr_expr self.pith_curr_var_name_index = pith_curr_var_name_index self.indent_level_curr = indent_level_curr beartype-0.18.5/beartype/_check/code/codemagic.py000066400000000000000000000140341461113517100216500ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype decorator **type-checking expression magic** (i.e., global string constants embedded in the implementations of boolean expressions type-checking arbitrary objects against arbitrary PEP-compliant type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._data.error.dataerrmagic import EXCEPTION_PLACEHOLDER from itertools import count # ....................{ EXCEPTION }.................... EXCEPTION_PREFIX_FUNC_WRAPPER_LOCAL = ( f'{EXCEPTION_PLACEHOLDER}wrapper parameter ') ''' Human-readable substring describing a new wrapper parameter required by the current root type hint in exception messages. ''' EXCEPTION_PREFIX_HINT = f'{EXCEPTION_PLACEHOLDER}type hint ' ''' Human-readable substring describing the current root type hint generically (i.e., agnostic of the specific PEP standard to which this hint conforms) in exception messages. ''' #FIXME: Preserved for documentation purposes. When time permits, centralized #this documentation into the docstring of a new "HintMeta" dataclass, please. # # ....................{ HINT ~ meta }.................... # # Iterator yielding the next integer incrementation starting at 0, to be safely # # deleted *AFTER* defining the following 0-based indices via this iterator. # __hint_meta_index_counter = count(start=0, step=1) # # # HINT_META_INDEX_HINT = next(__hint_meta_index_counter) # ''' # 0-based index into each tuple of hint metadata providing the currently # visited hint. # # For both space and time efficiency, this metadata is intentionally stored as # 0-based integer indices of an unnamed tuple rather than: # # * Human-readable fields of a named tuple, which incurs space and time costs we # would rather *not* pay. # * 0-based integer indices of a tiny fixed list. Previously, this metadata was # actually stored as a fixed list. However, exhaustive profiling demonstrated # that reinitializing each such list by slice-assigning that list's items from # a tuple to be faster than individually assigning these items: # # .. code-block:: shell-session # # $ echo 'Slice w/ tuple:' && command python3 -m timeit -s \ # 'muh_list = ["a", "b", "c", "d",]' \ # 'muh_list[:] = ("e", "f", "g", "h",)' # Slice w/ tuple: # 2000000 loops, best of 5: 131 nsec per loop # $ echo 'Slice w/o tuple:' && command python3 -m timeit -s \ # 'muh_list = ["a", "b", "c", "d",]' \ # 'muh_list[:] = "e", "f", "g", "h"' # Slice w/o tuple: # 2000000 loops, best of 5: 138 nsec per loop # $ echo 'Separate:' && command python3 -m timeit -s \ # 'muh_list = ["a", "b", "c", "d",]' \ # 'muh_list[0] = "e" # muh_list[1] = "f" # muh_list[2] = "g" # muh_list[3] = "h"' # Separate: # 2000000 loops, best of 5: 199 nsec per loop # # So, not only does there exist no performance benefit to flattened fixed lists, # there exists demonstrable performance costs. # ''' # # # HINT_META_INDEX_PLACEHOLDER = next(__hint_meta_index_counter) # ''' # 0-based index into each tuple of hint metadata providing the **current # placeholder type-checking substring** (i.e., placeholder to be globally # replaced by a Python code snippet type-checking the current pith expression # against the hint described by this metadata on visiting that hint). # # This substring provides indirection enabling the currently visited parent hint # to defer and delegate the generation of code type-checking each child argument # of that hint to the later time at which that child argument is visited. # # Example # ------- # For example, the # :func:`beartype._decor._hint._pep._pephint.make_func_pith_code` function might # generate intermediary code resembling the following on visiting the # :data:`Union` parent of a ``Union[int, str]`` object *before* visiting either # the :class:`int` or :class:`str` children of that object: # # if not ( # @{0}! or # @{1}! # ): # raise get_func_pith_violation( # func=__beartype_func, # pith_name=$%PITH_ROOT_NAME/~, # pith_value=__beartype_pith_root, # ) # # Note the unique substrings ``"@{0}!"`` and ``"@{1}!"`` in that code, which that # function iteratively replaces with code type-checking each of the child # arguments of that :data:`Union` parent (i.e., :class:`int`, :class:`str`). The # final code memoized by that function might then resemble: # # if not ( # isinstance(__beartype_pith_root, int) or # isinstance(__beartype_pith_root, str) # ): # raise get_func_pith_violation( # func=__beartype_func, # pith_name=$%PITH_ROOT_NAME/~, # pith_value=__beartype_pith_root, # ) # ''' # # # HINT_META_INDEX_PITH_EXPR = next(__hint_meta_index_counter) # ''' # 0-based index into each tuple of hint metadata providing the **current # pith expression** (i.e., Python code snippet evaluating to the current possibly # nested object of the passed parameter or return value to be type-checked # against the currently visited hint). # ''' # # # HINT_META_INDEX_PITH_VAR_NAME = next(__hint_meta_index_counter) # ''' # 0-based index into each tuple of hint metadata providing the **current pith # variable name** (i.e., name of the unique local variable assigned the value of # the current pith either by a prior assignment statement or expression). # ''' # # # HINT_META_INDEX_INDENT_LEVEL = next(__hint_meta_index_counter) # ''' # 0-based index into each tuple of hint metadata providing the **current # indentation level** (i.e., 1-based positive integer describing the current level # of indentation appropriate for the currently visited hint). # ''' # # # # Delete the above counter for safety and sanity in equal measure. # del __hint_meta_index_counter beartype-0.18.5/beartype/_check/code/codemake.py000066400000000000000000003664111461113517100215160ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **type-checking code factories** (i.e., low-level callables dynamically generating pure-Python code snippets type-checking arbitrary objects against PEP-compliant type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... # All "FIXME:" comments for this submodule reside in this package's "__init__" # submodule to improve maintainability and readability here. # ....................{ IMPORTS }.................... from beartype.roar import ( BeartypeDecorHintPepException, BeartypeDecorHintPepUnsupportedException, BeartypeDecorHintPep593Exception, ) from beartype.typing import Optional from beartype._cave._cavefast import TestableTypes from beartype._check.checkmagic import ( ARG_NAME_CLS_STACK, ARG_NAME_GETRANDBITS, VAR_NAME_PITH_ROOT, ) from beartype._check.code.codemagic import ( EXCEPTION_PREFIX_FUNC_WRAPPER_LOCAL, EXCEPTION_PREFIX_HINT, ) from beartype._check.code.codescope import ( add_func_scope_type, add_func_scope_types, add_func_scope_type_or_types, express_func_scope_type_ref, ) from beartype._check.code.snip.codesnipcls import PITH_INDEX_TO_VAR_NAME from beartype._check.code.snip.codesnipstr import ( CODE_HINT_CHILD_PLACEHOLDER_PREFIX, CODE_HINT_CHILD_PLACEHOLDER_SUFFIX, CODE_PEP484_INSTANCE_format, CODE_PEP484585_GENERIC_CHILD_format, CODE_PEP484585_GENERIC_PREFIX, CODE_PEP484585_GENERIC_SUFFIX, CODE_PEP484585_MAPPING_format, CODE_PEP484585_MAPPING_KEY_ONLY_format, CODE_PEP484585_MAPPING_KEY_VALUE_format, CODE_PEP484585_MAPPING_VALUE_ONLY_format, CODE_PEP484585_MAPPING_KEY_ONLY_PITH_CHILD_EXPR_format, CODE_PEP484585_MAPPING_VALUE_ONLY_PITH_CHILD_EXPR_format, CODE_PEP484585_MAPPING_KEY_VALUE_PITH_CHILD_EXPR_format, CODE_PEP484585_SEQUENCE_ARGS_1_format, CODE_PEP484585_SEQUENCE_ARGS_1_PITH_CHILD_EXPR_format, CODE_PEP484585_SUBCLASS_format, CODE_PEP484585_TUPLE_FIXED_EMPTY_format, CODE_PEP484585_TUPLE_FIXED_LEN_format, CODE_PEP484585_TUPLE_FIXED_NONEMPTY_CHILD_format, CODE_PEP484585_TUPLE_FIXED_NONEMPTY_PITH_CHILD_EXPR_format, CODE_PEP484585_TUPLE_FIXED_PREFIX, CODE_PEP484585_TUPLE_FIXED_SUFFIX, CODE_PEP484604_UNION_CHILD_PEP_format, CODE_PEP484604_UNION_CHILD_NONPEP_format, CODE_PEP484604_UNION_PREFIX, CODE_PEP484604_UNION_SUFFIX, CODE_PEP572_PITH_ASSIGN_EXPR_format, CODE_PEP586_LITERAL_format, CODE_PEP586_PREFIX_format, CODE_PEP586_SUFFIX, CODE_PEP593_VALIDATOR_IS_format, CODE_PEP593_VALIDATOR_METAHINT_format, CODE_PEP593_VALIDATOR_PREFIX, CODE_PEP593_VALIDATOR_SUFFIX_format, ) from beartype._check.convert.convsanify import ( sanify_hint_child_if_unignorable_or_none, sanify_hint_child, ) from beartype._conf.confcls import BeartypeConf from beartype._data.code.datacodeindent import INDENT_LEVEL_TO_CODE from beartype._data.code.datacodemagic import ( LINE_RSTRIP_INDEX_AND, LINE_RSTRIP_INDEX_OR, ) from beartype._data.error.dataerrmagic import ( EXCEPTION_PLACEHOLDER as EXCEPTION_PREFIX) from beartype._data.hint.datahinttyping import ( CodeGenerated, LexicalScope, TypeStack, ) from beartype._data.hint.pep.sign.datapepsigns import ( HintSignAnnotated, HintSignForwardRef, HintSignGeneric, HintSignLiteral, # HintSignNone, HintSignTuple, HintSignType, ) from beartype._data.hint.pep.sign.datapepsignset import ( HINT_SIGNS_MAPPING, HINT_SIGNS_ORIGIN_ISINSTANCEABLE, HINT_SIGNS_SEQUENCE_ARGS_1, HINT_SIGNS_SUPPORTED_DEEP, HINT_SIGNS_UNION, ) from beartype._util.cache.utilcachecall import callable_cached from beartype._util.cache.pool.utilcachepoollistfixed import ( FIXED_LIST_SIZE_MEDIUM, acquire_fixed_list, release_fixed_list, ) from beartype._util.cache.pool.utilcachepoolobjecttyped import ( acquire_object_typed, release_object_typed, ) from beartype._util.func.utilfuncscope import add_func_scope_attr from beartype._util.hint.pep.proposal.pep484585.utilpep484585 import ( is_hint_pep484585_tuple_empty) from beartype._util.hint.pep.proposal.pep484585.utilpep484585 import ( get_hint_pep484585_args) from beartype._util.hint.pep.proposal.pep484585.utilpep484585generic import ( get_hint_pep484585_generic_type, iter_hint_pep484585_generic_bases_unerased_tree, ) from beartype._util.hint.pep.proposal.pep484585.utilpep484585type import ( get_hint_pep484585_type_superclass) from beartype._util.hint.pep.proposal.utilpep586 import ( get_hint_pep586_literals) from beartype._util.hint.pep.proposal.utilpep593 import ( get_hint_pep593_metadata, get_hint_pep593_metahint, ) from beartype._util.hint.pep.utilpepget import ( get_hint_pep_args, get_hint_pep_sign, get_hint_pep_sign_or_none, get_hint_pep_origin_type_isinstanceable, ) from beartype._util.hint.pep.utilpeptest import ( die_if_hint_pep_unsupported, is_hint_pep, is_hint_pep_args, ) from beartype._util.hint.utilhinttest import is_hint_ignorable from beartype._util.kind.map.utilmapset import update_mapping from beartype._util.text.utiltextmunge import replace_str_substrs from beartype._util.text.utiltextrepr import represent_object from random import getrandbits # ....................{ MAKERS }.................... @callable_cached def make_check_expr( # ..................{ ARGS ~ mandatory }.................. hint: object, conf: BeartypeConf, # ..................{ ARGS ~ optional }.................. cls_stack: TypeStack = None, ) -> CodeGenerated: ''' **Type-checking expression factory** (i.e., low-level callable dynamically generating a pure-Python boolean expression type-checking an arbitrary object against the passed PEP-compliant type hint). This code factory performs a breadth-first search (BFS) over the abstract graph of nested type hints reachable from the subscripted arguments of the passed root type hint. For each such (possibly nested) hint, this factory embeds one or more boolean subexpressions validating a (possibly nested sub)object of an arbitrary object against that hint into the full boolean expression created and returned by this factory. In short, this factory is the beating heart of :mod:`beartype`. We applaud you for your perseverance. You finally found the essence of the Great Bear. You did it!! Now, we clap. This code factory is memoized for efficiency. Caveats ------- **This factory intentionally accepts no** ``exception_prefix`` **parameter.** Why? Since that parameter is typically specific to the context-sensitive use case of the caller, accepting that parameter would prevent this factory from memoizing the passed hint with the returned code, which would rather defeat the point. Instead, this factory only: * Returns generic non-working code containing the placeholder :data:`VAR_NAME_PITH_ROOT` substring that the caller is required to globally replace by either the name of the current parameter *or* ``return`` for return values (e.g., by calling the builtin :meth:`str.replace` method) to generate the desired non-generic working code type-checking that parameter or return value. * Raises generic non-human-readable exceptions containing the placeholder :attr:`beartype._util.error.utilerrraise.EXCEPTION_PLACEHOLDER` substring that the caller is required to explicitly catch and raise non-generic human-readable exceptions from by calling the :func:`beartype._util.error.utilerrraise.reraise_exception_placeholder` function. Parameters ---------- hint : object PEP-compliant type hint to be type-checked. conf : BeartypeConf **Beartype configuration** (i.e., self-caching dataclass encapsulating all settings configuring type-checking for the passed object). cls_stack : TypeStack, optional **Type stack** (i.e., either a tuple of the one or more :func:`beartype.beartype`-decorated classes lexically containing the class variable or method annotated by this hint *or* :data:`None`). Defaults to :data:`None`. Returns ------- CodeGenerated Tuple containing the Python code snippet dynamically generated by this code generator and metadata describing that code. See the :attr:`beartype._data.hint.datahinttyping.CodeGenerated` type hint for details. Raises ------ BeartypeDecorHintPepException If this object is *not* a PEP-compliant type hint. BeartypeDecorHintPepUnsupportedException If this object is a PEP-compliant type hint currently unsupported by the :func:`beartype.beartype` decorator. BeartypeDecorHintPep484Exception If one or more PEP-compliant type hints visitable from this object are nested :attr:`typing.NoReturn` child hints, since :attr:`typing.NoReturn` is valid *only* as a non-nested return hint. BeartypeDecorHintPep593Exception If one or more PEP-compliant type hints visitable from this object subscript the :pep:`593`-compliant :class:`typing.Annotated` class such that: * The second argument subscripting that class is an instance of the :class:`beartype.vale.Is` class. * One or more further arguments subscripting that class are *not* instances of the :class:`beartype.vale.Is` class. Warns ----- BeartypeDecorHintPep585DeprecationWarning If one or more :pep:`484`-compliant type hints visitable from this object have been deprecated by :pep:`585`. ''' # ..................{ LOCALS ~ hint : root }.................. # Top-level hint relocalized for disambiguity. hint_root = hint # Delete the passed parameter whose name is ambiguous within the context of # this function for similar disambiguity. del hint # ..................{ LOCALS ~ hint : current }.................. # Currently visited hint. hint_curr = None # Current unsubscripted typing attribute associated with this hint (e.g., # "Union" if "hint_curr == Union[int, str]"). hint_curr_sign = None # Python expression evaluating to an isinstanceable type (e.g., origin type) # associated with the currently visited type hint if any. hint_curr_expr = None # Placeholder string to be globally replaced in the Python code snippet to # be returned (i.e., "func_wrapper_code") by a Python code snippet # type-checking the current pith expression (i.e., # "pith_curr_var_name") against the currently visited hint (i.e., # "hint_curr"). hint_curr_placeholder = None # Full Python expression evaluating to the value of the current pith (i.e., # possibly nested object of the passed parameter or return value to be # type-checked against the currently visited hint). # # Note that this is intentionally *NOT* an assignment expression but rather # the original inefficient expression provided by the parent type hint of # the currently visited hint. pith_curr_expr = None # Name of the current pith variable (i.e., local Python variable in the # body of the wrapper function whose value is that of the current pith). # This name is either: # * Initially, the name of the currently type-checked parameter or return. # * On subsequently type-checking nested items of the parameter or return, # the name of the local variable uniquely assigned to by the assignment # expression defined by "pith_curr_assign_expr" (i.e., the left-hand side # (LHS) of that assignment expression). pith_curr_var_name = VAR_NAME_PITH_ROOT # ..................{ LOCALS ~ hint : child }.................. # Currently iterated PEP-compliant child hint subscripting the currently # visited hint, initialized to the root hint to enable the subsequently # called _enqueue_hint_child() function to enqueue the root hint. hint_child = hint_root # Current unsubscripted typing attribute associated with this child hint # (e.g., "Union" if "hint_child == Union[int, str]"). hint_child_sign = None # ..................{ LOCALS ~ hint : childs }.................. # Current tuple of all child hints subscripting the currently visited hint # (e.g., "(int, str)" if "hint_curr == Union[int, str]"). hint_childs: tuple = None # type: ignore[assignment] # Current list of all output child hints to replace the Current tuple of all # input child hints subscripting the currently visited hint with. hint_childs_new: list = None # type: ignore[assignment] # Number of child hints subscripting the currently visited hint. hint_childs_len: int = None # type: ignore[assignment] # 0-based index of the currently iterated child hint of the "hint_childs" # tuple. hint_childs_index: int = None # type: ignore[assignment] # Current tuple of all child child hints subscripting the currently visited # child hint (e.g., "(int, str)" if "hint_child == Union[int, str]"). hint_child_childs: tuple = None # type: ignore[assignment] # ..................{ LOCALS ~ hint : metadata }.................. # Tuple of metadata describing the currently visited hint, appended by # the previously visited parent hint to the "hints_meta" stack. hint_curr_meta: tuple = None # type: ignore[assignment] # Fixed list of all metadata describing all visitable hints currently # discovered by the breadth-first search (BFS) below. This list acts as a # standard First In First Out (FILO) queue, enabling this BFS to be # implemented as an efficient imperative algorithm rather than an # inefficient (and dangerous, due to both unavoidable stack exhaustion and # avoidable infinite recursion) recursive algorithm. # # Note that this list is guaranteed by the previously called # _die_if_hint_repr_exceeds_child_limit() function to be larger than the # number of hints transitively visitable from this root hint. Ergo, *ALL* # indexation into this list performed by this BFS is guaranteed to be safe. # Ergo, avoid explicitly testing below that the "hints_meta_index_last" # integer maintained by this BFS is strictly less than # "FIXED_LIST_SIZE_MEDIUM", as this constraint is already guaranteed to be # the case. hints_meta = acquire_fixed_list(FIXED_LIST_SIZE_MEDIUM) # 0-based index of metadata describing the currently visited hint in the # "hints_meta" list. hints_meta_index_curr = 0 # 0-based index of metadata describing the last visitable hint in the # "hints_meta" list, initialized to "-1" to ensure that the initial # incrementation of this index by the _enqueue_hint_child() directly called # below initializes index 0 of the "hints_meta" fixed list. # # For efficiency, this integer also uniquely identifies the currently # iterated child type hint of the currently visited parent type hint. hints_meta_index_last = -1 # ..................{ LOCALS ~ func : code }.................. # Python code snippet type-checking the current pith against the currently # visited hint (to be appended to the "func_wrapper_code" string). func_curr_code: str = None # type: ignore[assignment] # ..................{ LOCALS ~ func : code : locals }.................. # Local scope (i.e., dictionary mapping from the name to value of each # attribute referenced in the signature) of this wrapper function required # by this Python code snippet. func_wrapper_scope: LexicalScope = {} # True only if one or more PEP-compliant type hints visitable from this # root hint require a pseudo-random integer. If true, the higher-level # beartype._decor.wrap.wrapmain.generate_code() function prefixes the body # of this wrapper function with code generating such an integer. is_var_random_int_needed = False # ..................{ LOCALS ~ indentation }.................. # Python code snippet expanding to the current level of indentation # appropriate for the currently visited hint. indent_curr: str = None # type: ignore[assignment] # 1-based indentation level describing the current level of indentation # appropriate for the currently visited hint. indent_level_curr = 2 # 1-based indentation level describing the current level of indentation # appropriate for the currently iterated child hint, initialized to the # root hint indentation level to enable the subsequently called # _enqueue_hint_child() function to enqueue the root hint. indent_level_child = indent_level_curr # ..................{ LOCALS ~ pep : 484 }.................. # Set of the unqualified classnames referred to by all relative forward # references visitable from this root hint if any *OR* "None" otherwise # (i.e., if no such forward references are visitable). hint_refs_type_basename: Optional[set] = None # ..................{ LOCALS ~ pep : 572 }.................. # The following local variables isolated to this subsection are only # relevant when the currently visited hint is *NOT* the root hint (i.e., # "hint_root"). If the currently visited hint is the root hint, the current # pith has already been localized to a local variable whose name is the # value of the "VAR_NAME_PITH_ROOT" string global and thus need *NOT* be # relocalized to another local variable using an assignment expression. # # These variables enable a non-trivial runtime optimization eliminating # repeated computations to obtain the child pith needed to type-check child # hints. For example, if the current hint constrains the current pith to be # a standard sequence, the child pith of that parent pith is a random item # selected from this sequence; since obtaining this child pith is # non-trivial, the computation required to do so is performed only once by # assigning this child pith to a unique local variable during type-checking # and then repeatedly type-checking that variable rather than the logic # required to continually reacquire this child pith: e.g., # # # Type-checking conditional for "List[List[str]]" under Python < 3.8. # if not ( # isinstance(__beartype_pith_0, list) and # ( # isinstance(__beartype_pith_0[__beartype_random_int % len(__beartype_pith_0)], list) and # isinstance(__beartype_pith_0[__beartype_random_int % len(__beartype_pith_0)][__beartype_random_int % len(__beartype_pith_0[__beartype_random_int % len(__beartype_pith_0)])], str) if __beartype_pith_0[__beartype_random_int % len(__beartype_pith_0)] else True # ) if __beartype_pith_0 else True # ): # # # The same conditional under Python >= 3.8. # if not ( # isinstance(__beartype_pith_0, list) and # ( # isinstance(__beartype_pith_1 := __beartype_pith_0[__beartype_random_int % len(__beartype_pith_0)], list) and # isinstance(__beartype_pith_1[__beartype_random_int % len(__beartype_pith_1)], str) if __beartype_pith_1 else True # ) if __beartype_pith_0 else True # ): # # Note that: # * The random item selected from the root pith (i.e., "__beartype_pith_1 # := __beartype_pith_0[__beartype_random_int % len(__beartype_pith_0)") # only occurs once under Python >= 3.8 but repeatedly under Python < 3.8. # In both cases, the same semantic type-checking is performed regardless # of optimization. # * This optimization implicitly "bottoms out" when the currently visited # hint is *NOT* subscripted by unignorable child hints. If all child hints # of the currently visited hint are either ignorable (e.g., "object", # "Any") *OR* are unignorable isinstanceable types (e.g., "int", "str"), # the currently visited hint has *NO* meaningful child hints and is thus # effectively a leaf node with respect to performing this optimization. # Integer suffixing the name of each local variable assigned the value of # the current pith in a assignment expression, thus uniquifying this # variable in the body of the current wrapper function. # # Note that this integer is intentionally incremented as an efficient # low-level scalar rather than as an inefficient high-level # "itertools.Counter" object. Since both are equally thread-safe in the # internal context of this function body, the former is preferable. pith_curr_var_name_index = 0 # Assignment expression assigning this full Python expression to the unique # local variable assigned the value of this expression. pith_curr_assign_expr: str = None # type: ignore[assignment] # ..................{ CLOSURES }.................. # Closures centralizing frequently repeated logic, addressing Don't Repeat # Yourself (DRY) concerns during the breadth-first search (BFS) below. def _enqueue_hint_child(pith_child_expr: str) -> str: ''' **Enqueue** (i.e., append) a new tuple of metadata describing the currently iterated child type hint to the end of the ``hints_meta`` queue, enabling this hint to be visited by the ongoing breadth-first search (BFS) traversing over this queue. Parameters ---------- pith_child_expr : str Python code snippet evaluating to the child pith to be type-checked against the currently iterated child type hint. This closure also implicitly expects the following local variables of the outer scope to be set to relevant values: hint_child : object Currently iterated child type hint subscripting the currently visited type hint. Returns ------- str Placeholder string to be subsequently replaced by code type-checking this child pith against this child type hint. ''' # print(f'pith_child_expr: {pith_child_expr}') # Allow these local variables of the outer scope to be modified below. nonlocal hints_meta_index_last # Increment both the 0-based index of metadata describing the last # visitable hint in the "hints_meta" list and the unique identifier of # the currently iterated child hint *BEFORE* overwriting the existing # metadata at this index. # # Note this index is guaranteed to *NOT* exceed the fixed length of # this list, by prior validation. hints_meta_index_last += 1 # Placeholder string to be globally replaced by code type-checking the # child pith against this child hint, intentionally prefixed and # suffixed by characters that: # # * Are intentionally invalid as Python code, guaranteeing that the # top-level call to the exec() builtin performed by the @beartype # decorator will raise a "SyntaxError" exception if the caller fails # to replace all placeholder substrings generated by this method. # * Protect the identifier embedded in this substring against ambiguous # global replacements of larger identifiers containing this # identifier. If this identifier were *NOT* protected in this manner, # then the first substring "0" generated by this method would # ambiguously overlap with the subsequent substring "10" generated by # this method, which would then produce catastrophically erroneous # and undebuggable Python code. hint_child_placeholder = ( f'{CODE_HINT_CHILD_PLACEHOLDER_PREFIX}' f'{str(hints_meta_index_last)}' f'{CODE_HINT_CHILD_PLACEHOLDER_SUFFIX}' ) # Create and insert a new tuple of metadata describing this child hint # at this index of this list. # # Note that this assignment is guaranteed to be safe, as # "FIXED_LIST_SIZE_MEDIUM" is guaranteed to be substantially larger than # "hints_meta_index_last". hints_meta[hints_meta_index_last] = ( hint_child, hint_child_placeholder, pith_child_expr, pith_curr_var_name_index, indent_level_child, ) # Return this placeholder string. return hint_child_placeholder # ..................{ LOCALS ~ closure }.................. # Local variables calling one or more closures declared above and thus # deferred until after declaring those closures. # Placeholder string to be globally replaced in the Python code snippet to # be returned (i.e., "func_wrapper_code") by a Python code snippet # type-checking the child pith expression (i.e., "pith_child_expr") against # the currently iterated child hint (i.e., "hint_child"), initialized to a # placeholder describing the root hint. hint_child_placeholder = _enqueue_hint_child(VAR_NAME_PITH_ROOT) # Python code snippet type-checking the root pith against the root hint, # localized separately from the "func_wrapper_code" snippet to enable this # function to validate this code to be valid *BEFORE* returning this code. func_root_code = hint_child_placeholder # Python code snippet to be returned, seeded with a placeholder to be # replaced on the first iteration of the breadth-first search performed # below with a snippet type-checking the root pith against the root hint. func_wrapper_code = func_root_code # ..................{ SEARCH }.................. # While the 0-based index of metadata describing the next visited hint in # the "hints_meta" list does *NOT* exceed that describing the last # visitable hint in this list, there remains at least one hint to be # visited in the breadth-first search performed by this iteration. while hints_meta_index_curr <= hints_meta_index_last: # Metadata describing the currently visited hint. hint_curr_meta = hints_meta[hints_meta_index_curr] # Assert this metadata is a tuple as expected. This enables us to # distinguish between proper access of used items and improper access # of unused items of the parent fixed list containing this tuple, since # an unused item of this list is initialized to "None" by default. assert hint_curr_meta.__class__ is tuple, ( f'Current hint metadata {repr(hint_curr_meta)} at ' f'index {hints_meta_index_curr} not tuple.' ) #FIXME: ...heh. It's time, people. Sadly, it turns out that redefining #the _enqueue_hint() closure on *EVERY* call to this function is a huge #time sink -- far huger than anything else, actually. Therefore: #* Define a new "HintMeta" dataclass defining one slotted field for each # of these metadata. #* Refactor the _enqueue_hint() closure into a HintMeta.enqueue_hint() # method. #* Replace all calls to the _enqueue_hint() closure with calls to the # HintMeta.enqueue_hint() method. #* Remove the _enqueue_hint() closure. #* Remove all of the following locals from this function in favour of # the "HintMeta" slotted fields of the same names: # * hint_curr. # * hint_curr_placeholder. # * pith_curr_expr. # * pith_curr_var_name_index. # * indent_level_curr. # Localize metadata for both efficiency and f-string purposes. # # Note that list unpacking is substantially more efficient than # manually indexing list items; the former requires only a single Python # statement, whereas the latter requires "n" Python statements. ( hint_curr, hint_curr_placeholder, pith_curr_expr, pith_curr_var_name_index, indent_level_curr, ) = hint_curr_meta # print(f'Visiting type hint {repr(hint_curr)}...') #FIXME: Comment this sanity check out after we're sufficiently #convinced this algorithm behaves as expected. While useful, this check #requires a linear search over the entire code and is thus costly. # assert hint_curr_placeholder in func_wrapper_code, ( # '{} {!r} placeholder {} not found in wrapper body:\n{}'.format( # hint_curr_exception_prefix, hint, hint_curr_placeholder, func_wrapper_code)) # ................{ PEP }................ # If this hint is PEP-compliant... if is_hint_pep(hint_curr): #FIXME: Refactor to call warn_if_hint_pep_unsupported() instead. #Actually...wait. This is probably still a valid test here. We'll #need to instead augment the is_hint_ignorable() function to #additionally test whether the passed hint is unsupported, in which #case that function should return false as well as emit a non-fatal #warning ala the new warn_if_hint_pep_unsupported() function -- #which should probably simply be removed now. *sigh* #FIXME: Actually, in that case, we can simply reduce the following #two calls to simply: # die_if_hint_pep_ignorable( # hint=hint_curr, exception_prefix=hint_curr_exception_prefix) #Of course, this implies we want to refactor the #die_if_hint_pep_unsupported() function into #die_if_hint_pep_ignorable()... probably. # If this hint is currently unsupported, raise an exception. # # Note the human-readable label prefixing the representations of # child PEP-compliant type hints is unconditionally passed. Since # the root hint has already been validated to be supported by # the above call to the same function, this call is guaranteed to # *NEVER* raise an exception for that hint. die_if_hint_pep_unsupported( hint=hint_curr, exception_prefix=EXCEPTION_PREFIX) # Else, this hint is supported. # Assert that this hint is unignorable. Iteration below generating # code for child hints of the current parent hint is *REQUIRED* to # explicitly ignore ignorable child hints. Since the caller has # explicitly ignored ignorable root hints, these two guarantees # together ensure that all hints visited by this breadth-first # search *SHOULD* be unignorable. Naturally, we validate that here. assert not is_hint_ignorable(hint_curr), ( f'{EXCEPTION_PREFIX}ignorable type hint ' f'{repr(hint_curr)} not ignored.' ) # Sign uniquely identifying this hint. hint_curr_sign = get_hint_pep_sign(hint_curr) # print(f'Visiting PEP type hint {repr(hint_curr)} sign {repr(hint_curr_sign)}...') #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # NOTE: Whenever adding support for (i.e., when generating code # type-checking) a new "typing" attribute below, similar support # for that attribute *MUST* also be added to the parallel: # * "beartype._check.error" subpackage, which raises exceptions on # the current pith failing this check. # * "beartype._data.hint.pep.sign.datapepsignset.HINT_SIGNS_SUPPORTED_DEEP" # frozen set of all signs for which this function generates deeply # type-checking code. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! #FIXME: Python 3.10 provides proper syntactic support for "case" #statements, which should allow us to dramatically optimize this #"if" logic into equivalent "case" logic *AFTER* we drop support #for Python 3.9. Of course, that will be basically never, so we'll #have to preserve this for basically forever. What you gonna do? #FIXME: Actually, we should probably just leverage a hypothetical #"beartype.vale.IsInline[...]" validator to coerce this slow O(n) #procedural logic into fast O(1) object-oriented logic. Of course, #object-oriented logic is itself slow -- so we only do this if we #can sufficiently memoize that logic. Consideration! # Switch on (as in, pretend Python provides a "case" statement) # the sign identifying this hint to decide which type of code to # generate to type-check the current pith against the current hint. # # This decision is intentionally implemented as a linear series of # tests ordered in descending likelihood for efficiency. While # alternative implementations (that are more readily readable and # maintainable) do exist, these alternatives all appear to be # substantially less efficient. # # Consider the standard alternative of sequestering the body of # each test implemented below into either: # # * A discrete private function called by this function. This # approach requires maintaining a global private dictionary # mapping from each support unsubscripted typing attribute to # the function generating code for that attribute: e.g., # def pep_code_check_union(...): ... # _HINT_TYPING_ATTR_ARGLESS_TO_CODER = { # typing.Union: pep_code_check_union, # } # Each iteration of this loop then looks up the function # generating code for the current attribute from this dictionary # and calls that function to do so. Function calls come with # substantial overhead in Python, impacting performance more # than the comparable linear series of tests implemented below. # Additionally, these functions *MUST* mutate local variables of # this function by some arcane means -- either: # * Passing these locals to each such function, returning these # locals from each such function, and assigning these return # values to these locals in this function after each such call. # * Passing a single composite fixed list of these locals to each # such function, which then mutates these locals in-place, # which then necessitates this function permanently store these # locals in such a list rather than as local variables. # * A discrete closure of this function, which adequately resolves # the aforementioned locality issue via the "nonlocal" keyword at # a substantial up-front performance cost of redeclaring these # closures on each invocation of this function. # # ..............{ SHALLOW }.............. # Perform shallow type-checking logic (i.e., logic that does *NOT* # recurse and thus "bottoms out" at this hint) *BEFORE* deep # type-checking logic. The latter needs additional setup (e.g., # generation of assignment expressions) *NOT* needed by the former, # whose requirements are more understandably minimalist. Note that: # * Shallow type-checking code should access this pith via # "pith_curr_expr". Since this code does *NOT* recurse, # "pith_curr_expr" accesses this pith optimally efficiently # * Deep type-checking code should access this pith via # "pith_assign_expr". Since that code *DOES* recurse, only # "pith_assign_expr" accesses this pith optimally efficiently; # "pith_curr_expr" accesses this pith extremely inefficiently. # # ..............{ ORIGIN }.............. # If this hint both... if ( # Originates from an origin type and may thus be shallowly # type-checked against that type *AND is either... hint_curr_sign in HINT_SIGNS_ORIGIN_ISINSTANCEABLE and ( # Unsubscripted *OR*... not is_hint_pep_args(hint_curr) or #FIXME: Remove this branch *AFTER* deeply supporting all #hints. # Currently unsupported with deep type-checking... hint_curr_sign not in HINT_SIGNS_SUPPORTED_DEEP ) ): # Then generate trivial code shallowly type-checking the current # pith as an instance of the origin type originating this sign # (e.g., "list" for the hint "typing.List[int]"). # Code type-checking the current pith against this origin type. func_curr_code = CODE_PEP484_INSTANCE_format( pith_curr_expr=pith_curr_expr, # Python expression evaluating to this origin type. hint_curr_expr=add_func_scope_type( # Origin type of this hint if any *OR* raise an # exception -- which should *NEVER* happen, as this hint # was validated above to be supported. cls=get_hint_pep_origin_type_isinstanceable(hint_curr), func_scope=func_wrapper_scope, exception_prefix=EXCEPTION_PREFIX_FUNC_WRAPPER_LOCAL, ), ) # Else, this hint is either subscripted, not shallowly # type-checkable, *OR* deeply type-checkable. # # ..............{ FORWARDREF }.............. # If this hint is a forward reference... elif hint_curr_sign is HintSignForwardRef: # Render this forward reference accessible to the body of this # wrapper function by populating: # * A Python expression evaluating to the class referred to by # this forward reference when accessed via the private # "__beartypistry" parameter. # * A set of the unqualified classnames referred to by all # relative forward references, including this reference if # relative. If this set was previously uninstantiated (i.e., # "None"), this assignment initializes this local to the new # set instantiated by this call; else, this assignment # preserves this local set as is. hint_curr_expr, hint_refs_type_basename = ( express_func_scope_type_ref( forwardref=hint_curr, forwardrefs_class_basename=hint_refs_type_basename, func_scope=func_wrapper_scope, exception_prefix=EXCEPTION_PREFIX, )) # Code type-checking the current pith against this class. func_curr_code = CODE_PEP484_INSTANCE_format( pith_curr_expr=pith_curr_expr, hint_curr_expr=hint_curr_expr, ) # Else, this hint is *NOT* a forward reference. # # Since this hint is *NOT* shallowly type-checkable, this hint # *MUST* be deeply type-checkable. So, we do so now. # # ..............{ DEEP }.............. # Perform deep type-checking logic (i.e., logic that is guaranteed # to recurse and thus *NOT* "bottom out" at this hint). else: # Tuple of all arguments subscripting this hint if any *OR* the # empty tuple otherwise (e.g., if this hint is its own # unsubscripted "typing" attribute). # # Note that the "__args__" dunder attribute is *NOT* guaranteed # to exist for arbitrary PEP-compliant type hints. Ergo, we # obtain this attribute via a higher-level utility getter # instead. hint_childs = get_hint_pep_args(hint_curr) hint_childs_len = len(hint_childs) # Python code snippet expanding to the current level of # indentation appropriate for the current hint. indent_curr = INDENT_LEVEL_TO_CODE[indent_level_curr] # 1-based indentation level describing the current level of # indentation appropriate for the currently iterated child hint. indent_level_child = indent_level_curr + 1 # ............{ DEEP ~ expression }............ #FIXME: Unit test that this is behaving as expected. Doing so #will require further generalizations, including: #* In the "beartype._decor.decormain" submodule: # * Detect when running under tests. # * When running under tests, define a new # "func_wrapper.__beartype_wrapper_code" attribute added to # decorated callables to be the "func_wrapper_code" string # rather than True. Note that this obviously isn't the right way # to do source code association. Ideally, we'd at least # interface with the stdlib "linecache" module (e.g., by calling # the linecache.lazycache() function intended to be used to # cache the source code for non-file-based modules) and possibly # even go so far as to define a PEP 302-compatible beartype # module loader. That's out of scope, so this suffices for now. #* In the "beartype_test.a00_unit.data._data_hint_pep" submodule: # * Add a new "_PepHintMetadata.code_str_match_regexes" field, # defined as an iterable of regular expressions matching # substrings of the "func_wrapper.__beartype_wrapper_code" # attribute that are expected to exist. # * For most "HINTS_PEP_META" entries, default this field to # merely the empty tuple. # * For deeply nested "HINTS_PEP_META" entries, define this # field as follows: # code_str_match_regexes=(r'\s+:=\s+',) #* In the "beartype_test.a00_unit.pep.p484.test_p484" submodule: # * Match the "pep_hinted.__beartype_wrapper_code" string against # all regular expressions in the "code_str_match_regexes" # iterable for the currently iterated "pep_hint_meta". # #This is fairly important, as we have no other reusable means of #ascertaining whether this is actually being applied in general. #FIXME: That's all great, except for the #"func_wrapper.__beartype_wrapper_code" part. Don't do that, #please. We really do just want to do this right the first time. As #expected, the key to doing so is the linecache.lazycache() #function, whose implementation under Python 3.7 reads: # # def lazycache(filename, module_globals): # """Seed the cache for filename with module_globals. # # The module loader will be asked for the source only when getlines is # called, not immediately. # # If there is an entry in the cache already, it is not altered. # # :return: True if a lazy load is registered in the cache, # otherwise False. To register such a load a module loader with a # get_source method must be found, the filename must be a cachable # filename, and the filename must not be already cached. # """ # if filename in cache: # if len(cache[filename]) == 1: # return True # else: # return False # if not filename or (filename.startswith('<') and filename.endswith('>')): # return False # # Try for a __loader__, if available # if module_globals and '__loader__' in module_globals: # name = module_globals.get('__name__') # loader = module_globals['__loader__'] # get_source = getattr(loader, 'get_source', None) # # if name and get_source: # get_lines = functools.partial(get_source, name) # cache[filename] = (get_lines,) # return True # return False # #Given that, what we need to do is: #* Define a new "beartype._decor._pep302" submodule implementing a # PEP 302-compatible loader for @beartype-generated wrapper # functions, enabling external callers (including the stdlib # "linecache" module) to obtain the source for these functions. # For space efficiency, this submodule should internally store # code in a compressed format -- which probably means "gzip" for # maximal portability. This submodule should at least define these # attributes: # * "_FUNC_WRAPPER_MODULE_NAME_TO_CODE", a dictionary mapping from # the unique fake module names assigned to @beartype-generated # wrapper functions by the @beartype decorator to the compressed # source strings for those fake modules. # * get_source(), a function accepting one unique fake module name # assigned to an arbitrary @beartype-generated wrapper function # by the @beartype decorator and returning the uncompressed # source string for that fake module. Clearly, this function # should internally access the # "_FUNC_WRAPPER_MODULE_NAME_TO_CODE" dictionary and either: # * If the passed module name has *NOT* already been registered # to that dictionary, raise an exception. # * Else, uncompress the compressed source string previously # registered under that module name with that dictionary and # return that uncompressed string. Don't worry about caching # uncompressed strings here; that's exactly what the stdlib # "linecache" module already does on our behalf. # Ergo, this function should have signature resembling: # def get_source(func_wrapper_module_name: str) -> str: # * set_source(), a function accepting one unique fake module name # assigned to an arbitrary @beartype-generated wrapper function # by the @beartype decorator as well as as the uncompressed # source string for that fake module. Clearly, this function # should internally # "_FUNC_WRAPPER_MODULE_NAME_TO_CODE" dictionary and either: # * If the passed module name has already been registered to # that dictionary, raise an exception. # * Else, compress the passed uncompressed source string and # register that compressed string under that module name with # that dictionary. #* In the "beartype._decor.decormain" submodule: # * Do... something? Oh, boy. Why didn't we finish this comment? # If the expression yielding the current pith is neither... if not ( # The root pith *NOR*... # # Note that this is merely a negligible optimization for the # common case in which the current pith is the root pith # (i.e., this is the first iteration of the outermost loop). # The subsequent call to the str.isidentifier() method is # *MUCH* more expensive than this object identity test. pith_curr_expr is VAR_NAME_PITH_ROOT or # A simple Python identifier *NOR*... pith_curr_expr.isidentifier() or # A complex Python expression already containing the # assignment expression-specific "walrus" operator ":=". # Since this implies this expression to already be an # assignment expression, needlessly reassigning the local # variable to which this assignment expression was # previously assigned to yet another redundant local # variable only harms efficiency for *NO* tangible gain # (e.g., expanding the efficient assignment expression # "__beartype_pith_1 := next(iter(__beartype_pith_0))" to # the inefficient assignment expression # "__beartype_pith_2 := __beartype_pith_1 := # next(iter(__beartype_pith_0))"). # # Note that this edge case is induced by closure calls # performed below of the form: # _enqueue_hint_child(pith_curr_assign_expr) # # As of this writing, the only such edge case is a PEP 484- # or 604-compliant union containing *ONLY* two or more # PEP-compliant type hints (e.g., "list[str] | set[bytes]"). ':=' in pith_curr_expr ): # Then the current pith is safely assignable to a unique # local variable via an assignment expression. # # Note that we explicitly test against piths rather than # seemingly equivalent metadata to account for edge cases. # Notably, child hints of unions (and possibly other # "typing" objects) do *NOT* narrow the current pith and are # *NOT* the root hint. Ergo, a seemingly equivalent test # like "hints_meta_index_curr != 0" would generate false # positives and thus unnecessarily inefficient code. # Increment the integer suffixing the name of this variable # *BEFORE* defining this variable. pith_curr_var_name_index += 1 # Name of this local variable. pith_curr_var_name = PITH_INDEX_TO_VAR_NAME[ pith_curr_var_name_index] # Assignment expression assigning this full expression to # this local variable. pith_curr_assign_expr = CODE_PEP572_PITH_ASSIGN_EXPR_format( pith_curr_var_name=pith_curr_var_name, pith_curr_expr=pith_curr_expr, ) # Else, the current pith is *NOT* safely assignable to a unique # local variable via an assignment expression. Since the # expression yielding the current pith is a simple Python # identifier, there is *NO* benefit to assigning that to another # local variable via another assignment expression, which would # just be an alias of the existing local variable assigned via # the existing assignment expression. Moreover, whereas chained # assignments are syntactically valid, chained assignment # expressions are syntactically invalid unless explicitly # protected by parens: e.g., # >>> a = b = 'Mother*Teacher*Destroyer' # <-- fine # >>> (a := "Mother's Abomination") # <-- fine # >>> (a := # ... (b := "Mother's Illumination")) # <-- fine # >>> (a := b := "Mother's Illumination") # <-- not fine # SyntaxError: invalid syntax # # In this case... else: # Name of this local variable. pith_curr_var_name = PITH_INDEX_TO_VAR_NAME[ pith_curr_var_name_index] # Preserve the Python code snippet evaluating to the value # of the current pith as is. pith_curr_assign_expr = pith_curr_expr # ............{ UNION }............ # If this hint is a union (e.g., "typing.Union[bool, str]", # typing.Optional[float]")... # # Note that unions are non-physical abstractions of physical # types and thus *NOT* themselves subject to type-checking; # only the subscripted arguments of unions are type-checked. # This differs from "typing" pseudo-containers like # "List[int]", in which both the parent "List" and child "int" # types represent physical types to be type-checked. Ergo, # unions themselves impose no narrowing of the current pith # expression and thus *CANNOT* by definition benefit from # assignment expressions. This differs from "typing" # pseudo-containers, which narrow the current pith expression # and thus do benefit from assignment expressions. if hint_curr_sign in HINT_SIGNS_UNION: # Assert this union to be subscripted by one or more child # hints. Note this should *ALWAYS* be the case, as: # * The unsubscripted "typing.Union" object is explicitly # listed in the "HINTS_REPR_IGNORABLE_SHALLOW" set and # should thus have already been ignored when present. # * The "typing" module explicitly prohibits empty union # subscription: e.g., # >>> typing.Union[] # SyntaxError: invalid syntax # >>> typing.Union[()] # TypeError: Cannot take a Union of no types. assert hint_childs, ( f'{EXCEPTION_PREFIX}union type hint ' f'{repr(hint_curr)} unsubscripted.' ) # Else, this union is subscripted by two or more arguments. # Why two rather than one? Because the "typing" module # reduces unions of one argument to that argument: e.g., # >>> import typing # >>> typing.Union[int] # int # 0-based index of the currently iterated child hint. hint_childs_index = 0 # For efficiency, reuse a previously created list of all # new child hints of this parent union. hint_childs_new = acquire_object_typed(list) hint_childs_new.clear() # For each subscripted argument of this union... # # Note that this preliminary iteration: # * Modifies the "hint_childs" container being iterated over # and is thus intentionally implemented as a cumbersome # "while" loop rather than a convenient "for" loop. # * Exists for the sole purpose of explicitly flattening # *ALL* child unions nested in this parent union. This # iteration *CANNOT* be efficiently combined with the # iteration performed below for that reason. # * Does *NOT* recursively flatten arbitrarily nested # child unions regardless of nesting depth in this parent # union. Doing so is non-trivial and currently *NOT* # required by any existing edge cases. "Huzzah!" while hint_childs_index < hint_childs_len: # Current child hint of this union. hint_child = hint_childs[hint_childs_index] # This child hint sanified (i.e., sanitized) from this # child hint if this child hint is reducible *OR* # preserved as is otherwise (i.e., if this child hint is # irreducible). # # Note that: # * This sanification is intentionally performed # *BEFORE* this child hint is tested as being either # PEP-compliant or -noncompliant. Why? Because a small # subset of low-level reduction routines performed by # this high-level sanification actually expand a # PEP-noncompliant type into a PEP-compliant type # hint. This includes: # * The PEP-noncompliant "float' and "complex" types, # implicitly expanded to the PEP 484-compliant # "float | int" and "complex | float | int" type # hints (respectively) when the non-default # "conf.is_pep484_tower=True" parameter is enabled. # * This sanification intentionally calls the # lower-level sanify_hint_child() rather than the # higher-level # sanify_hint_child_if_unignorable_or_none() sanifier. # Technically, the latter would suffice as well. # Pragmatically, both are semantically equivalent here # but the former is faster. Why? By definition, this # union is unignorable. If this union were ignorable, # the parent hint containing this union would already # have ignored this union. Moreover, *ALL* child # hints subscripting an unignorable union are # necessarily also unignorable. It follows that this # child hint need *NOT* be tested for ignorability. # print(f'Sanifying union child hint {repr(hint_child)} under {repr(conf)}...') hint_child = sanify_hint_child( hint=hint_child, conf=conf, cls_stack=cls_stack, exception_prefix=EXCEPTION_PREFIX, ) # print(f'Sanified union child hint to {repr(hint_child)}...') # Sign of this sanified child hint if this hint is # PEP-compliant *OR* "None" otherwise (i.e., if this # hint is PEP-noncompliant). hint_child_sign = get_hint_pep_sign_or_none(hint_child) # If this child hint is itself a child union nested in # this parent union, explicitly flatten this nested # union by appending *ALL* child child hints # subscripting this child union onto this parent union. # # Note that this edge case currently *ONLY* arises when # this child hint has been expanded by the above call to # the sanify_hint_child() function from a non-union (e.g., # "float") into a union (e.g., "float | int"). The # standard PEP 484-compliant "typing.Union" factory # already implicitly flattens nested unions: e.g., # >>> from typing import Union # >>> Union[float, Union[int, str]] # typing.Union[float, int, str] if hint_child_sign in HINT_SIGNS_UNION: # Tuple of all child child type hints subscripting # this child union. hint_child_childs = get_hint_pep_args(hint_child) # print(f'Expanding union {repr(hint_curr)} with child union {repr(hint_child_childs)}...') # Append these child child type hints to this parent # union. hint_childs_new.extend(hint_child_childs) # Else, this child hint is *NOT* itself a union. In this # case, append this child hint to this parent union. else: hint_childs_new.append(hint_child) # Increment the 0-based index of the currently iterated # child hint. hint_childs_index += 1 # Freeze this temporary list back to this permanent tuple, # replacing the prior unflattened contents of this tuple. hint_childs = tuple(hint_childs_new) # Release this list back to its respective pool. release_object_typed(hint_childs_new) # print(f'Flattened union to {repr(hint_childs)}...') # For efficiency, reuse previously created sets of the # following (when available): # * "hint_childs_nonpep", the set of all PEP-noncompliant # child hints subscripting this union. # * "hint_childs_pep", the set of all PEP-compliant child # hints subscripting this union. # # Since these child hints require fundamentally different # forms of type-checking, prefiltering child hints into # these sets *BEFORE* generating code type-checking these # child hints improves both efficiency and maintainability. hint_childs_nonpep = acquire_object_typed(set) hint_childs_pep = acquire_object_typed(set) # Clear these sets prior to use below. hint_childs_nonpep.clear() hint_childs_pep.clear() # For each subscripted argument of this union... for hint_child in hint_childs: #FIXME: Uncomment as desired for debugging. This test is #currently a bit too costly to warrant uncommenting. # Assert that this child hint is *NOT* shallowly ignorable. # Why? Because any union containing one or more shallowly # ignorable child hints is deeply ignorable and should thus # have already been ignored after a call to the # is_hint_ignorable() tester passed this union on handling # the parent hint of this union. # assert ( # repr(hint_curr) not in HINTS_REPR_IGNORABLE_SHALLOW), ( # f'{hint_curr_exception_prefix} {repr(hint_curr)} child ' # f'{repr(hint_child)} ignorable but not ignored.') # If this child hint is PEP-compliant... if is_hint_pep(hint_child): # Filter this child hint into the set of # PEP-compliant child hints. # # Note that this PEP-compliant child hint *CANNOT* # also be filtered into the set of PEP-noncompliant # child hints, even if this child hint originates # from a non-"typing" type (e.g., "List[int]" from # "list"). Why? Because that would then induce # false positives when the current pith shallowly # satisfies this non-"typing" type but does *NOT* # deeply satisfy this child hint. hint_childs_pep.add(hint_child) # Else, this child hint is PEP-noncompliant. In this # case, filter this child hint into the list of # PEP-noncompliant arguments. else: hint_childs_nonpep.add(hint_child) # Initialize the code type-checking the current pith against # these arguments to the substring prefixing all such code. func_curr_code = CODE_PEP484604_UNION_PREFIX # If this union is subscripted by one or more # PEP-noncompliant child hints, generate and append # efficient code type-checking these child hints *BEFORE* # less efficient code type-checking any PEP-compliant child # hints subscripting this union. if hint_childs_nonpep: func_curr_code += ( CODE_PEP484604_UNION_CHILD_NONPEP_format( # Python expression yielding the value of the # current pith. Specifically... pith_curr_expr=( # If this union is also subscripted by one # or more PEP-compliant child hints, prefer # the expression assigning this value to a # local variable efficiently reused by # subsequent code generated for those # PEP-compliant child hints. pith_curr_assign_expr if hint_childs_pep else # Else, this union is subscripted by *NO* # PEP-compliant child hints. Since this is # the first and only test generated for this # union, prefer the expression yielding the # value of the current pith *WITHOUT* # assigning this value to a local variable, # which would needlessly go unused. pith_curr_expr ), # Python expression evaluating to a tuple of # these arguments. # # Note that we would ideally avoid coercing this # set into a tuple when this set only contains # one type by passing that type directly to the # _add_func_wrapper_local_type() function. # Sadly, the "set" class defines no convenient # or efficient means of retrieving the only item # of a 1-set. Indeed, the most efficient means # of doing so is to iterate over that set and # immediately halt iteration: # for first_item in muh_set: break # # While we *COULD* technically leverage that # approach here, doing so would also mandate # adding multiple intermediate tests, mitigating # any performance gains. Ultimately, we avoid # doing so by falling back to the usual # approach. See also this relevant # self-StackOverflow post: # https://stackoverflow.com/a/40054478/2809027 hint_curr_expr=add_func_scope_types( types=hint_childs_nonpep, func_scope=func_wrapper_scope, exception_prefix=( EXCEPTION_PREFIX_FUNC_WRAPPER_LOCAL), ), )) # For the 0-based index and each child hint of this union... for hint_child_index, hint_child in enumerate( hint_childs_pep): # Code deeply type-checking this child hint. func_curr_code += CODE_PEP484604_UNION_CHILD_PEP_format( # Expression yielding the value of this pith. hint_child_placeholder=_enqueue_hint_child( # If either... # # Then prefer the expression efficiently reusing # the value previously assigned to a local # variable by either the above conditional or # prior iteration of the current conditional. pith_curr_var_name if ( # This union is also subscripted by one or # more PEP-noncompliant child hints *OR*... hint_childs_nonpep or # This is any PEP-compliant child hint # *EXCEPT* the first... hint_child_index ) else # Then this union is not subscripted by any # PEP-noncompliant child hints *AND* this is the # first PEP-compliant child hint. In this case, # preface this code with an expression assigning # this value to a local variable efficiently # reused by code generated by subsequent # iteration. # # Note this child hint is guaranteed to be # followed by at least one more child hint. Why? # Because the "typing" module forces unions to # be subscripted by two or more child hints. By # deduction, those child hints *MUST* be # PEP-compliant. Ergo, we need *NOT* explicitly # validate that constraint here. pith_curr_assign_expr )) # If this code is *NOT* its initial value, this union is # subscripted by one or more unignorable child hints and # the above logic generated code type-checking these child # hints. In this case... if func_curr_code is not CODE_PEP484604_UNION_PREFIX: # Munge this code to... func_curr_code = ( # Strip the erroneous " or" suffix appended by the # last child hint from this code. f'{func_curr_code[:LINE_RSTRIP_INDEX_OR]}' # Suffix this code by the substring suffixing all # such code. f'{CODE_PEP484604_UNION_SUFFIX}' # Format the "indent_curr" prefix into this code, # deferred above for efficiency. ).format(indent_curr=indent_curr) # Else, this snippet is its initial value and thus # ignorable. # Release this pair of sets back to their respective pools. release_object_typed(hint_childs_nonpep) release_object_typed(hint_childs_pep) # Else, this hint is *NOT* a union. # # ..........{ SEQUENCES ~ variadic }............ # If this hint is either... elif ( # A standard sequence (e.g., "typing.List[int]") *OR*... hint_curr_sign in HINT_SIGNS_SEQUENCE_ARGS_1 or ( # A tuple *AND*... hint_curr_sign is HintSignTuple and # This tuple is subscripted by exactly two child hints # *AND*... hint_childs_len == 2 and # The second child hint is just an unquoted ellipsis... hint_childs[1] is Ellipsis ) # Then this hint is of the form "Tuple[{typename}, ...]", # typing a tuple accepting a variadic number of items all # satisfying the "{typename}" child hint. Since this case # is semantically equivalent to that of standard sequences, # we transparently handle both here for maintainability. # # See below for logic handling fixed-length tuples. # Then this hint is either a single-argument sequence *OR* a # similar hint semantically resembling a single-argument # sequence subscripted by one argument and one or more # ignorable arguments. In this case... ): # Python expression evaluating to the origin type of this # sequence hint. hint_curr_expr = add_func_scope_type( # Origin type of this sequence hint. cls=get_hint_pep_origin_type_isinstanceable(hint_curr), func_scope=func_wrapper_scope, exception_prefix=EXCEPTION_PREFIX_FUNC_WRAPPER_LOCAL, ) # print(f'Sequence type hint {hint_curr} origin type scoped: {hint_curr_expr}') # Possibly ignorable insane child hint subscripting this # sequence hint, defined as either... hint_child = ( # If this hint is a variadic tuple, the parent "if" # statement above has already validated the contents of # this tuple. In this case, efficiently get the lone # child hint of this parent hint *WITHOUT* validation. hint_childs[0] if hint_curr_sign is HintSignTuple else # Else, this hint is a single-argument sequence, in # which case the contents of this sequence have yet to # be validated. In this case, inefficiently get the lone # child hint of this parent hint *WITH* validation. get_hint_pep484585_args( hint=hint_curr, args_len=1, exception_prefix=EXCEPTION_PREFIX, ) ) # Unignorable sane child hint sanified from this possibly # ignorable insane child hint *OR* "None" otherwise (i.e., # if this child hint is ignorable). hint_child = sanify_hint_child_if_unignorable_or_none( hint=hint_child, conf=conf, cls_stack=cls_stack, exception_prefix=EXCEPTION_PREFIX, ) # If this child hint is unignorable, deeply type-check both # the type of the current pith *AND* a randomly indexed item # of this pith. Specifically... if hint_child is not None: # Record that a pseudo-random integer is now required. is_var_random_int_needed = True # Code type-checking this pith against this type. func_curr_code = CODE_PEP484585_SEQUENCE_ARGS_1_format( indent_curr=indent_curr, pith_curr_assign_expr=pith_curr_assign_expr, pith_curr_var_name=pith_curr_var_name, hint_curr_expr=hint_curr_expr, hint_child_placeholder=_enqueue_hint_child( # Python expression yielding the value of a # randomly indexed item of the current pith # (i.e., standard sequence) to be # type-checked against this child hint. CODE_PEP484585_SEQUENCE_ARGS_1_PITH_CHILD_EXPR_format( pith_curr_var_name=pith_curr_var_name)), ) # Else, this child hint is ignorable. In this case, fallback # to trivial code shallowly type-checking this pith as an # instance of this origin type. else: func_curr_code = CODE_PEP484_INSTANCE_format( pith_curr_expr=pith_curr_expr, hint_curr_expr=hint_curr_expr, ) # Else, this hint is neither a standard sequence *NOR* variadic # tuple. # # ............{ SEQUENCES ~ tuple : fixed }............ # If this hint is a tuple, this tuple is *NOT* of the variadic # form and *MUST* thus be of the fixed-length form. # # Note that if this hint is a: # * PEP 484-compliant "typing.Tuple"-based hint, this hint is # guaranteed to contain one or more child hints. Moreover, if # this hint contains exactly one child hint that is the empty # tuple, this hint is the empty fixed-length form # "typing.Tuple[()]". # * PEP 585-compliant "tuple"-based hint, this hint is *NOT* # guaranteed to contain one or more child hints. If this hint # contains *NO* child hints, this hint is equivalent to the # empty fixed-length PEP 484-compliant form # "typing.Tuple[()]". Yes, PEP 585 even managed to violate # PEP 484-compliance. UUUURGH! # # While tuples are sequences, the "typing.Tuple" singleton that # types tuples violates the syntactic norms established for # other standard sequences by concurrently supporting two # different syntaxes with equally different semantics: # * "typing.Tuple[{typename}, ...]", typing a tuple whose items # all satisfy the "{typename}" child hint. Note that the # "..." substring here is a literal ellipses. # * "typing.Tuple[{typename1}, {typename2}, ..., {typenameN}]", # typing a tuple whose: # * First item satisfies the "{typename1}" child hint. # * Second item satisfies the "{typename2}" child hint. # * Last item satisfies the "{typenameN}" child hint. # Note that the "..." substring here is *NOT* a literal # ellipses. # # This is what happens when unreadable APIs are promoted. elif hint_curr_sign is HintSignTuple: # Assert this tuple is *NOT* of the syntactic form # "typing.Tuple[{typename}, ...]" handled by prior logic. assert ( hint_childs_len <= 1 or hint_childs[1] is not Ellipsis ), (f'{EXCEPTION_PREFIX}variadic tuple type hint ' f'{repr(hint_curr)} unhandled.') # Initialize the code type-checking this pith against this # tuple to the substring prefixing all such code. func_curr_code = CODE_PEP484585_TUPLE_FIXED_PREFIX # If this hint is the empty fixed-length tuple, generate # and append code type-checking the current pith to be the # empty tuple. This edge case constitutes a code smell. if is_hint_pep484585_tuple_empty(hint_curr): func_curr_code += ( CODE_PEP484585_TUPLE_FIXED_EMPTY_format( pith_curr_var_name=pith_curr_var_name)) # Else, that ridiculous edge case does *NOT* apply. In this # case... else: # Append code type-checking the length of this pith. func_curr_code += ( CODE_PEP484585_TUPLE_FIXED_LEN_format( pith_curr_var_name=pith_curr_var_name, hint_childs_len=hint_childs_len, )) # For each possibly ignorable insane child hint of this # parent tuple... for hint_child_index, hint_child in enumerate( hint_childs): # Unignorable sane child hint sanified from this # possibly ignorable insane child hint *OR* "None" # otherwise (i.e., if this child hint is ignorable). hint_child = ( sanify_hint_child_if_unignorable_or_none( hint=hint_child, conf=conf, cls_stack=cls_stack, exception_prefix=EXCEPTION_PREFIX, )) # If this child hint is unignorable, deeply # type-check this child pith. if hint_child is not None: func_curr_code += CODE_PEP484585_TUPLE_FIXED_NONEMPTY_CHILD_format( hint_child_placeholder=_enqueue_hint_child( # Python expression yielding the value # of the currently indexed item of this # tuple to be type-checked against this # child hint. CODE_PEP484585_TUPLE_FIXED_NONEMPTY_PITH_CHILD_EXPR_format( pith_curr_var_name=pith_curr_var_name, pith_child_index=hint_child_index, ) ), ) # Else, this child hint is ignorable. # Munge this code to... func_curr_code = ( # Strip the erroneous " and" suffix appended by the # last child hint from this code. f'{func_curr_code[:LINE_RSTRIP_INDEX_AND]}' # Suffix this code by the substring suffixing all such # code. f'{CODE_PEP484585_TUPLE_FIXED_SUFFIX}' # Format... ).format( indent_curr=indent_curr, pith_curr_assign_expr=pith_curr_assign_expr, ) # Else, this hint is *NOT* a tuple. # # ..........{ MAPPINGS }............ # If this hint is a standard mapping (e.g., "dict[str, int]")... elif hint_curr_sign in HINT_SIGNS_MAPPING: # Python expression evaluating to the origin type of this # mapping hint. hint_curr_expr = add_func_scope_type( # Origin type of this sequence. cls=get_hint_pep_origin_type_isinstanceable(hint_curr), func_scope=func_wrapper_scope, exception_prefix=EXCEPTION_PREFIX_FUNC_WRAPPER_LOCAL, ) # 2-tuple of the possibly ignorable insane child key and # value hints subscripting this mapping hint. hint_childs = get_hint_pep484585_args( # type: ignore[assignment] hint=hint_curr, args_len=2, exception_prefix=EXCEPTION_PREFIX, ) #FIXME: Consider also contextually considering child key #hints that reduce to "Hashable" to be ignorable. This #includes complex type hints like "Union[Hashable, str]", #which reduces to "Hashable". We can't particularly be #bothered at the moment. This is a microoptimization and #will probably require a non-trivial amount of work. *sigh* # Unignorable sane child key and value hints sanified from # these possibly ignorable insane child key and value hints # *OR* "None" otherwise (i.e., if ignorable). hint_child_key = sanify_hint_child_if_unignorable_or_none( hint=hint_childs[0], conf=conf, cls_stack=cls_stack, exception_prefix=EXCEPTION_PREFIX, ) hint_child_value = sanify_hint_child_if_unignorable_or_none( hint=hint_childs[1], # type: ignore[has-type] conf=conf, cls_stack=cls_stack, exception_prefix=EXCEPTION_PREFIX, ) # If at least one of these child hints are unignorable... if hint_child_key or hint_child_value: # If this child key hint is unignorable... if hint_child_key: # If this child value hint is also unignorable... if hint_child_value: # Increase the indentation level of code # type-checking this child value pith. indent_level_child += 1 # Increment the integer suffixing the name of a # unique local variable storing the value of # this child key pith *BEFORE* defining this # variable. pith_curr_var_name_index += 1 # Name of this local variable. pith_curr_key_var_name = PITH_INDEX_TO_VAR_NAME[ pith_curr_var_name_index] # Expose this hint to the subsequent call to the # _enqueue_hint_child() closure. hint_child = hint_child_key # Placeholder string to be subsequently replaced # by code type-checking this child key pith # against this hint. hint_key_placeholder = _enqueue_hint_child( pith_curr_key_var_name) # Expose this hint to the subsequent call to the # _enqueue_hint_child() closure. hint_child = hint_child_value # Placeholder string to be subsequently replaced # by code type-checking this child value pith # against this hint. hint_value_placeholder = _enqueue_hint_child( CODE_PEP484585_MAPPING_KEY_VALUE_PITH_CHILD_EXPR_format( pith_curr_var_name=pith_curr_var_name, pith_curr_key_var_name=pith_curr_key_var_name, )) # Code deeply type-checking these child key and # value piths against these hints. func_curr_code_key_value = ( CODE_PEP484585_MAPPING_KEY_VALUE_format( indent_curr=indent_curr, pith_curr_key_var_name=( # pyright: ignore pith_curr_key_var_name), pith_curr_var_name=pith_curr_var_name, hint_key_placeholder=( hint_key_placeholder), hint_value_placeholder=( hint_value_placeholder), )) # Else, this child value hint is ignorable. In this # case... else: # Expose this child key hint to the subsequent # call to the _enqueue_hint_child() closure. hint_child = hint_child_key # Code deeply type-checking only this child key # pith against this hint. func_curr_code_key_value = ( CODE_PEP484585_MAPPING_KEY_ONLY_format( indent_curr=indent_curr, # Placeholder string to be subsequently # replaced by code type-checking this # child key pith against this hint. hint_key_placeholder=_enqueue_hint_child( CODE_PEP484585_MAPPING_KEY_ONLY_PITH_CHILD_EXPR_format( pith_curr_var_name=( pith_curr_var_name))), )) # Else, this child key hint is ignorable. By process # of elimination, this child value hint *MUST* be # unignorable. In this case... else: # Expose this child value hint to the subsequent # call to the _enqueue_hint_child() closure. hint_child = hint_child_value # Code deeply type-checking only this child value # pith against this hint. func_curr_code_key_value = ( CODE_PEP484585_MAPPING_VALUE_ONLY_format( indent_curr=indent_curr, # Placeholder string to be subsequently # replaced by code type-checking this # child value pith against this hint. hint_value_placeholder=_enqueue_hint_child( CODE_PEP484585_MAPPING_VALUE_ONLY_PITH_CHILD_EXPR_format( pith_curr_var_name=( pith_curr_var_name))), )) # Code deeply type-checking this pith as well as at # least one of these child key and value piths. func_curr_code = CODE_PEP484585_MAPPING_format( indent_curr=indent_curr, pith_curr_assign_expr=pith_curr_assign_expr, pith_curr_var_name=pith_curr_var_name, hint_curr_expr=hint_curr_expr, func_curr_code_key_value=func_curr_code_key_value, ) # Else, these child key *AND* value hints are both # ignorable. In this case, fallback to trivial code # shallowly type-checking this pith as an instance of this # origin type. else: func_curr_code = CODE_PEP484_INSTANCE_format( pith_curr_expr=pith_curr_expr, hint_curr_expr=hint_curr_expr, ) # Else, this hint is *NOT* a mapping. # # ............{ ANNOTATED }............ # If this hint is a PEP 593-compliant type metahint, this # metahint is guaranteed by the reduction performed above to be # beartype-specific (i.e., metahint whose second argument is a # beartype validator produced by subscripting a beartype # validator factory). In this case... elif hint_curr_sign is HintSignAnnotated: # Defer heavyweight imports. from beartype.vale._core._valecore import BeartypeValidator # Initialize the code type-checking this pith against this # metahint to the substring prefixing all such code. func_curr_code = CODE_PEP593_VALIDATOR_PREFIX # Unignorable sane metahint annotating this parent hint # sanified from this possibly ignorable insane metahint *OR* # "None" otherwise (i.e., if this metahint is ignorable). hint_child = sanify_hint_child_if_unignorable_or_none( hint=get_hint_pep593_metahint(hint_curr), conf=conf, cls_stack=cls_stack, exception_prefix=EXCEPTION_PREFIX, ) # Python expression yielding the value of the current pith, # defaulting to the name of the local variable assigned to # by the assignment expression performed below. hint_curr_expr = pith_curr_var_name # Tuple of the one or more beartype validators annotating # this metahint. hints_child = get_hint_pep593_metadata(hint_curr) # print(f'hints_child: {repr(hints_child)}') # If this metahint is ignorable... if hint_child is None: # If this metahint is annotated by only one beartype # validator, the most efficient expression yielding the # value of the current pith is simply the full Python # expression *WITHOUT* assigning that value to a # reusable local variable in an assignment expression. # *NO* assignment expression is needed in this case. # # Why? Because beartype validators are *NEVER* recursed # into. Each beartype validator is guaranteed to be the # leaf of a type-checking subtree, guaranteeing this # pith to be evaluated only once. if len(hints_child) == 1: hint_curr_expr = pith_curr_expr # Else, this metahint is annotated by two or more # beartype validators. In this case, the most efficient # expression yielding the value of the current pith is # the assignment expression assigning this value to a # reusable local variable. else: hint_curr_expr = pith_curr_assign_expr # Else, this metahint is unignorable. In this case... else: # Code deeply type-checking this metahint. func_curr_code += CODE_PEP593_VALIDATOR_METAHINT_format( indent_curr=indent_curr, # Python expression yielding the value of the # current pith assigned to a local variable # efficiently reused by code generated by the # following iteration. # # Note this child hint is guaranteed to be followed # by at least one more test expression referencing # this local variable. Why? Because the "typing" # module forces metahints to be subscripted by one # child hint and one or more arbitrary objects. # Ergo, we need *NOT* explicitly validate that here. hint_child_placeholder=_enqueue_hint_child( pith_curr_assign_expr), ) # Else, this metahint is ignorable. # For the 0-based index and each beartype validator # annotating this metahint... for hint_child_index, hint_child in enumerate(hints_child): # print(f'Type-checking PEP 593 type hint {repr(hint_curr)} argument {repr(hint_child)}...') # If this is *NOT* a beartype validator, raise an # exception. # # Note that the previously called sanify_hint_child() # function validated only the first such to be a # beartype validator. All remaining arguments have yet # to be validated, so we do so now for consistency and # safety. if not isinstance(hint_child, BeartypeValidator): raise BeartypeDecorHintPep593Exception( f'{EXCEPTION_PREFIX}PEP 593 type hint ' f'{repr(hint_curr)} subscripted by both ' f'@beartype-specific and -agnostic metadata ' f'(i.e., {represent_object(hint_child)} not ' f'beartype validator).' ) # Else, this argument is beartype-specific. # # If this is any beartype validator *EXCEPT* the first, # set the Python expression yielding the value of the # current pith to the name of the local variable # assigned to by the prior assignment expression. By # deduction, it *MUST* be the case now that either: # * This metahint was unignorable, in which case this # assignment uselessly reduplicates the exact same # assignment performed above. While non-ideal, this # assignment is sufficiently efficient to make any # optimizations here effectively worthless. # * This metahint was ignorable, in which case this # expression was set above to the assignment # expression assigning this pith for the first # beartype validator. Since this iteration has already # processed the first beartype validator, this # assignment expression has already been performed. # Avoid inefficiently re-performing this assignment # expression for each additional beartype validator by # efficiently reusing the previously assigned local. elif hint_child_index: hint_curr_expr = pith_curr_var_name # Else, this is the first beartype validator. See above. # Code deeply type-checking this validator. func_curr_code += CODE_PEP593_VALIDATOR_IS_format( indent_curr=indent_curr, # Python expression formatting the current pith into # the "{obj}" format substring previously embedded # by this validator into this code string. hint_child_expr=hint_child._is_valid_code.format( # Indentation unique to this child hint. indent=INDENT_LEVEL_TO_CODE[indent_level_child], obj=hint_curr_expr, ), ) # Generate locals safely merging the locals required by # both this validator code *AND* the current code # type-checking this entire root hint. update_mapping( mapping_trg=func_wrapper_scope, mapping_src=hint_child._is_valid_code_locals, ) # Munge this code to... func_curr_code = ( # Strip the erroneous " and" suffix appended by the # last child hint from this code. f'{func_curr_code[:LINE_RSTRIP_INDEX_AND]}' # Suffix this code by the substring suffixing all such # code. f'{CODE_PEP593_VALIDATOR_SUFFIX_format(indent_curr=indent_curr)}' ) # Else, this hint is *NOT* a metahint. # # ............{ SUBCLASS }............ # If this hint is either a PEP 484- or 585-compliant subclass # type hint... elif hint_curr_sign is HintSignType: #FIXME: Optimization: if the superclass is an ignorable #class (e.g., "object", "Protocol"), this type hint is #ignorable (e.g., "Type[object]", "type[Protocol]"). We'll #thus want to: #* Add that detection logic to one or more # is_hint_*_ignorable() testers elsewhere. #* Call is_hint_ignorable() below. #* Unit test such type hints to indeed be ignorable. # Superclass this pith is required to be a subclass of. hint_child = get_hint_pep484585_type_superclass( hint=hint_curr, exception_prefix=EXCEPTION_PREFIX, ) # If this superclass is either a class *OR* tuple of # classes... if isinstance(hint_child, TestableTypes): # Python expression evaluating to this superclass. hint_curr_expr = add_func_scope_type_or_types( type_or_types=hint_child, # type: ignore[arg-type] func_scope=func_wrapper_scope, exception_prefix=( EXCEPTION_PREFIX_FUNC_WRAPPER_LOCAL), ) # Else, this superclass is *NOT* actually a class. By # process of elimination and the validation already # performed above by the # get_hint_pep484585_type_superclass() getter, this # superclass *MUST* be a forward reference to a class. else: # Render this forward reference accessible to the body # of this wrapper function. See above for commentary. hint_curr_expr, hint_refs_type_basename = ( express_func_scope_type_ref( forwardref=hint_child, # type: ignore[arg-type] forwardrefs_class_basename=( hint_refs_type_basename), func_scope=func_wrapper_scope, exception_prefix=EXCEPTION_PREFIX, )) # Code type-checking this pith against this superclass. func_curr_code = CODE_PEP484585_SUBCLASS_format( pith_curr_assign_expr=pith_curr_assign_expr, pith_curr_var_name=pith_curr_var_name, hint_curr_expr=hint_curr_expr, indent_curr=indent_curr, ) # Else, this hint is neither a PEP 484- nor 585-compliant # subclass type hint. # # ............{ GENERIC or PROTOCOL }............ # If this hint is either a: # * PEP 484-compliant generic (i.e., user-defined class # subclassing a combination of one or more of the # "typing.Generic" superclass and other "typing" non-class # pseudo-superclasses) *OR*... # * PEP 544-compliant protocol (i.e., class subclassing a # combination of one or more of the "typing.Protocol" # superclass and other "typing" non-class # pseudo-superclasses) *OR*... # * PEP 585-compliant generic (i.e., user-defined class # subclassing at least one non-class PEP 585-compliant # pseudo-superclasses) *OR*... # # ...then this hint is a PEP-compliant generic. In this case... elif hint_curr_sign is HintSignGeneric: #FIXME: *THIS IS NON-IDEAL.* Ideally, we should propagate #*ALL* child type hints subscripting a generic up to *ALL* #pseudo-superclasses of that generic (e.g., the "int" child #hint subscripting a parent hint "MuhGeneric[int]" of type #"class MuhGeneric(list[T]): pass" up to its "list[T]" #pseudo-superclass). # #For now, we just strip *ALL* child type hints subscripting #a generic with the following call. This suffices, because #we just need this to work. So it goes, uneasy code #bedfellows. # Reduce this hint to the object originating this generic # (if any) by stripping all child type hints subscripting # this hint from this hint. Why? Because these child type # hints convey *NO* meaningful semantics and are thus safely # ignorable. Consider this simple example, in which the # subscription "[int]" not only conveys *NO* meaningful # semantics but actually conveys paradoxically conflicting # semantics contradicting the original generic declaration: # class ListOfListsOfStrs(list[list[str]]): pass # ListOfListsOfStrs[int] # <-- *THIS MEANS NOTHING* # # Specifically: # * If this hint is an unsubscripted generic (e.g., # "typing.IO"), preserve this hint as is. In this case, # this hint is a standard isinstanceable class. # * If this hint is a subscripted generic (e.g., # "typing.IO[str]"), reduce this hint to the object # originating this generic (e.g., "typing.IO"). hint_curr = get_hint_pep484585_generic_type( hint=hint_curr, exception_prefix=EXCEPTION_PREFIX) # print(f'Visiting generic type {repr(hint_curr)}...') # Initialize the code type-checking this pith against this # generic to the substring prefixing all such code. func_curr_code = CODE_PEP484585_GENERIC_PREFIX # For each unignorable unerased transitive pseudo-superclass # originally declared as a superclass of this generic... for hint_child in ( iter_hint_pep484585_generic_bases_unerased_tree( hint=hint_curr, conf=conf, exception_prefix=EXCEPTION_PREFIX, )): # print(f'Visiting generic type hint {repr(hint_curr)} unerased base {repr(hint_child)}...') # Generate and append code type-checking this pith # against this superclass. func_curr_code += CODE_PEP484585_GENERIC_CHILD_format( hint_child_placeholder=_enqueue_hint_child( # Python expression efficiently reusing the # value of this pith previously assigned to a # local variable by the prior expression. pith_curr_var_name)) # Munge this code to... func_curr_code = ( # Strip the erroneous " and" suffix appended by the # last child hint from this code. f'{func_curr_code[:LINE_RSTRIP_INDEX_AND]}' # Suffix this code by the substring suffixing all such # code. f'{CODE_PEP484585_GENERIC_SUFFIX}' # Format... ).format( # Indentation deferred above for efficiency. indent_curr=indent_curr, pith_curr_assign_expr=pith_curr_assign_expr, # Python expression evaluating to this generic type. hint_curr_expr=add_func_scope_type( cls=hint_curr, func_scope=func_wrapper_scope, exception_prefix=( EXCEPTION_PREFIX_FUNC_WRAPPER_LOCAL), ), ) # print(f'{hint_curr_exception_prefix} PEP generic {repr(hint)} handled.') # Else, this hint is *NOT* a generic. # # ............{ LITERAL }............ # If this hint is a PEP 586-compliant type hint (i.e., the # "typing.Literal" singleton subscripted by one or more literal # objects), this hint is largely useless and thus intentionally # detected last. Why? Because "typing.Literal" is subscriptable # by objects that are instances of only *SIX* possible types, # which is sufficiently limiting as to render this singleton # patently absurd and a farce that we weep to even implement. # In this case... elif hint_curr_sign is HintSignLiteral: # Tuple of zero or more literal objects subscripting this # hint, intentionally replacing the current such tuple due # to the non-standard implementation of the third-party # "typing_extensions.Literal" type hint factory. hint_childs = get_hint_pep586_literals( hint=hint_curr, exception_prefix=EXCEPTION_PREFIX) # Initialize the code type-checking this pith against this # hint to the substring prefixing all such code. func_curr_code = CODE_PEP586_PREFIX_format( pith_curr_assign_expr=pith_curr_assign_expr, #FIXME: If "typing.Literal" is ever extended to support #substantially more types (and thus actually becomes #useful), optimize the construction of the "types" set #below to instead leverage a similar #"acquire_object_typed(set)" caching solution as that #currently employed for unions. For now, we only shrug. # Python expression evaluating to a tuple of the unique # types of all literal objects subscripting this hint. hint_child_types_expr=add_func_scope_types( # Set comprehension of all unique literal objects # subscripting this hint, implicitly discarding all # duplicate such objects. types={ type(hint_child) for hint_child in hint_childs }, func_scope=func_wrapper_scope, exception_prefix=( EXCEPTION_PREFIX_FUNC_WRAPPER_LOCAL), ), ) # For each literal object subscripting this hint... for hint_child in hint_childs: # Generate and append efficient code type-checking # this data validator by embedding this code as is. func_curr_code += CODE_PEP586_LITERAL_format( pith_curr_var_name=pith_curr_var_name, # Python expression evaluating to this object. hint_child_expr=add_func_scope_attr( attr=hint_child, func_scope=func_wrapper_scope, exception_prefix=( EXCEPTION_PREFIX_FUNC_WRAPPER_LOCAL), ), ) # Munge this code to... func_curr_code = ( # Strip the erroneous " or" suffix appended by the last # child hint from this code. f'{func_curr_code[:LINE_RSTRIP_INDEX_OR]}' # Suffix this code by the appropriate substring. f'{CODE_PEP586_SUFFIX}' ).format(indent_curr=indent_curr) # Else, this hint is *NOT* a PEP 586-compliant type hint. # ............{ UNSUPPORTED }............ # Else, this hint is neither shallowly nor deeply supported and # is thus unsupported. Since an exception should have already # been raised above in this case, this conditional branch # *NEVER* be triggered. Nonetheless, raise an exception. else: raise BeartypeDecorHintPepUnsupportedException( f'{EXCEPTION_PREFIX_HINT}' f'{repr(hint_curr)} unsupported but ' f'erroneously detected as supported with ' f'beartype sign {hint_curr_sign}.' ) # ................{ NON-PEP }................ # Else, this hint is *NOT* PEP-compliant. # # ................{ NON-PEP ~ type }................ # If this hint is a non-"typing" class... # # Note that: # * This test is intentionally performed *AFTER* that testing whether # this hint is PEP-compliant, thus guaranteeing this hint to be a # PEP-noncompliant non-"typing" class rather than a PEP-compliant # type hint originating from such a class. Since many hints are both # PEP-compliant *AND* originate from such a class (e.g., the "List" # in "List[int]", PEP-compliant but originating from the # PEP-noncompliant builtin class "list"), testing these hints first # for PEP-compliance ensures we generate non-trivial code deeply # type-checking these hints instead of trivial code only shallowly # type-checking the non-"typing" classes from which they originate. # * This class is guaranteed to be a subscripted argument of a # PEP-compliant type hint (e.g., the "int" in "Union[Dict[str, str], # int]") rather than the root type hint. Why? Because if this class # were the root type hint, it would have already been passed into a # faster submodule generating PEP-noncompliant code instead. elif isinstance(hint_curr, type): # Code type-checking the current pith against this type. func_curr_code = CODE_PEP484_INSTANCE_format( pith_curr_expr=pith_curr_expr, # Python expression evaluating to this type. hint_curr_expr=add_func_scope_type( cls=hint_curr, func_scope=func_wrapper_scope, exception_prefix=EXCEPTION_PREFIX_HINT, ), ) # ................{ NON-PEP ~ bad }................ # Else, this hint is neither PEP-compliant *NOR* a class. In this case, # raise an exception. Note that: # * This should *NEVER* happen, as the "typing" module goes to great # lengths to validate the integrity of PEP-compliant types at # declaration time. # * The higher-level die_unless_hint_nonpep() validator is # intentionally *NOT* called here, as doing so would permit both: # * PEP-noncompliant forward references, which could admittedly be # disabled by passing "is_str_valid=False" to that call. # * PEP-noncompliant tuple unions, which currently *CANNOT* be # disabled by passing such an option to that call. else: raise BeartypeDecorHintPepException( f'{EXCEPTION_PREFIX_HINT}{repr(hint_curr)} ' f'not PEP-compliant.' ) # ................{ CLEANUP }................ # Inject this code into the body of this wrapper. func_wrapper_code = replace_str_substrs( text=func_wrapper_code, old=hint_curr_placeholder, new=func_curr_code, ) # Nullify the metadata describing the previously visited hint in this # list for safety. hints_meta[hints_meta_index_curr] = None # Increment the 0-based index of metadata describing the next visited # hint in the "hints_meta" list *BEFORE* visiting that hint but *AFTER* # performing all other logic for the currently visited hint. hints_meta_index_curr += 1 # ..................{ CLEANUP }.................. # Release the fixed list of all such metadata. release_fixed_list(hints_meta) # If the Python code snippet to be returned remains unchanged from its # initial value, the breadth-first search above failed to generate code. In # this case, raise an exception. # # Note that this test is inexpensive, as the third character of the # "func_root_code" code snippet is guaranteed to differ from that of # "func_wrapper_code" code snippet if this function behaved as expected, # which it should have... but may not have, which is why we're testing. if func_wrapper_code == func_root_code: raise BeartypeDecorHintPepException( f'{EXCEPTION_PREFIX_HINT}{repr(hint_root)} unchecked.') # Else, the breadth-first search above successfully generated code. # ..................{ CODE ~ scope }.................. # If type-checking for the root pith requires the type stack, pass a hidden # parameter to this wrapper function exposing this stack. if cls_stack: func_wrapper_scope[ARG_NAME_CLS_STACK] = cls_stack # Else, type-checking for the root pith requires *NO* type stack. # If type-checking for the root pith requires a pseudo-random integer, pass # a hidden parameter to this wrapper function exposing the # random.getrandbits() function required to generate this integer. if is_var_random_int_needed: func_wrapper_scope[ARG_NAME_GETRANDBITS] = getrandbits # Else, type-checking for the root pith requires *NO* pseudo-random integer. # ..................{ CODE ~ suffix }.................. # Tuple of the unqualified classnames referred to by all relative forward # references visitable from this hint converted from that set to reduce # space consumption after memoization by @callable_cached, defined as... hint_refs_type_basename_tuple = ( # If *NO* relative forward references are visitable from this root # hint, the empty tuple; () if hint_refs_type_basename is None else # Else, that set converted into a tuple. tuple(hint_refs_type_basename) ) # Return all metadata required by higher-level callers. return ( func_wrapper_code, func_wrapper_scope, hint_refs_type_basename_tuple, ) beartype-0.18.5/beartype/_check/code/codescope.py000066400000000000000000000677141461113517100217160ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator PEP-compliant code wrapper scope utilities** (i.e., functions handling the possibly nested lexical scopes enclosing wrapper functions generated by the :func:`beartype.beartype` decorator). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... #FIXME: Hah-hah! Finally figured out how to do recursive type hints... mostly. #It's a two-parter consisting of: #* *PART I.* In the first part: # * Refactor our code generation algorithm to additionally maintain a stack of # all parent type hints of the currently visited type hint. Note that we need # to do this anyway to support the __beartype_hint__() protocol. See "FIXME:" # comments in the "beartype.plug._plughintable" submodule pertaining to that # protocol for further details on properly building out this stack. # * When that algorithm visits a forward reference: # * That algorithm calls the express_func_scope_type_ref() function # generating type-checking code for that reference. Refactor that call to # additionally pass that stack of parent hints to that function. # * Refactor the express_func_scope_type_ref() function to: # * If the passed forward reference is relative, additionally return that # stack in the returned 3-tuple # "(forwardref_expr, forwardrefs_class_basename, forwardref_parent_hints)", # where "forwardref_parent_hints" is that stack. #* *PART II.* In the second part: # * Refactor the beartype._decor.wrap.wrapmain._unmemoize_func_wrapper_code() # function to additionally: # * If the passed forward reference is relative *AND* the unqualified # basename of an existing attribute in a local or global scope of the # currently decorated callable *AND* the value of that attribute is a # parent type hint on the stack of parent type hints returned by the # previously called express_func_scope_type_ref() function, then # *THIS REFERENCE INDICATES A RECURSIVE TYPE HINT.* In this case: # * Replace this forward reference with a new recursive type-checking # "beartype._check.forward.reference.fwdrefabc.BeartypeForwardRef_{forwardref}" # subclass whose is_instance() tester method recursively calls itself # indefinitely. If doing so generates a "RecursionError", @beartype # considers that the user's problem. *wink* # #Done and done. Phew! # ....................{ IMPORTS }.................... from beartype.roar import BeartypeDecorHintNonpepException from beartype.typing import ( Dict, List, Optional, Tuple, ) from beartype._cave._cavemap import NoneTypeOr from beartype._check.forward.reference.fwdrefmake import ( make_forwardref_indexable_subtype) from beartype._check.forward.reference.fwdreftest import is_forwardref from beartype._check.code.snip.codesnipstr import ( CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_PREFIX, CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_SUFFIX, ) from beartype._data.cls.datacls import TYPES_SET_OR_TUPLE from beartype._data.hint.datahinttyping import ( LexicalScope, Pep484585ForwardRef, SetOrTupleTypes, TypeOrTupleTypes, TupleTypes, ) from beartype._util.cls.pep.utilpep3119 import ( die_unless_type_isinstanceable, die_unless_object_isinstanceable, ) from beartype._util.cls.utilclstest import is_type_builtin from beartype._util.func.utilfuncscope import add_func_scope_attr from beartype._util.hint.pep.proposal.pep484585.utilpep484585ref import ( get_hint_pep484585_ref_names) from beartype._util.utilobject import get_object_type_basename from collections.abc import Set # ....................{ ADDERS ~ type }.................... #FIXME: Unit test us up, please. def add_func_scope_ref( # Mandatory parameters. func_scope: LexicalScope, ref_module_name: Optional[str], ref_name: str, # Optional parameters. exception_prefix: str = 'Globally or locally scoped forward reference ', ) -> str: ''' Add a new **scoped forward reference proxy** (i.e., new key-value pair of the passed dictionary mapping from the name to value of each globally or locally scoped attribute externally accessed elsewhere, whose key is a machine-readable name internally generated by this function to uniquely refer to a new forward reference proxy proxying the class with the passed attribute name residing in the module with the passed module name) to the passed scope *and* return that name. Parameters ---------- func_scope : LexicalScope Local or global scope to add this class or tuple of classes to. ref_module_name : Optional[str] Possibly undefined fully-qualified module name referred to by this forward reference. ref_name : str Possibly unqualified classname referred to by this forward reference. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to a sensible string. Returns ------- str Name of this forward reference proxy in this scope generated by this function. Raises ------ _BeartypeUtilCallableException If an attribute with the same name as that internally generated by this adder but having a different value already exists in this scope. This adder uniquifies names by object identifier and should thus *never* generate name collisions. This exception is thus intentionally raised as a private rather than public exception. ''' # Forward reference proxy referring to this class. hint_ref = make_forwardref_indexable_subtype(ref_module_name, ref_name) # Name of a new parameter passing this forward reference proxy. hint_ref_arg_name = add_func_scope_attr( func_scope=func_scope, attr=hint_ref) # Return this name. return hint_ref_arg_name # ....................{ ADDERS ~ type }.................... #FIXME: Unit test us up, please. def add_func_scope_type_or_types( # Mandatory parameters. func_scope: LexicalScope, type_or_types: TypeOrTupleTypes, # Optional parameters. exception_prefix: str = ( 'Globally or locally scoped class or tuple of classes '), ) -> str: ''' Add a new **scoped class or tuple of classes** (i.e., new key-value pair of the passed dictionary mapping from the name to value of each globally or locally scoped attribute externally accessed elsewhere, whose key is a machine-readable name internally generated by this function to uniquely refer to the passed class or tuple of classes and whose value is that class or tuple) to the passed scope *and* return that name. This function additionally caches this tuple with the beartypistry singleton to reduce space consumption for tuples duplicated across the active Python interpreter. Parameters ---------- func_scope : LexicalScope Local or global scope to add this class or tuple of classes to. type_or_types : TypeOrTupleTypes Arbitrary class or tuple of classes to be added to this scope. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to a sensible string. Returns ------- str Name of this class or tuple in this scope generated by this function. Raises ------ BeartypeDecorHintNonpepException If this hint is either: * Neither a class nor tuple. * A tuple that is empty. BeartypeDecorHintPep3119Exception If hint is: * A class that is *not* isinstanceable (i.e., passable as the second argument to the :func:`isinstance` builtin). * A tuple of one or more items that are *not* isinstanceable classes. _BeartypeUtilCallableException If an attribute with the same name as that internally generated by this adder but having a different value already exists in this scope. This adder uniquifies names by object identifier and should thus *never* generate name collisions. This exception is thus intentionally raised as a private rather than public exception. ''' # Return either... return ( # If this hint is a class, the name of a new parameter passing this # class; add_func_scope_type( func_scope=func_scope, cls=type_or_types, exception_prefix=exception_prefix, ) if isinstance(type_or_types, type) else # Else, this hint is *NOT* a class. In this case: # * If this hint is a tuple of classes, the name of a new parameter # passing this tuple. # * Else, raise an exception. add_func_scope_types( func_scope=func_scope, types=type_or_types, exception_prefix=exception_prefix, ) ) def add_func_scope_type( # Mandatory parameters. func_scope: LexicalScope, cls: type, # Optional parameters. exception_prefix: str = 'Globally or locally scoped class ', ) -> str: ''' Add a new **scoped class** (i.e., new key-value pair of the passed dictionary mapping from the name to value of each globally or locally scoped attribute externally accessed elsewhere, whose key is a machine-readable name internally generated by this function to uniquely refer to the passed class and whose value is that class) to the passed scope *and* return that name. Parameters ---------- func_scope : LexicalScope Local or global scope to add this class to. cls : type Arbitrary class to be added to this scope. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to a sensible string. Returns ------- str Name of this class in this scope generated by this function. Raises ------ BeartypeDecorHintPep3119Exception If this class is *not* isinstanceable (i.e., passable as the second argument to the :func:`isinstance` builtin). _BeartypeUtilCallableException If an attribute with the same name as that internally generated by this adder but having a different value already exists in this scope. This adder uniquifies names by object identifier and should thus *never* generate name collisions. This exception is thus intentionally raised as a private rather than public exception. ''' # If this object is *NOT* an isinstanceable class, raise an exception. die_unless_type_isinstanceable(cls=cls, exception_prefix=exception_prefix) # Else, this object is an isinstanceable class. # Return either... return ( # If this type is a builtin (i.e., globally accessible C-based type # requiring *no* explicit importation), the unqualified basename of # this type as is, as this type requires no parametrization; get_object_type_basename(cls) if is_type_builtin(cls) else # Else, the name of a new parameter passing this class. add_func_scope_attr( func_scope=func_scope, attr=cls, exception_prefix=exception_prefix) ) def add_func_scope_types( # Mandatory parameters. func_scope: LexicalScope, types: SetOrTupleTypes, # Optional parameters. is_unique: Optional[bool] = None, exception_prefix: str = ( 'Globally or locally scoped set or tuple of classes '), ) -> str: ''' Add a new **scoped tuple of classes** (i.e., new key-value pair of the passed dictionary mapping from the name to value of each globally or locally scoped attribute externally accessed elsewhere, whose key is a machine-readable name internally generated by this function to uniquely refer to the passed set or tuple of classes and whose value is that tuple) to the passed scope *and* return that machine-readable name. This function additionally caches this tuple with the :data:`._tuple_union_to_tuple_union` dictionary to reduce space consumption for tuples duplicated across the active Python interpreter. Parameters ---------- func_scope : LexicalScope Local or global scope to add this object to. types : SetOrTupleOfTypes Set or tuple of arbitrary types to be added to this scope. is_unique : Optional[bool] Tri-state boolean governing whether this function attempts to deduplicate types in the ``types`` iterable. Specifically, either: * :data:`True`, in which case the caller guarantees ``types`` to contain *no* duplicate types. * :data:`False`, in which case this function assumes ``types`` to contain duplicate types by internally (in order): #. Coercing this tuple into a set, thus implicitly ignoring both duplicates and ordering of types in this tuple. #. Coercing that set back into another tuple. #. If these two tuples differ, the passed tuple contains one or more duplicates; in this case, the duplicate-free tuple is cached and passed. #. Else, the passed tuple contains no duplicates; in this case, the passed tuple is cached and passed. * :data:`None`, in which case this function reduces this parameter to either: * :data:`True` if ``types`` is a :class:`tuple`. * :data:`False` if ``types`` is a :class:`set`. This tri-state boolean does *not* simply enable an edge-case optimization, though it certainly does that; this boolean enables callers to guarantee that this function caches and passes the passed tuple rather than a new tuple internally created by this function. Defaults to :data:`None`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to a sensible string. Returns ------- str Name of this tuple in this scope generated by this function. Raises ------ BeartypeDecorHintNonpepException If this hint is either: * Neither a set nor tuple. * A set or tuple that is empty. BeartypeDecorHintPep3119Exception If one or more items of this hint are *not* isinstanceable classes (i.e., classes passable as the second argument to the :func:`isinstance` builtin). _BeartypeUtilCallableException If an attribute with the same name as that internally generated by this adder but having a different value already exists in this scope. This adder uniquifies names by object identifier and should thus *never* generate name collisions. This exception is thus intentionally raised as a private rather than public exception. ''' assert isinstance(is_unique, NoneTypeOr[bool]), ( f'{repr(is_unique)} neither bool nor "None".') # ....................{ VALIDATE }.................... # If this container is neither a set nor tuple, raise an exception. if not isinstance(types, TYPES_SET_OR_TUPLE): raise BeartypeDecorHintNonpepException( f'{exception_prefix}{repr(types)} neither set nor tuple.') # Else, this container is either a set or tuple. # # If this container is empty, raise an exception. elif not types: raise BeartypeDecorHintNonpepException(f'{exception_prefix}empty.') # Else, this container is non-empty. # # If this container only contains one type, register only this type. elif len(types) == 1: return add_func_scope_type( # The first and only item of this container, accessed as either: # * If this container is a tuple, that item with fast indexing. # * If this container is a set, that item with slow iteration. cls=types[0] if isinstance(types, tuple) else next(iter(types)), func_scope=func_scope, exception_prefix=exception_prefix, ) # Else, this container either contains two or more types. # If the caller did *NOT* explicitly pass the "is_unique" parameter, default # this parameter to true *ONLY* if this container is a set. if is_unique is None: is_unique = isinstance(types, set) # Else, the caller explicitly passed the "is_unique" parameter. # # In either case, "is_unique" is now a proper bool. assert isinstance(is_unique, bool) # ....................{ FORWARDREF }.................... # True only if this container contains one or more beartype-specific forward # reference proxies. Although these proxies are technically isinstanceable # classes, attempting to pass these proxies as the second parameter to the # isinstance() builtin also raises exceptions when the underlying # user-defined classes proxied by these proxies have yet to be declared. # Since these proxies are thus *MUCH* more fragile than standard classes, we # reduce the likelihood of exceptions by deprioritizing these proxies in # this container (i.e., moving these proxies to the end of this container). is_types_ref = False # For each type in this container... for cls in types: # If this type is a beartype-specific forward reference proxy... if is_forwardref(cls): # print(f'Found forward reference proxy {repr(cls)}...') # Note that this container contains at least one such proxy. is_types_ref = True # Halt iteration. break # If this container contains at least one such proxy... if is_types_ref: # List of all such proxies in this container. # # Note that we intentionally avoid instantiating this pair of lists # above in the common case that this container contains no such proxies. types_ref: List[type] = [] # List of all other types in this container (i.e., normal types that are # *NOT* beartype-specific forward reference proxies). types_nonref: List[type] = [] # For each type in this container... for cls in types: # If this type is such a proxy, append this proxy to the list of all # such proxies. if is_forwardref(cls): types_ref.append(cls) # Else, this type is *NOT* such a proxy. In this case... else: # print(f'Appending non-forward reference proxy {repr(cls)}...') # If this non-proxy is *NOT* an isinstanceable class, raise an # exception. # # Note that the companion "types_ref" tuple is intentionally # *NOT* validated above. Why? Because doing so would prematurely # invoke the __instancecheck__() dunder method on the metaclass # of the proxies in that tuple, which would then erroneously # attempt to resolve the possibly undefined types to which those # proxies refer. Instead, simply accept that tuple of proxies as # is for now and defer validating those proxies for later. die_unless_type_isinstanceable( cls=cls, exception_prefix=exception_prefix) # Append this proxy to the list of all non-proxy types types_nonref.append(cls) # If the caller guaranteed these tuples to be duplicate-free, # efficiently concatenate these lists into a tuple such that all # non-proxy types appear *BEFORE* all proxy types. if is_unique: types = tuple(types_nonref + types_ref) # Else, the caller failed to guarantee these tuples to be # duplicate-free. In this case, coerce these tuples into (in order): # * Sets, thus ignoring duplicates and ordering. # * Back into duplicate-free tuples. else: types = tuple(set(types_nonref)) + tuple(set(types_ref)) # Else, the caller guaranteed these tuples to be duplicate-free. # Else, this container contains *NO* such proxies. In this case, preserve # the ordering of items in this container as is. else: # If this container is a set, coerce this frozenset into a tuple. if isinstance(types, Set): types = tuple(types) # Else, this container is *NOT* a set. By elimination, this container # should now be a tuple. # # In either case, this container should now be a tuple. # If this container is *NOT* a tuple or is a tuple containing one or # more items that are *NOT* isinstanceable classes, raise an exception. die_unless_object_isinstanceable( obj=types, exception_prefix=exception_prefix) # Else, this container is a tuple of only isinstanceable classes. # If the caller failed to guarantee this tuple to be duplicate-free, # coerce this tuple into (in order): # * A set, thus ignoring duplicates and ordering. # * Back into a duplicate-free tuple. if not is_unique: # print(f'Uniquifying type tuple {repr(types)} to...') types = tuple(set(types)) # print(f'...uniquified type tuple {repr(types)}.') # Else, the caller guaranteed this tuple to be duplicate-free. # In either case, this container is now guaranteed to be a tuple containing # only duplicate-free classes. assert isinstance(types, tuple), ( f'{exception_prefix}{repr(types)} not tuple.') # ....................{ CACHE }.................... # If this tuple has *NOT* already been cached, do so. if types not in _tuple_union_to_tuple_union: _tuple_union_to_tuple_union[types] = types # Else, this tuple has already been cached. In this case, deduplicate this # tuple by reusing the previously cached tuple. else: types = _tuple_union_to_tuple_union[types] # ....................{ RETURN }.................... # Return the name of a new parameter passing this tuple. return add_func_scope_attr( attr=types, func_scope=func_scope, exception_prefix=exception_prefix) # ....................{ EXPRESSERS ~ type }.................... def express_func_scope_type_ref( # Mandatory parameters. func_scope: LexicalScope, forwardref: Pep484585ForwardRef, forwardrefs_class_basename: Optional[set], # Optional parameters. exception_prefix: str = 'Globally or locally scoped forward reference ', ) -> Tuple[str, Optional[set]]: ''' Express the passed :pep:`484`- or :pep:`585`-compliant **forward reference** (i.e., fully-qualified or unqualified name of an arbitrary class that typically has yet to be declared) as a Python expression evaluating to this forward reference when accessed via the beartypistry singleton added as a new key-value pair of the passed dictionary, whose key is the string :attr:`beartype._check.checkmagic.ARG_NAME_TYPISTRY` and whose value is the beartypistry singleton. Parameters ---------- func_scope : LexicalScope Local or global scope to add this forward reference to. forwardref : Pep484585ForwardRef Forward reference to be expressed relative to this scope. forwardrefs_class_basename : Optional[set] Set of all existing **relative forward references** (i.e., unqualified basenames of all types referred to by all relative forward references relative to this scope) if any *or* :data:`None` otherwise (i.e., if no relative forward references have been expressed relative to this scope). exception_prefix : str, optional Human-readable substring describing this forward reference in exception exception message. Defaults to a sensible string. Returns ------- Tuple[str, Optional[set]] 2-tuple ``(forwardref_expr, forwardrefs_class_basename)``, where: * ``forwardref_expr`` is the Python expression evaluating to this forward reference when accessed via the beartypistry singleton added to this scope. * ``forwardrefs_class_basename`` is either: * If this forward reference is a fully-qualified classname, the passed ``forwardrefs_class_basename`` set as is. * If this forward reference is an unqualified classname, either: * If the passed ``forwardrefs_class_basename`` set is *not* :data:`None`, this set with this classname added to it. * Else, a new set containing only this classname. Raises ------ BeartypeDecorHintForwardRefException If this forward reference is *not* actually a forward reference. ''' # Possibly undefined fully-qualified module name and possibly unqualified # classname referred to by this forward reference. ref_module_name, ref_name = get_hint_pep484585_ref_names( hint=forwardref, exception_prefix=exception_prefix) # If either... if ( # This reference was instantiated with a module name... ref_module_name or # This classname contains one or more "." characters and is thus already # (...hopefully) fully-qualified... '.' in ref_name # Then this classname is either absolute *OR* relative to some module. In # either case, the class referred to by this reference can now be # dynamically imported at a later time. In this case... ): # Name of the hidden parameter providing this forward reference # proxy to be passed to this wrapper function. ref_expr = add_func_scope_ref( func_scope=func_scope, ref_module_name=ref_module_name, ref_name=ref_name, exception_prefix=exception_prefix, ) # Else, this classname is unqualified. In this case... else: assert isinstance(forwardrefs_class_basename, NoneTypeOr[set]), ( f'{repr(forwardrefs_class_basename)} neither set nor "None".') # If this set of unqualified classnames referred to by all relative # forward references has yet to be instantiated, do so. if forwardrefs_class_basename is None: forwardrefs_class_basename = set() # In any case, this set now exists. # Add this unqualified classname to this set. forwardrefs_class_basename.add(ref_name) # Placeholder substring to be replaced by the caller with a Python # expression evaluating to this unqualified classname canonicalized # relative to the module declaring the currently decorated callable # when accessed via the private "__beartypistry" parameter. ref_expr = ( f'{CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_PREFIX}' f'{ref_name}' f'{CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_SUFFIX}' ) # Return a 2-tuple of this expression and set of unqualified classnames. return ref_expr, forwardrefs_class_basename # ....................{ PRIVATE ~ globals }.................... _tuple_union_to_tuple_union: Dict[TupleTypes, TupleTypes] = {} ''' **Tuple union cache** (i.e., dictionary mapping from each tuple union passed to the :func:`.add_func_scope_types` adder to that same union, preventing tuple unions from being duplicated across calls to that adder). This cache serves a dual purpose. Notably, this cache both enables: * External callers to iterate over all previously instantiated forward reference proxies. This is particularly useful when responding to module reloading, which requires that *all* previously cached types be uncached. * A minor reduction in space complexity by de-duplicating duplicating tuple unions. Since the existing ``callable_cached`` decorator could trivially do so as well, however, this is only a negligible side effect. ''' beartype-0.18.5/beartype/_check/code/snip/000077500000000000000000000000001461113517100203325ustar00rootroot00000000000000beartype-0.18.5/beartype/_check/code/snip/__init__.py000066400000000000000000000000001461113517100224310ustar00rootroot00000000000000beartype-0.18.5/beartype/_check/code/snip/codesnipcls.py000066400000000000000000000067711461113517100232250ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **type-checking expression snippet classes** (i.e., low-level classes dynamically and efficiently generating substrings intended to be interpolated into boolean expressions type-checking arbitrary objects against various type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._check.checkmagic import VAR_NAME_PITH_PREFIX # ....................{ SUBCLASSES }.................... class PithIndexToVarName(dict): ''' **Local pith variable name cache** (i.e., dictionary mapping from the 1-based index uniquely identifying each **pith** (i.e., current parameter or return value *or* item contained in the current parameter or return value type-checked by the current call in the body of a runtime type-checker dynamically generated by :mod:`beartype`) to the corresponding name of a prospective local variable assigned that value in that body). See Also -------- :data:`.PITH_INDEX_TO_VAR_NAME` Singleton instance of this dictionary subclass. ''' # ....................{ DUNDERS }.................... def __missing__(self, pith_index: int) -> str: ''' Dunder method explicitly called by the superclass :meth:`dict.__getitem__` method implicitly called on the first ``[``- and ``]``-delimited attempt to access a local pith variable name uniquely identified by the passed 1-based index. Parameters ---------- pith_index : int 1-based index suffixing the local pith variable name to be created, cached, and returned. Returns ------- str Prospective name of this local pith variable. Raises ------ AssertionError If either: * ``pith_level`` is *not* an integer. * ``pith_level`` is a **negative integer** (i.e., less than 0). ''' assert isinstance(pith_index, int), f'{repr(pith_index)} not integer.' assert pith_index >= 0, f'{pith_index} < 0.' # print(f'Generating indentation level {indent_level}...') # Prospective name of this local pith variable. pith_var_name = f'{VAR_NAME_PITH_PREFIX}{pith_index}' # Cache this name. self[pith_index] = pith_var_name # Return this name. return pith_var_name # ....................{ MAPPINGS }.................... PITH_INDEX_TO_VAR_NAME = PithIndexToVarName() ''' **Indentation cache singleton** (i.e., global dictionary efficiently mapping from 1-based indentation levels to the corresponding indentation string constant). Caveats ------- **Indentation string constants should always be accessed via this cache rather than manually generated.** This cache dynamically creates and efficiently caches indentation string constants on the first access of those constants, obviating the performance cost of string formatting required to create these constants. Examples -------- .. code-block:: pycon >>> from beartype._check.code.snip.codesnipcls import PITH_INDEX_TO_VAR_NAME >>> PITH_INDEX_TO_VAR_NAME[1] '__beartype_pith_1' >>> PITH_INDEX_TO_VAR_NAME[2] '__beartype_pith_2' ''' beartype-0.18.5/beartype/_check/code/snip/codesnipstr.py000066400000000000000000000616161461113517100232530ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **type-checking expression snippets** (i.e., triple-quoted pure-Python string constants formatted and concatenated together to dynamically generate boolean expressions type-checking arbitrary objects against various type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._check.checkmagic import ( VAR_NAME_RANDOM_INT, ) from collections.abc import Callable # ....................{ HINT ~ placeholder : child }.................... CODE_HINT_CHILD_PLACEHOLDER_PREFIX = '@[' ''' Prefix of each **placeholder hint child type-checking substring** (i.e., placeholder to be globally replaced by a Python code snippet type-checking the current pith expression against the currently iterated child hint of the currently visited parent hint). ''' CODE_HINT_CHILD_PLACEHOLDER_SUFFIX = ')!' ''' Suffix of each **placeholder hint child type-checking substring** (i.e., placeholder to be globally replaced by a Python code snippet type-checking the current pith expression against the currently iterated child hint of the currently visited parent hint). ''' # ....................{ HINT ~ placeholder : forwardref }.................... CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_PREFIX = '${FORWARDREF:' ''' Prefix of each **placeholder unqualified forward reference classname substring** (i.e., placeholder to be globally replaced by a Python code snippet evaluating to the currently visited unqualified forward reference hint canonicalized into a fully-qualified classname relative to the external caller-defined module declaring the currently decorated callable). ''' CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_SUFFIX = ']?' ''' Suffix of each **placeholder unqualified forward reference classname substring** (i.e., placeholder to be globally replaced by a Python code snippet evaluating to the currently visited unqualified forward reference hint canonicalized into a fully-qualified classname relative to the external caller-defined module declaring the currently decorated callable). ''' # ....................{ HINT ~ pep : 572 }.................... CODE_PEP572_PITH_ASSIGN_EXPR = '''{pith_curr_var_name} := {pith_curr_expr}''' ''' Assignment expression assigning the full Python expression yielding the value of the current pith to a unique local variable, enabling child type hints to obtain this pith via this efficient variable rather than via this inefficient full Python expression. ''' #FIXME: Preserved for posterity in the likelihood we'll need this again. *sigh* # CODE_PEP572_PITH_ASSIGN_AND = ''' # {indent_curr} # Localize this pith as a stupidly fast assignment expression. # {indent_curr} ({pith_curr_assign_expr}) is {pith_curr_var_name} and''' # ''' # Code snippet embedding an assignment expression assigning the full Python # expression yielding the value of the current pith to a unique local variable. # # This snippet is itself intended to be embedded in higher-level code snippets as # the first child expression of those snippets, enabling subsequent expressions in # those snippets to efficiently obtain this pith via this efficient variable # rather than via this inefficient full Python expression. # # This snippet is a tautology that is guaranteed to evaluate to :data:`True` whose # side effect is this assignment expression. Note that there exist numerous less # efficient alternatives, including: # # * ``({pith_curr_assign_expr}).__class__``, which is also guaranteed to evaluate # to :data:`True` but which implicitly triggers the ``__getattr__()`` dunder # method and thus incurs a performance penalty for user-defined objects # inefficiently overriding that method. # * ``isinstance({pith_curr_assign_expr}, object)``, which is also guaranteed to # evaluate :data:`True` but which is surprisingly inefficient in all cases. # ''' # ....................{ HINT ~ pep : (484|585) : generic }.................... CODE_PEP484585_GENERIC_PREFIX = '''( {indent_curr} # True only if this pith is of this generic type. {indent_curr} isinstance({pith_curr_assign_expr}, {hint_curr_expr}) and''' ''' :pep:`484`- and :pep:`585`-compliant code snippet prefixing all code type-checking the current pith against each unerased pseudo-superclass subclassed by a :pep:`484`-compliant **generic** (i.e., PEP-compliant type hint subclassing a combination of one or more of the :mod:`typing.Generic` superclass, the :mod:`typing.Protocol` superclass, and/or other :mod:`typing` non-class objects). Caveats ------- The ``{indent_curr}`` format variable is intentionally brace-protected to efficiently defer its interpolation until the complete PEP-compliant code snippet type-checking the current pith against *all* subscripted arguments of this parent type has been generated. ''' CODE_PEP484585_GENERIC_SUFFIX = ''' {indent_curr})''' ''' :pep:`484`- and :pep:`585`-compliant code snippet suffixing all code type-checking the current pith against each unerased pseudo-superclass subclassed by a :pep:`484`-compliant generic. ''' CODE_PEP484585_GENERIC_CHILD = ''' {{indent_curr}} # True only if this pith deeply satisfies this unerased {{indent_curr}} # pseudo-superclass of this generic. {{indent_curr}} {hint_child_placeholder} and''' ''' :pep:`484`- and :pep:`585`-compliant code snippet type-checking the current pith against the current unerased pseudo-superclass subclassed by a :pep:`484`-compliant generic. Caveats ------- The caller is required to manually slice the trailing suffix ``" and"`` after applying this snippet to the last unerased pseudo-superclass of such a generic. While there exist alternate and more readable means of accomplishing this, this approach is the optimally efficient. The ``{indent_curr}`` format variable is intentionally brace-protected to efficiently defer its interpolation until the complete PEP-compliant code snippet type-checking the current pith against *all* subscripted arguments of this parent type has been generated. ''' # ....................{ HINT ~ pep : (484|585) : mapping }.................... CODE_PEP484585_MAPPING = '''( {indent_curr} # True only if this pith is of this mapping type *AND*... {indent_curr} isinstance({pith_curr_assign_expr}, {hint_curr_expr}) and {indent_curr} # True only if either this mapping is empty *OR* this mapping {indent_curr} # is non-empty and... {indent_curr} (not {pith_curr_var_name} or ({func_curr_code_key_value})) {indent_curr})''' ''' :pep:`484`- and :pep:`585`-compliant code snippet type-checking the current pith against a parent **standard mapping type** (i.e., type hint subscripted by exactly two child type hints constraining *all* key-value pairs of this pith, which necessarily satisfies the :class:`collections.abc.Mapping` protocol with guaranteed :math:`O(1)` indexation of at least the first pair). Caveats ------- **This snippet cannot contain ternary conditionals.** See :data:`.CODE_PEP484585_SEQUENCE_ARGS_1` for further commentary. There exist numerous means of accessing the first key-value pair of a dictionary. The approach taken here is well-known to be the fastest, as documented at this `StackOverflow answer`_. .. _StackOverflow answer: https://stackoverflow.com/a/70490285/2809027 ''' CODE_PEP484585_MAPPING_KEY_ONLY_PITH_CHILD_EXPR = ( '''next(iter({pith_curr_var_name}))''') ''' :pep:`484`- and :pep:`585`-compliant Python expression efficiently yielding the first key of the current mapping pith. ''' CODE_PEP484585_MAPPING_VALUE_ONLY_PITH_CHILD_EXPR = ( '''next(iter({pith_curr_var_name}.values()))''') ''' :pep:`484`- and :pep:`585`-compliant Python expression efficiently yielding the first value of the current mapping pith when type-checking *only* the values of this mapping (i.e., when the keys of this mapping are ignorable). ''' CODE_PEP484585_MAPPING_KEY_VALUE_PITH_CHILD_EXPR = ( '''{pith_curr_var_name}[{pith_curr_key_var_name}]''') ''' :pep:`484`- and :pep:`585`-compliant Python expression efficiently yielding the first value of the current mapping pith when type-checking both the keys *and* values of this mapping (i.e., when the keys of this mapping are unignorable). ''' CODE_PEP484585_MAPPING_KEY_ONLY = ''' {indent_curr} # True only if this key satisfies this hint. {indent_curr} {hint_key_placeholder}''' ''' :pep:`484`- and :pep:`585`-compliant code snippet type-checking *only* the first key of the current pith against *only* the key child type hint subscripting a parent standard mapping type. This snippet intentionally avoids type-checking values and is thus suitable for type-checking mappings with ignorable value child type hints (e.g., ``dict[str, object]``). ''' CODE_PEP484585_MAPPING_VALUE_ONLY = ''' {indent_curr} # True only if this value satisfies this hint. {indent_curr} {hint_value_placeholder}''' ''' :pep:`484`- and :pep:`585`-compliant code snippet type-checking *only* the first value of the current pith against *only* the value child type hint subscripting a parent standard mapping type. This snippet intentionally avoids type-checking keys and is thus suitable for type-checking mappings with ignorable key child type hints (e.g., ``dict[object, str]``). ''' CODE_PEP484585_MAPPING_KEY_VALUE = f''' {{indent_curr}} # Localize the first key of this mapping. {{indent_curr}} ({{pith_curr_key_var_name}} := {CODE_PEP484585_MAPPING_KEY_ONLY_PITH_CHILD_EXPR}) is {{pith_curr_key_var_name}} and {{indent_curr}} # True only if this key satisfies this hint. {{indent_curr}} {{hint_key_placeholder}} and {{indent_curr}} # True only if this value satisfies this hint. {{indent_curr}} {{hint_value_placeholder}}''' ''' :pep:`484`- and :pep:`585`-compliant code snippet type-checking *only* the first key-value pair of the current pith against *only* the key and value child type hints subscripting a parent standard mapping type. This snippet intentionally type-checks both keys and values is thus unsuitable for type-checking mappings with ignorable key or value child type hints (e.g., ``dict[object, str]``, ``dict[str, object]``). ''' # ....................{ HINT ~ pep : (484|585) : sequence }.................... CODE_PEP484585_SEQUENCE_ARGS_1 = '''( {indent_curr} # True only if this pith is of this sequence type *AND*... {indent_curr} isinstance({pith_curr_assign_expr}, {hint_curr_expr}) and {indent_curr} # True only if either this sequence is empty *OR* this sequence {indent_curr} # is both non-empty and a random item satisfies this hint. {indent_curr} (not {pith_curr_var_name} or {hint_child_placeholder}) {indent_curr})''' ''' :pep:`484`- and :pep:`585`-compliant code snippet type-checking the current pith against a parent **standard sequence type** (i.e., type hint subscripted by exactly one child type hint constraining *all* items of this pith, which necessarily satisfies the :class:`collections.abc.Sequence` protocol with guaranteed :math:`O(1)` indexation across all sequence items). Caveats ------- **This snippet cannot contain ternary conditionals.** For unknown reasons suggesting a critical defect in the current implementation of Python 3.8's assignment expressions, this snippet raises :class:`UnboundLocalError` exceptions resembling the following when this snippet contains one or more ternary conditionals: UnboundLocalError: local variable '__beartype_pith_1' referenced before assignment In particular, the initial draft of this snippet guarded against empty sequences with a seemingly reasonable ternary conditional: .. code-block:: python CODE_PEP484585_SEQUENCE_ARGS_1 = \'\'\'( {indent_curr} isinstance({pith_curr_assign_expr}, {hint_curr_expr}) and {indent_curr} {hint_child_placeholder} if {pith_curr_var_name} else True {indent_curr})\'\'\' That should behave as expected, but doesn't, presumably due to obscure scoping rules and a non-intuitive implementation of ternary conditionals in CPython. Ergo, the current version of this snippet guards against empty sequences with disjunctions and conjunctions (i.e., ``or`` and ``and`` operators) instead. Happily, the current version is more efficient than the equivalent approach based on ternary conditional (albeit slightly less intuitive). ''' CODE_PEP484585_SEQUENCE_ARGS_1_PITH_CHILD_EXPR = ( f'''{{pith_curr_var_name}}[{VAR_NAME_RANDOM_INT} % len({{pith_curr_var_name}})]''') ''' :pep:`484`- and :pep:`585`-compliant Python expression yielding the value of a randomly indexed item of the current sequence pith. ''' # ....................{ HINT ~ pep : (484|585) : tuple }.................... CODE_PEP484585_TUPLE_FIXED_PREFIX = '''( {indent_curr} # True only if this pith is a tuple. {indent_curr} isinstance({pith_curr_assign_expr}, tuple) and''' ''' :pep:`484`- and :pep:`585`-compliant code snippet prefixing all code type-checking the current pith against each subscripted child hint of an itemized :class:`typing.Tuple` type of the form ``typing.Tuple[{typename1}, {typename2}, ..., {typenameN}]``. ''' CODE_PEP484585_TUPLE_FIXED_SUFFIX = ''' {indent_curr})''' ''' :pep:`484`- and :pep:`585`-compliant code snippet suffixing all code type-checking the current pith against each subscripted child hint of an itemized :class:`typing.Tuple` type of the form ``typing.Tuple[{typename1}, {typename2}, ..., {typenameN}]``. ''' CODE_PEP484585_TUPLE_FIXED_EMPTY = ''' {{indent_curr}} # True only if this tuple is empty. {{indent_curr}} not {pith_curr_var_name} and''' ''' :pep:`484`- and :pep:`585`-compliant code snippet prefixing all code type-checking the current pith to be empty against an itemized :class:`typing.Tuple` type of the non-standard form ``typing.Tuple[()]``. See Also -------- :data:`CODE_PEP484585_TUPLE_FIXED_NONEMPTY_CHILD` Further details. ''' CODE_PEP484585_TUPLE_FIXED_LEN = ''' {{indent_curr}} # True only if this tuple is of the expected length. {{indent_curr}} len({pith_curr_var_name}) == {hint_childs_len} and''' ''' :pep:`484`- and :pep:`585`-compliant code snippet prefixing all code type-checking the current pith to be of the expected length against an itemized :class:`typing.Tuple` type of the non-standard form ``typing.Tuple[()]``. See Also -------- :data:`CODE_PEP484585_TUPLE_FIXED_NONEMPTY_CHILD` Further details. ''' CODE_PEP484585_TUPLE_FIXED_NONEMPTY_CHILD = ''' {{indent_curr}} # True only if this item of this non-empty tuple deeply {{indent_curr}} # satisfies this child hint. {{indent_curr}} {hint_child_placeholder} and''' ''' :pep:`484`- and :pep:`585`-compliant code snippet type-checking the current pith against the current child hint subscripting an itemized :class:`typing.Tuple` type of the form ``typing.Tuple[{typename1}, {typename2}, ..., {typenameN}]``. Caveats ------- The caller is required to manually slice the trailing suffix ``" and"`` after applying this snippet to the last subscripted child hint of an itemized :class:`typing.Tuple` type. While there exist alternate and more readable means of accomplishing this, this approach is the optimally efficient. The ``{indent_curr}`` format variable is intentionally brace-protected to efficiently defer its interpolation until the complete PEP-compliant code snippet type-checking the current pith against *all* subscripted arguments of this parent type has been generated. ''' CODE_PEP484585_TUPLE_FIXED_NONEMPTY_PITH_CHILD_EXPR = ( '''{pith_curr_var_name}[{pith_child_index}]''') ''' :pep:`484`- and :pep:`585`-compliant Python expression yielding the value of the currently indexed item of the current pith (which, by definition, *must* be a tuple). ''' # ....................{ HINT ~ pep : (484|585) : subclass }.................... CODE_PEP484585_SUBCLASS = '''( {indent_curr} # True only if this pith is a class *AND*... {indent_curr} isinstance({pith_curr_assign_expr}, type) and {indent_curr} # True only if this class subclasses this superclass. {indent_curr} issubclass({pith_curr_var_name}, {hint_curr_expr}) {indent_curr})''' ''' :pep:`484`- and :pep:`585`-compliant code snippet type-checking the current pith to be a subclass of the subscripted child hint of a :pep:`484`- or :pep:`585`-compliant **subclass type hint** (e.g., :attr:`typing.Type`, :class:`type`). ''' # ....................{ HINT ~ pep : 484 : instance }.................... CODE_PEP484_INSTANCE = '''isinstance({pith_curr_expr}, {hint_curr_expr})''' ''' :pep:`484`-compliant code snippet type-checking the current pith against the current child PEP-compliant type expected to be a trivial non-:mod:`typing` type (e.g., :class:`int`, :class:`str`). Caveats ------- **This snippet is intentionally compact rather than embedding a human-readable comment.** For example, this snippet intentionally avoids doing this: .. code-block:: python CODE_PEP484_INSTANCE = ' {indent_curr}# True only if this pith is of this type. {indent_curr}isinstance({pith_curr_expr}, {hint_curr_expr})' Although feasible, doing that would significantly complicate code generation for little to *no* tangible gain. Indeed, we actually tried doing that once. We failed hard after breaking everything. **Avoid the mistakes of the past.** ''' # ....................{ HINT ~ pep : 484 : union }.................... CODE_PEP484604_UNION_PREFIX = '''(''' ''' :pep:`484`-compliant code snippet prefixing all code type-checking the current pith against each subscripted argument of a :class:`typing.Union` type hint. ''' CODE_PEP484604_UNION_SUFFIX = ''' {indent_curr})''' ''' :pep:`484`-compliant code snippet suffixing all code type-checking the current pith against each subscripted argument of a :class:`typing.Union` type hint. ''' CODE_PEP484604_UNION_CHILD_NONPEP = ''' {{indent_curr}} # True only if this pith is of one of these types. {{indent_curr}} isinstance({pith_curr_expr}, {hint_curr_expr}) or''' ''' :pep:`484`-compliant code snippet type-checking the current pith against the current PEP-noncompliant child argument subscripting a parent :class:`typing.Union` type hint. See Also -------- :data:`CODE_PEP484604_UNION_CHILD_PEP` Further details. ''' CODE_PEP484604_UNION_CHILD_PEP = ''' {{indent_curr}} {hint_child_placeholder} or''' ''' :pep:`484`-compliant code snippet type-checking the current pith against the current PEP-compliant child argument subscripting a parent :class:`typing.Union` type hint. Caveats ------- The caller is required to manually slice the trailing suffix ``" or"`` after applying this snippet to the last subscripted argument of such a hint. While there exist alternate and more readable means of accomplishing this, this approach is the optimally efficient. The ``{indent_curr}`` format variable is intentionally brace-protected to efficiently defer its interpolation until the complete PEP-compliant code snippet type-checking the current pith against *all* subscripted arguments of this parent hint has been generated. ''' # ....................{ HINT ~ pep : 586 }.................... CODE_PEP586_PREFIX = '''( {{indent_curr}} # True only if this pith is of one of these literal types. {{indent_curr}} isinstance({pith_curr_assign_expr}, {hint_child_types_expr}) and (''' ''' :pep:`586`-compliant code snippet prefixing all code type-checking the current pith against a :pep:`586`-compliant :class:`typing.Literal` type hint subscripted by one or more literal objects. ''' CODE_PEP586_SUFFIX = ''' {indent_curr}))''' ''' :pep:`586`-compliant code snippet suffixing all code type-checking the current pith against a :pep:`586`-compliant :class:`typing.Literal` type hint subscripted by one or more literal objects. ''' CODE_PEP586_LITERAL = ''' {{indent_curr}} # True only if this pith is equal to this literal. {{indent_curr}} {pith_curr_var_name} == {hint_child_expr} or''' ''' :pep:`586`-compliant code snippet type-checking the current pith against the current child literal object subscripting a :pep:`586`-compliant :class:`typing.Literal` type hint. Caveats ------- The caller is required to manually slice the trailing suffix ``" and"`` after applying this snippet to the last subscripted argument of such a :class:`typing.Literal` type. While there exist alternate and more readable means of accomplishing this, this approach is the optimally efficient. The ``{indent_curr}`` format variable is intentionally brace-protected to efficiently defer its interpolation until the complete PEP-compliant code snippet type-checking the current pith against *all* subscripted arguments of this parent hint has been generated. ''' # ....................{ HINT ~ pep : 593 }.................... CODE_PEP593_VALIDATOR_PREFIX = '''(''' ''' :pep:`593`-compliant code snippet prefixing all code type-checking the current pith against a :pep:`593`-compliant :obj:`typing.Annotated` type hint subscripted by one or more :mod:`beartype.vale` validators. ''' CODE_PEP593_VALIDATOR_SUFFIX = ''' {indent_curr})''' ''' :pep:`593`-compliant code snippet suffixing all code type-checking the current pith against each a :pep:`593`-compliant :class:`typing.Annotated` type hint subscripted by one or more :mod:`beartype.vale` validators. ''' CODE_PEP593_VALIDATOR_METAHINT = ''' {indent_curr} {hint_child_placeholder} and''' ''' :pep:`593`-compliant code snippet type-checking the current pith against the **metahint** (i.e., first child type hint) subscripting a obj:`typing.Annotated` type hint subscripted by one or more :mod:`beartype.vale` validators. ''' CODE_PEP593_VALIDATOR_IS = ''' {indent_curr} # True only if this pith satisfies this caller-defined {indent_curr} # validator of this annotated metahint. {indent_curr} {hint_child_expr} and''' ''' :pep:`593`-compliant code snippet type-checking the current pith against :mod:`beartype`-specific **data validator code** (i.e., caller-defined :meth:`beartype.vale.BeartypeValidator._is_valid_code` string) of the current child :mod:`beartype.vale` validator subscripting a parent :pep:`593`-compliant :class:`typing.Annotated` type hint. Caveats ------- The caller is required to manually slice the trailing suffix ``" and"`` after applying this snippet to the last subscripted argument of such a :class:`typing.Annotated` type. While there exist alternate and more readable means of accomplishing this, this approach is the optimally efficient. ''' # ..................{ FORMATTERS }.................. # str.format() methods, globalized to avoid inefficient dot lookups elsewhere. # This is an absurd micro-optimization. *fight me, github developer community* CODE_PEP484_INSTANCE_format: Callable = ( CODE_PEP484_INSTANCE.format) CODE_PEP484585_GENERIC_CHILD_format: Callable = ( CODE_PEP484585_GENERIC_CHILD.format) CODE_PEP484585_MAPPING_format: Callable = ( CODE_PEP484585_MAPPING.format) CODE_PEP484585_MAPPING_KEY_ONLY_format: Callable = ( CODE_PEP484585_MAPPING_KEY_ONLY.format) CODE_PEP484585_MAPPING_KEY_VALUE_format: Callable = ( CODE_PEP484585_MAPPING_KEY_VALUE.format) CODE_PEP484585_MAPPING_VALUE_ONLY_format: Callable = ( CODE_PEP484585_MAPPING_VALUE_ONLY.format) CODE_PEP484585_MAPPING_KEY_ONLY_PITH_CHILD_EXPR_format: Callable = ( CODE_PEP484585_MAPPING_KEY_ONLY_PITH_CHILD_EXPR.format) CODE_PEP484585_MAPPING_VALUE_ONLY_PITH_CHILD_EXPR_format: Callable = ( CODE_PEP484585_MAPPING_VALUE_ONLY_PITH_CHILD_EXPR.format) CODE_PEP484585_MAPPING_KEY_VALUE_PITH_CHILD_EXPR_format: Callable = ( CODE_PEP484585_MAPPING_KEY_VALUE_PITH_CHILD_EXPR.format) CODE_PEP484585_SEQUENCE_ARGS_1_format: Callable = ( CODE_PEP484585_SEQUENCE_ARGS_1.format) CODE_PEP484585_SEQUENCE_ARGS_1_PITH_CHILD_EXPR_format: Callable = ( CODE_PEP484585_SEQUENCE_ARGS_1_PITH_CHILD_EXPR.format) CODE_PEP484585_SUBCLASS_format: Callable = ( CODE_PEP484585_SUBCLASS.format) CODE_PEP484585_TUPLE_FIXED_EMPTY_format: Callable = ( CODE_PEP484585_TUPLE_FIXED_EMPTY.format) CODE_PEP484585_TUPLE_FIXED_LEN_format: Callable = ( CODE_PEP484585_TUPLE_FIXED_LEN.format) CODE_PEP484585_TUPLE_FIXED_NONEMPTY_CHILD_format: Callable = ( CODE_PEP484585_TUPLE_FIXED_NONEMPTY_CHILD.format) CODE_PEP484585_TUPLE_FIXED_NONEMPTY_PITH_CHILD_EXPR_format: Callable = ( CODE_PEP484585_TUPLE_FIXED_NONEMPTY_PITH_CHILD_EXPR.format) CODE_PEP484604_UNION_CHILD_PEP_format: Callable = ( CODE_PEP484604_UNION_CHILD_PEP.format) CODE_PEP484604_UNION_CHILD_NONPEP_format: Callable = ( CODE_PEP484604_UNION_CHILD_NONPEP.format) # CODE_PEP572_PITH_ASSIGN_AND_format: Callable = ( # CODE_PEP572_PITH_ASSIGN_AND.format) CODE_PEP572_PITH_ASSIGN_EXPR_format: Callable = ( CODE_PEP572_PITH_ASSIGN_EXPR.format) CODE_PEP586_LITERAL_format: Callable = ( CODE_PEP586_LITERAL.format) CODE_PEP586_PREFIX_format: Callable = ( CODE_PEP586_PREFIX.format) CODE_PEP593_VALIDATOR_IS_format: Callable = ( CODE_PEP593_VALIDATOR_IS.format) CODE_PEP593_VALIDATOR_METAHINT_format: Callable = ( CODE_PEP593_VALIDATOR_METAHINT.format) CODE_PEP593_VALIDATOR_SUFFIX_format: Callable = ( CODE_PEP593_VALIDATOR_SUFFIX.format) beartype-0.18.5/beartype/_check/convert/000077500000000000000000000000001461113517100201275ustar00rootroot00000000000000beartype-0.18.5/beartype/_check/convert/__init__.py000066400000000000000000000000001461113517100222260ustar00rootroot00000000000000beartype-0.18.5/beartype/_check/convert/convcoerce.py000066400000000000000000000546731461113517100226460ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **PEP-agnostic type hint coercers** (i.e., mid-level callables *permanently* converting type hints from one format into another, either losslessly or in a lossy manner). Type hint coercions imposed by this submodule are externalized outside :mod:`beartype` as globally scoped changes accessible to other modules. These coercions are permanently applied to the ``__annotations__`` dunder dictionaries of the classes and callables annotated by these type hints. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... #FIXME: coerce_hint() should also rewrite unhashable hints to be hashable *IF #FEASIBLE.* This isn't always feasible, of course (e.g., "Annotated[[]]", #"Literal[[]]"). The one notable place where this *IS* feasible is with PEP #585-compliant type hints subscripted by unhashable rather than hashable #iterables, which can *ALWAYS* be safely rewritten to be hashable (e.g., #coercing "callable[[], None]" to "callable[(), None]"). #FIXME: [PEP 544] coerce_hint() should also coerce PEP 544-compatible protocols #*NOT* decorated by @typing.runtime_checkable to be decorated by that decorator, #as such protocols are unusable at runtime. Yes, we should always try something #*REALLY* sneaky and clever. # #Specifically, rather than accept "typing" nonsense verbatim, we could instead: #* Detect PEP 544-compatible protocol type hints *NOT* decorated by # @typing.runtime_checkable. The existing is_type_isinstanceable() tester now # detects whether arbitrary classes are isinstanceable, so just call that. #* Emit a non-fatal warning advising the end user to resolve this on their end. #* Meanwhile, beartype can simply: # * Dynamically fabricate a new PEP 544-compatible protocol decorated by # @typing.runtime_checkable using the body of the undecorated user-defined # protocol as its base. Indeed, simply subclassing a new subclass decorated # by @typing.runtime_checkable from the undecorated user-defined protocol as # its base with a noop body of "pass" should suffice. # * Replacing all instances of the undecorated user-defined protocol with that # decorated beartype-defined protocol in annotations. Note this would # strongly benefit from some form of memoization or caching. Since this edge # case should be fairly rare, even a dictionary would probably be overkill. # Just implementing something resembling the following memoized getter # in the "utilpep544" submodule would probably suffice: # @callable_cached # def get_pep544_protocol_checkable_from_protocol_uncheckable( # protocol_uncheckable: object) -> Protocol: # ... # #Checkmate, "typing". Checkmate. # ....................{ IMPORTS }.................... from beartype.typing import ( Any, Optional, Union, ) from beartype._cave._cavefast import NotImplementedType from beartype._cave._cavemap import NoneTypeOr from beartype._data.func.datafuncarg import ARG_NAME_RETURN from beartype._data.func.datafunc import METHOD_NAMES_DUNDER_BINARY from beartype._check.checkcall import BeartypeCall from beartype._check.forward.fwdmain import resolve_hint from beartype._util.cache.map.utilmapbig import CacheUnboundedStrong from beartype._util.hint.utilhinttest import is_hint_uncached from beartype._util.hint.pep.proposal.pep484.utilpep484union import ( make_hint_pep484_union) # ....................{ COERCERS ~ root }.................... #FIXME: Document mypy-specific coercion in the docstring as well, please. def coerce_func_hint_root( hint: object, pith_name: Optional[str], bear_call: BeartypeCall, exception_prefix: str, ) -> object: ''' PEP-compliant type hint coerced (i.e., converted) from the passed **root type hint** (i.e., possibly PEP-noncompliant type hint annotating the parameter or return with the passed name of the passed callable) if this hint is coercible *or* this hint as is otherwise (i.e., if this hint is *not* coercible). This function is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator). Since the hint returned by this function conditionally depends upon the passed callable, memoizing this function would consume space needlessly with *no* useful benefit. Caveats ------- This function *cannot* be meaningfully memoized, since the passed type hint is *not* guaranteed to be cached somewhere. Only functions passed cached type hints can be meaningfully memoized. Since this high-level function internally defers to unmemoized low-level functions that are :math:`O(n)` for :math:``n` the size of the inheritance hierarchy of this hint, this function should be called sparingly. See the :mod:`beartype._decor.cache.cachehint` submodule for further details. Parameters ---------- hint : object Possibly PEP-noncompliant type hint to be possibly coerced. pith_name : Optional[str] Either: * If this hint annotates a parameter of some callable, the name of that parameter. * If this hint annotates the return of some callable, ``"return"``. * Else, :data:`None`. bear_call : BeartypeCall Decorated callable annotated by this hint. exception_prefix : str Human-readable label prefixing the representation of this object in the exception message. Returns ------- object Either: * If this possibly PEP-noncompliant hint is coercible, a PEP-compliant type hint coerced from this hint. * Else, this hint as is unmodified. ''' assert isinstance(pith_name, NoneTypeOr[str]), ( f'{repr(pith_name)} neither string nor "None".') assert bear_call.__class__ is BeartypeCall, ( f'{repr(bear_call)} not @beartype call.') # print(f'Coercing pith "{pith_name}" annotated by type hint {repr(hint)}...') # ..................{ FORWARD REFERENCE }.................. # If this hint is stringified (e.g., as a PEP 484- or 563-compliant forward # reference), resolve this hint to the non-string hint to which this hint # refers *BEFORE* performing any subsequent logic with this hint -- *ALL* of # which assumes this hint to be a non-string hint. if isinstance(hint, str): hint = resolve_hint( hint=hint, bear_call=bear_call, exception_prefix=exception_prefix, ) # Else, this hint is *NOT* stringified. # # In either case, this hint is guaranteed to now be a non-string hint. # ..................{ MYPY }.................. # If... if ( # This hint annotates the return for the decorated callable *AND*... pith_name == ARG_NAME_RETURN and # The decorated callable is a binary dunder method (e.g., __eq__())... bear_call.func_wrapper_name in METHOD_NAMES_DUNDER_BINARY ): # Expand this hint to accept both this hint *AND* the "NotImplemented" # singleton as valid returns from this method. Why? Because this # expansion has been codified by mypy and is thus a de-facto typing # standard, albeit one currently lacking formal PEP standardization. # # Consider this representative binary dunder method: # class MuhClass: # @beartype # def __eq__(self, other: object) -> bool: # if isinstance(other, TheCloud): # return self is other # return NotImplemented # # Technically, that method *COULD* be retyped to return: # def __eq__(self, other: object) -> Union[ # bool, type(NotImplemented)]: # # Pragmatically, mypy and other static type checkers do *NOT* currently # support the type() builtin in a sane manner and thus raise errors # given the otherwise valid logic above. This means that the following # equivalent approach also yields the same errors: # NotImplementedType = type(NotImplemented) # class MuhClass: # @beartype # def __eq__(self, other: object) -> Union[ # bool, NotImplementedType]: # if isinstance(other, TheCloud): # return self is other # return NotImplemented # # Of course, the latter approach can be manually rectified by # explicitly typing that type as "Any": e.g., # NotImplementedType: Any = type(NotImplemented) # # Of course, expecting users to be aware of these ludicrous sorts of # mypy idiosyncrasies merely to annotate an otherwise normal binary # dunder method is one expectation too far. # # In theory, official CPython developers have already resolved this # under Python >= 3.10 by defining the "types.NotImplementedType" type. # In practice, that fails to assist older Python versions. Mypy has # thus taken the surprisingly sensible course of silently ignoring this # edge case by effectively performing the same type expansion as # performed here. *applause* return Union[hint, NotImplementedType] # pyright: ignore[reportGeneralTypeIssues] # Defer to the function-agnostic root hint coercer as a generic fallback. return coerce_hint_root(hint=hint, exception_prefix=exception_prefix) def coerce_hint_root(hint: object, exception_prefix: str) -> object: ''' PEP-compliant type hint coerced (i.e., converted) from the passed **root type hint** (i.e., possibly PEP-noncompliant type hint that has *no* parent type hint) if this hint is coercible *or* this hint as is otherwise (i.e., if this hint is *not* coercible). Specifically, if the passed hint is: * A **PEP-noncompliant tuple union** (i.e., tuple of one or more standard classes and forward references to standard classes), this function: * Coerces this tuple union into the equivalent :pep:`484`-compliant union. * Replaces this tuple union in the ``__annotations__`` dunder tuple of this callable with this :pep:`484`-compliant union. * Returns this :pep:`484`-compliant union. This function is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator). See caveats that follow. Caveats ------- This function *cannot* be meaningfully memoized, since the passed type hint is *not* guaranteed to be cached somewhere. Only functions passed cached type hints can be meaningfully memoized. Since this high-level function internally defers to unmemoized low-level functions that are ``O(n)`` for ``n`` the size of the inheritance hierarchy of this hint, this function should be called sparingly. See the :mod:`beartype._decor.cache.cachehint` submodule for further details. Parameters ---------- hint : object Possibly PEP-noncompliant type hint to be possibly coerced. exception_prefix : str Human-readable label prefixing the representation of this object in the exception message. Returns ------- object Either: * If this possibly PEP-noncompliant hint is coercible, a PEP-compliant type hint coerced from this hint. * Else, this hint as is unmodified. ''' # ..................{ NON-PEP }.................. # If this hint is a PEP-noncompliant tuple union, coerce this union into # the equivalent PEP-compliant union subscripted by the same child hints. # By definition, PEP-compliant unions are a superset of PEP-noncompliant # tuple unions and thus accept all child hints accepted by the latter. if isinstance(hint, tuple): return make_hint_pep484_union(hint) # Else, this hint is *NOT* a PEP-noncompliant tuple union. # Since none of the above conditions applied, this hint could *NOT* be # specifically coerced as a root type hint. Nonetheless, this hint may # still be generically coercible as a hint irrespective of its contextual # position relative to other type hints. # # Return this hint, possibly coerced as a context-agnostic type hint. return coerce_hint_any(hint) # ....................{ COERCERS ~ any }.................... def coerce_hint_any(hint: object) -> Any: ''' PEP-compliant type hint coerced (i.e., converted) from the passed PEP-compliant type hint if this hint is coercible *or* this hint as is otherwise (i.e., if this hint is *not* coercible). Specifically, if the passed hint is: * A **PEP-compliant uncached type hint** (i.e., hint *not* already internally cached by its parent class or module), this function: * If this hint has already been passed to a prior call of this function, returns the semantically equivalent PEP-compliant type hint having the same machine-readable representation as this hint cached by that call. Doing so deduplicates this hint, which both: * Minimizes space complexity across the lifetime of this process. * Minimizes time complexity by enabling beartype-specific memoized callables to efficiently reduce to constant-time lookup operations when repeatedly passed copies of this hint nonetheless sharing the same machine-readable representation. * Else, internally caches this hint with a thread-safe global cache and returns this hint as is. Uncached hints include: * :pep:`484`-compliant subscripted generics under Python >= 3.9 (e.g., ``from typing import List; class MuhPep484List(List): pass; MuhPep484List[int]``). See below for further commentary. * :pep:`585`-compliant type hints, including both: * Builtin :pep:`585`-compliant type hints (e.g., ``list[int]``). * User-defined :pep:`585`-compliant generics (e.g., ``class MuhPep585List(list): pass; MuhPep585List[int]``). * Already cached, this hint is already PEP-compliant by definition. In this case, this function preserves and returns this hint as is. This function is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator). See caveats that follow. Design ------ This function does *not* bother caching **self-caching type hints** (i.e., type hints that externally cache themselves), as these hints are already cached elsewhere. Self-cached type hints include most type hints created by subscripting type hint factories declared by the :mod:`typing` module, which internally cache their resulting type hints: e.g., .. code-block:: python >>> import typing >>> typing.List[int] is typing.List[int] True Equivalently, this function *only* caches **uncached type hints** (i.e., type hints that do *not* externally cache themselves), as these hints are *not* already cached elsewhere. Uncached type hints include *all* :pep:`585`-compliant type hints produced by subscripting builtin container types, which fail to internally cache their resulting type hints: e.g., .. code-block:: python >>> list[int] is list[int] False This function enables callers to coerce uncached type hints into :mod:`beartype`-cached type hints. :mod:`beartype` effectively requires *all* type hints to be cached somewhere! :mod:`beartype` does *not* care who, what, or how is caching those type hints -- only that they are cached before being passed to utility functions in the :mod:`beartype` codebase. Why? Because most such utility functions are memoized for efficiency by the :func:`beartype._util.cache.utilcachecall.callable_cached` decorator, which maps passed parameters (typically including the standard ``hint`` parameter accepting a type hint) based on object identity to previously cached return values. You see the problem, we trust. Uncached type hints that are otherwise semantically equal are nonetheless distinct objects and will thus be treated as distinct parameters by memoization decorators. If this function did *not* exist, uncached type hints could *not* be coerced into :mod:`beartype`-cached type hints and thus could *not* be memoized, dramatically reducing the efficiency of :mod:`beartype` for standard type hints. Caveats ------- This function *cannot* be meaningfully memoized, since the passed type hint is *not* guaranteed to be cached somewhere. Only functions passed cached type hints can be meaningfully memoized. Since this high-level function internally defers to unmemoized low-level functions that are :math:`O(n)` for :math:`n` the size of the inheritance hierarchy of this hint, this function should be called sparingly. This function intentionally does *not* cache :pep:`484`-compliant generics subscripted by type variables under Python < 3.9. Those hints are technically uncached but silently treated by this function as self-cached and thus preserved as is. Why? Because correctly detecting those hints as uncached would require an unmemoized :math:`O(n)` search across the inheritance hierarchy of *all* passed objects and thus all type hints annotating callables decorated by :func:`beartype.beartype`. Since this failure only affects obsolete Python versions *and* since the only harms induced by this failure are a slight increase in space and time consumption for edge-case type hints unlikely to actually be used in real-world code, this tradeoff is more than acceptable. We're not the bad guy here. Right? Parameters ---------- hint : object Type hint to be possibly coerced. Returns ------- object Either: * If this PEP-compliant type hint is coercible, another PEP-compliant type hint coerced from this hint. * Else, this hint as is unmodified. ''' # ..................{ NON-SELF-CACHING }.................. # If this hint is *NOT* self-caching, this hint *MUST* thus be explicitly # cached here. Failing to do so would disable subsequent memoization, # reducing decoration- and call-time efficiency when decorating callables # repeatedly annotated by copies of this hint. # # Specifically, deduplicate this hint by either: # * If this is the first copy of this hint passed to this function, cache # this hint under its machine-readable implementation. # * Else, one or more prior copies of this hint have already been passed to # this function. In this case, replace this subsequent copy by the first # copy of this hint originally passed to a prior call of this function. if is_hint_uncached(hint): # print(f'Self-caching type hint {repr(hint)}...') return _hint_repr_to_hint.cache_or_get_cached_value( key=repr(hint), value=hint) # Else, this hint is (hopefully) self-caching. # Return this uncoerced hint as is. return hint # ....................{ PRIVATE ~ mappings }.................... _hint_repr_to_hint = CacheUnboundedStrong() ''' **Type hint cache** (i.e., thread-safe cache mapping from the machine-readable representations of all non-self-cached type hints to cached singleton instances of those hints).** This cache caches: * :pep:`585`-compliant type hints, which do *not* cache themselves. * :pep:`604`-compliant unions, which do *not* cache themselves. This cache does *not* cache: * Type hints declared by the :mod:`typing` module, which implicitly cache themselves on subscription thanks to inscrutable metaclass magic. * :pep:`563`-compliant **deferred type hints** (i.e., type hints persisted as evaluable strings rather than actual type hints). Ideally, this cache would cache the evaluations of *all* deferred type hints. Sadly, doing so is infeasible in the general case due to global and local namespace lookups (e.g., ``Dict[str, int]`` only means what you think it means if an importation resembling ``from typing import Dict`` preceded that type hint). Design ------ **This dictionary is intentionally thread-safe.** Why? Because this dictionary is used to modify the ``__attributes__`` dunder variable of arbitrary callables. Since most such callables are either module- or class-scoped, that variable is effectively global. To prevent race conditions between competing threads contending over that variable, this dictionary *must* be thread-safe. **This dictionary is intentionally designed as a naive dictionary rather than a robust LRU cache,** for the same reasons that callables accepting hints are memoized by the :func:`beartype._util.cache.utilcachecall.callable_cached` rather than the :func:`functools.lru_cache` decorator. Why? Because: * The number of different type hints instantiated across even worst-case codebases is negligible in comparison to the space consumed by those hints. * The :attr:`sys.modules` dictionary persists strong references to all callables declared by previously imported modules. In turn, the ``func.__annotations__`` dunder dictionary of each such callable persists strong references to all type hints annotating that callable. In turn, these two statements imply that type hints are *never* garbage collected but instead persisted for the lifetime of the active Python process. Ergo, temporarily caching hints in an LRU cache is pointless, as there are *no* space savings in dropping stale references to unused hints. **This dictionary intentionally caches machine-readable representation strings hashes rather than alternative keys** (e.g., actual hashes). Why? Disambiguity. Although comparatively less efficient in both space and time to construct than hashes, the :func:`repr` strings produced for two dissimilar type hints *never* ambiguously collide unless an external caller maliciously modified one or more identifying dunder attributes of those hints (e.g., the ``__module__``, ``__qualname__``, and/or ``__name__`` dunder attributes). That should *never* occur in production code. Meanwhile, the :func:`hash` values produced for two dissimilar type hints *commonly* ambiguously collide. This is why hashable containers (e.g., :class:`dict`, :class:`set`) explicitly handle hash table collisions and why we are *not* going to do so. ''' beartype-0.18.5/beartype/_check/convert/convreduce.py000066400000000000000000000716331461113517100226500ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **PEP-agnostic type hint reducers** (i.e., low-level callables *temporarily* converting type hints from one format into another, either losslessly or in a lossy manner). Type hint reductions imposed by this submodule are purely internal to :mod:`beartype` itself and thus transient in nature. These reductions are *not* permanently applied to the ``__annotations__`` dunder dictionaries of the classes and callables annotated by these type hints. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import ( Any, # Callable, Dict, Optional, ) from beartype._conf.confcls import BeartypeConf from beartype._data.hint.pep.sign.datapepsigncls import HintSign from beartype._data.hint.pep.sign.datapepsigns import ( HintSignAbstractSet, HintSignAnnotated, HintSignAsyncContextManager, HintSignAsyncGenerator, HintSignAsyncIterable, HintSignAsyncIterator, HintSignAwaitable, HintSignByteString, HintSignCallable, HintSignChainMap, HintSignCollection, HintSignContainer, HintSignContextManager, HintSignCoroutine, HintSignCounter, HintSignDefaultDict, HintSignDeque, HintSignDict, HintSignFinal, HintSignFrozenSet, HintSignGenerator, HintSignGeneric, HintSignHashable, HintSignItemsView, HintSignIterable, HintSignIterator, HintSignKeysView, HintSignList, HintSignLiteralString, HintSignMappingView, HintSignMapping, HintSignMatch, HintSignMutableMapping, HintSignMutableSequence, HintSignMutableSet, HintSignNewType, HintSignNone, HintSignNumpyArray, HintSignOrderedDict, HintSignPanderaAny, HintSignPattern, HintSignPep557DataclassInitVar, HintSignPep585BuiltinSubscriptedUnknown, HintSignPep695TypeAlias, HintSignReversible, HintSignSelf, HintSignSequence, HintSignSet, HintSignSized, HintSignTuple, HintSignType, HintSignTypeAlias, HintSignTypeGuard, HintSignTypeVar, HintSignTypedDict, HintSignValuesView, ) from beartype._data.hint.datahinttyping import TypeStack from beartype._util.cache.utilcachecall import callable_cached from beartype._util.hint.nonpep.mod.utilmodnumpy import ( reduce_hint_numpy_ndarray) from beartype._util.hint.nonpep.mod.utilmodpandera import ( reduce_hint_pandera) from beartype._util.hint.pep.proposal.pep484.utilpep484 import ( reduce_hint_pep484_deprecated, reduce_hint_pep484_none, ) from beartype._util.hint.pep.proposal.pep484.utilpep484generic import ( reduce_hint_pep484_generic) from beartype._util.hint.pep.proposal.pep484.utilpep484newtype import ( reduce_hint_pep484_newtype) from beartype._util.hint.pep.proposal.pep484.utilpep484typevar import ( reduce_hint_pep484_typevar) from beartype._util.hint.pep.proposal.pep484585.utilpep484585type import ( reduce_hint_pep484585_type) from beartype._util.hint.pep.proposal.utilpep557 import ( reduce_hint_pep557_initvar) from beartype._util.hint.pep.proposal.utilpep585 import ( reduce_hint_pep585_builtin_subscripted_unknown) from beartype._util.hint.pep.proposal.utilpep589 import reduce_hint_pep589 from beartype._util.hint.pep.proposal.utilpep591 import reduce_hint_pep591 from beartype._util.hint.pep.proposal.utilpep593 import reduce_hint_pep593 from beartype._util.hint.pep.proposal.utilpep613 import reduce_hint_pep613 from beartype._util.hint.pep.proposal.utilpep647 import reduce_hint_pep647 from beartype._util.hint.pep.proposal.utilpep673 import reduce_hint_pep673 from beartype._util.hint.pep.proposal.utilpep675 import reduce_hint_pep675 from beartype._util.hint.pep.proposal.utilpep695 import reduce_hint_pep695 from beartype._util.hint.pep.utilpepget import get_hint_pep_sign_or_none from beartype._util.hint.pep.utilpepreduce import reduce_hint_pep_unsigned from beartype._util.utilobject import SENTINEL from collections.abc import Callable # ....................{ REDUCERS }.................... def reduce_hint( # Mandatory parameters. hint: Any, conf: BeartypeConf, # Optional parameters. cls_stack: TypeStack = None, pith_name: Optional[str] = None, exception_prefix: str = '', ) -> object: ''' Lower-level type hint reduced (i.e., converted) from the passed higher-level type hint if this hint is reducible *or* this hint as is otherwise (i.e., if this hint is irreducible). This reducer *cannot* be meaningfully memoized, since multiple passed parameters (e.g., ``pith_name``, ``cls_stack``) are typically isolated to a handful of callables across the codebase currently being decorated by :mod:`beartype`. Memoizing this reducer would needlessly consume space and time. To improve efficiency, this reducer is instead implemented in terms of two lower-level private reducers: * The memoized :func:`._reduce_hint_cached` reducer, responsible for efficiently reducing *most* (but not all) type hints. * The unmemoized :func:`._reduce_hint_uncached` reducer, responsible for inefficiently reducing the small subset of type hints contextually requiring these problematic parameters. Parameters ---------- hint : Any Type hint to be possibly reduced. conf : BeartypeConf **Beartype configuration** (i.e., self-caching dataclass encapsulating all settings configuring type-checking for the passed object). cls_stack : TypeStack, optional **Type stack** (i.e., either a tuple of the one or more :func:`beartype.beartype`-decorated classes lexically containing the class variable or method annotated by this hint *or* :data:`None`). Defaults to :data:`None`. pith_name : Optional[str], optional Either: * If this hint annotates a parameter of some callable, the name of that parameter. * If this hint annotates the return of some callable, ``"return"``. * Else, :data:`None`. Defaults to :data:`None`. exception_prefix : str, optional Substring prefixing exception messages raised by this function. Defaults to the empty string. Returns ------- object Either: * If the passed hint is reducible, another hint reduced from this hint. * Else, this hint as is unmodified. ''' # Previously reduced instance of this hint, initialized to the sentinel to # guarantee that the passed hint is *NEVER* equal to the previously reduced # instance of this hint unless actually reduced below. This is necessary, as # "None" is a valid type hint reduced to "type(None)" below. hint_prev: object = SENTINEL # Repeatedly reduce this hint to increasingly irreducible hints until this # hint is no longer reducible. while True: # This possibly contextual hint inefficiently reduced to another hint. # # Note that we intentionally reduce lower-level contextual hints # *BEFORE* reducing higher-level context-free hints. In theory, order of # reduction *SHOULD* be insignificant; in practice, we suspect # unforeseen and unpredictable interactions between these two # reductions. To reduce the likelihood of fire-breathing dragons here, # we reduce lower-level hints first. hint = _reduce_hint_uncached( hint=hint, conf=conf, pith_name=pith_name, cls_stack=cls_stack, exception_prefix=exception_prefix, ) # This possibly context-free hint efficiently reduced to another hint. hint = _reduce_hint_cached(hint, conf, exception_prefix) # If the current and previously reduced instances of this hint are # identical, the above reductions preserved this hint as is rather than # reducing this hint, implying this hint to irreducible. In this case, # stop reducing. if hint is hint_prev: break # Else, the current and previously reduced instances of this hint # differ, implying this hint to still be reducible. In this case, # continue reducing. # Previously reduced instance of this hint. hint_prev = hint # Return this possibly reduced hint. return hint # ....................{ PRIVATE ~ reducers }.................... def _reduce_hint_uncached( hint: Any, conf: BeartypeConf, cls_stack: TypeStack, pith_name: Optional[str], exception_prefix: str, ) -> object: ''' Lower-level **contextual type hint** (i.e., type hint contextually dependent on the kind of class, attribute, callable parameter, or callable return annotated by this hint) inefficiently reduced (i.e., converted) from the passed higher-level context-free type hint if this hint is reducible *or* this hint as is otherwise (i.e., if this hint is irreducible). This reducer *cannot* be meaningfully memoized, since multiple passed parameters (e.g., ``pith_name``, ``cls_stack``) are typically isolated to a handful of callables across the codebase currently being decorated by :mod:`beartype`. Thankfully, this reducer is responsible for reducing only a small subset of type hints requiring these problematic parameters. Parameters ---------- hint : Any Type hint to be possibly reduced. conf : BeartypeConf **Beartype configuration** (i.e., self-caching dataclass encapsulating all settings configuring type-checking for the passed object). cls_stack : TypeStack **Type stack** (i.e., either tuple of zero or more arbitrary types *or* :data:`None`). See also the :func:`.beartype_object` decorator. pith_name : Optional[str] Either: * If this hint annotates a parameter of some callable, the name of that parameter. * If this hint annotates the return of some callable, ``"return"``. * Else, :data:`None`. exception_prefix : str Substring prefixing exception messages raised by this function. Returns ------- object Either: * If the passed hint is reducible, another hint reduced from this hint. * Else, this hint as is unmodified. ''' # Sign uniquely identifying this hint if this hint is identifiable *OR* # "None" otherwise (e.g., if this hint is merely an isinstanceable class). hint_sign = get_hint_pep_sign_or_none(hint) # Callable reducing this hint if a callable reducing hints of this sign was # previously registered *OR* "None" otherwise (i.e., if *NO* such callable # was registered, in which case this hint is preserved as is). hint_reducer = _HINT_SIGN_TO_REDUCE_HINT_UNCACHED.get(hint_sign) # If a callable reducing hints of this sign was previously registered, # reduce this hint to another hint via this callable. if hint_reducer is not None: # type: ignore[call-arg] # print(f'Reducing hint {repr(hint)} to...') hint = hint_reducer( hint=hint, # pyright: ignore[reportGeneralTypeIssues] conf=conf, cls_stack=cls_stack, pith_name=pith_name, exception_prefix=exception_prefix, ) # print(f'...{repr(hint)}.') # Else, *NO* such callable was registered. Preserve this hint as is, you! # Return this possibly reduced hint. return hint @callable_cached def _reduce_hint_cached( hint: Any, conf: BeartypeConf, exception_prefix: str, ) -> object: ''' Lower-level **context-free type hint** (i.e., type hint *not* contextually dependent on the kind of class, attribute, callable parameter, or callable return annotated by this hint) efficiently reduced (i.e., converted) from the passed higher-level context-free type hint if this hint is reducible *or* this hint as is otherwise (i.e., if this hint is irreducible). This reducer is memoized for efficiency. Thankfully, this reducer is responsible for reducing *most* (but not all) type hints. Parameters ---------- hint : Any Type hint to be possibly reduced. conf : BeartypeConf **Beartype configuration** (i.e., self-caching dataclass encapsulating all settings configuring type-checking for the passed object). exception_prefix : str Substring prefixing exception messages raised by this function. Returns ------- object Either: * If the passed hint is reducible, another hint reduced from this hint. * Else, this hint as is unmodified. ''' # Attempt to... try: # If this beartype configuration coercively overrides this source hint # with a corresponding target hint, do so now *BEFORE* attempting to # reduce this hint via standard reduction heuristics. User preferences # take preference over standards. # # Note that this one-liner looks ridiculous, but actually works. More # importantly, this is the fastest way to accomplish this. Flex! hint = conf.hint_overrides.get(hint, hint) # If doing so raises a "TypeError", this source hint is unhashable and thus # inapplicable for hint overriding. In this case, silently ignore this hint. except TypeError: pass # Sign uniquely identifying this hint if this hint is identifiable *OR* # "None" otherwise (e.g., if this hint is merely an isinstanceable class). hint_sign = get_hint_pep_sign_or_none(hint) # Callable reducing this hint if a callable reducing hints of this sign was # previously registered *OR* "None" otherwise (i.e., if *NO* such callable # was registered, in which case this hint is preserved as is). hint_reducer = _HINT_SIGN_TO_REDUCE_HINT_CACHED.get(hint_sign) # If a callable reducing hints of this sign was previously registered, # reduce this hint to another hint via this callable. if hint_reducer is not None: hint = hint_reducer( # type: ignore[call-arg] hint=hint, # pyright: ignore[reportGeneralTypeIssues] conf=conf, exception_prefix=exception_prefix, ) # Else, *NO* such callable was registered. Preserve this hint as is, you! # Return this possibly reduced hint. return hint # ....................{ PRIVATE ~ hints }.................... # Note that these type hints would ideally be defined with the mypy-specific # "callback protocol" pseudostandard, documented here: # https://mypy.readthedocs.io/en/stable/protocols.html#callback-protocols # # Doing so would enable static type-checkers to type-check that the values of # these dictionaries are valid reducer functions. Sadly, that pseudostandard is # absurdly strict to the point of practical uselessness. Attempting to conform # to that pseudostandard would require refactoring *ALL* reducer functions to # explicitly define the same signature. However, we have intentionally *NOT* # done that. Why? Doing so would substantially increase the fragility of this # API by preventing us from readily adding and removing infrequently required # parameters (e.g., "cls_stack", "pith_name"). Callback protocols suck, frankly. _HintSignToReduceHintCached = Dict[Optional[HintSign], Callable] ''' PEP-compliant type hint matching a **cached reducer dictionary** (i.e., mapping from each sign uniquely identifying various type hints to a memoized callable reducing those higher- to lower-level hints). ''' _HintSignToReduceHintUncached = _HintSignToReduceHintCached ''' PEP-compliant type hint matching an **uncached reducer dictionary** (i.e., mapping from each sign uniquely identifying various type hints to an unmemoized callable reducing those higher- to lower-level hints). ''' # ....................{ PRIVATE ~ dicts }.................... _HINT_SIGN_TO_REDUCE_HINT_CACHED: _HintSignToReduceHintCached = { # ..................{ NON-PEP }.................. # If this hint is identified by *NO* sign, this hint is either an # isinstanceable type *OR* a hint unrecognized by beartype. In either case, # apply the following reductions: # # * If this configuration enables support for the PEP 484-compliant implicit # numeric tower: # * Expand the "float" type hint to the "float | int" union. # * Expand the "complex" type hint to the "complex | float | int" union. None: reduce_hint_pep_unsigned, # ..................{ PEP 484 }.................. # If this hint is a PEP 484-compliant IO generic base class *AND* the active # Python interpreter targets Python >= 3.8 and thus supports PEP # 544-compliant protocols, reduce this functionally useless hint to the # corresponding functionally useful beartype-specific PEP 544-compliant # protocol implementing this hint. # # Note that PEP 484-compliant IO generic base classes are technically usable # under Python < 3.8 (e.g., by explicitly subclassing those classes from # third-party classes). Ergo, we can neither safely emit warnings nor raise # exceptions on visiting these classes under *ANY* Python version. HintSignGeneric: reduce_hint_pep484_generic, # If this hint is a PEP 484-compliant new type, reduce this new type to the # user-defined class aliased by this new type. HintSignNewType: reduce_hint_pep484_newtype, # If this is the PEP 484-compliant "None" singleton, reduce this hint to # the type of that singleton. While *NOT* explicitly defined by the # "typing" module, PEP 484 explicitly supports this singleton: # When used in a type hint, the expression None is considered # equivalent to type(None). # # The "None" singleton is used to type callables lacking an explicit # "return" statement and thus absurdly common. HintSignNone: reduce_hint_pep484_none, #FIXME: Remove this branch *AFTER* deeply type-checking type variables. # If this type variable was parametrized by one or more bounded # constraints, reduce this hint to these constraints. HintSignTypeVar: reduce_hint_pep484_typevar, # ..................{ PEP (484|585) }.................. # If this hint is a PEP 484- or 585-compliant subclass type hint subscripted # by an ignorable child type hint (e.g., "object", "typing.Any"), silently # ignore this argument by reducing this hint to the "type" superclass. # # Note that: # * This reduction could be performed elsewhere, but remains here as doing # so here dramatically simplifies matters elsewhere. # * This reduction *CANNOT* be performed by the is_hint_ignorable() tester, # as subclass type hints subscripted by ignorable child type hints are # *NOT* ignorable; they're reducible to the "type" superclass. HintSignType: reduce_hint_pep484585_type, # ..................{ PEP 557 }.................. # If this hint is a dataclass-specific initialization-only instance # variable (i.e., instance of the PEP 557-compliant "dataclasses.InitVar" # class introduced by Python 3.8.0), reduce this functionally useless hint # to the functionally useful child type hint subscripting this parent hint. HintSignPep557DataclassInitVar: reduce_hint_pep557_initvar, # ..................{ PEP 585 }.................. # If this hint is a PEP 585-compliant unrecognized subscripted builtin type # hint (i.e., C-based type hint that is *NOT* an isinstanceable type, # instantiated by subscripting a pure-Python origin class subclassing the # C-based "types.GenericAlias" type where that origin class is unrecognized # by :mod:`beartype` and thus PEP-noncompliant), reduce this C-based type # hint (which is *NOT* type-checkable as is) to its unsubscripted # pure-Python origin class (which is type-checkable as is). Examples include # "os.PathLike[...]" and "weakref.weakref[...]" type hints. HintSignPep585BuiltinSubscriptedUnknown: ( reduce_hint_pep585_builtin_subscripted_unknown), # ..................{ PEP 589 }.................. #FIXME: Remove *AFTER* deeply type-checking typed dictionaries. For now, #shallowly type-checking such hints by reduction to untyped dictionaries #remains the sanest temporary work-around. # If this hint is a PEP 589-compliant typed dictionary (i.e., # "typing.TypedDict" or "typing_extensions.TypedDict" subclass), silently # ignore all child type hints annotating this dictionary by reducing this # hint to the "Mapping" superclass. Yes, "Mapping" rather than "dict". By # PEP 589 edict: # First, any TypedDict type is consistent with Mapping[str, object]. # # Typed dictionaries are largely discouraged in the typing community, due to # their non-standard semantics and syntax. HintSignTypedDict: reduce_hint_pep589, # ..................{ PEP 591 }.................. #FIXME: Remove *AFTER* deeply type-checking final type hints. # If this hint is a PEP 591-compliant "typing.Final[...]" type hint, # silently reduce this hint to its subscripted argument (e.g., from # "typing.Final[int]" to merely "int"). HintSignFinal: reduce_hint_pep591, # ..................{ PEP 593 }.................. # If this hint is a PEP 593-compliant beartype-agnostic type metahint, # ignore all annotations on this hint by reducing this hint to the # lower-level hint it annotates. HintSignAnnotated: reduce_hint_pep593, # ..................{ PEP 675 }.................. #FIXME: Remove *AFTER* deeply type-checking literal strings. Note that doing #so will prove extremely non-trivial or possibly even infeasible, suggesting #we will probably *NEVER* deeply type-check literal strings. It's *NOT* #simply a matter of efficiently parsing ASTs at runtime; it's that as well #as correctly transitively inferring literal strings across operations and #calls, which effectively requires parsing the entire codebase and #constructing an in-memory graph of all type relations. See also: # https://peps.python.org/pep-0675/#inferring-literalstring # If this hint is a PEP 675-compliant "typing.LiteralString" type hint, # reduce this hint to the standard "str" type. HintSignLiteralString: reduce_hint_pep675, # ..................{ PEP 695 }.................. # If this hint is a PEP 695-compliant "type" alias, reduce this alias to the # underlying hint lazily referred to by this alias. HintSignPep695TypeAlias: reduce_hint_pep695, # ..................{ NON-PEP ~ numpy }.................. # If this hint is a PEP-noncompliant typed NumPy array (e.g., # "numpy.typing.NDArray[np.float64]"), reduce this hint to the equivalent # well-supported beartype validator. HintSignNumpyArray: reduce_hint_numpy_ndarray, # ..................{ NON-PEP ~ pandera }.................. # If this hint is *ANY* PEP-noncompliant Pandera type hint (e.g., # "pandera.typing.DataFrame[...]"), reduce this hint to an arbitrary # PEP-compliant ignorable type hint. See this reducer for commentary. HintSignPanderaAny: reduce_hint_pandera, } ''' Dictionary mapping from each sign uniquely identifying PEP-compliant type hints to that sign's **cached reducer** (i.e., low-level function efficiently memoized by the :func:`.callable_cached` decorator reducing those higher- to lower-level hints). Each value of this dictionary is expected to have a signature resembling: .. code-block:: python def reduce_hint_pep{pep_number}( hint: object, conf: BeartypeConf, pith_name: Optional[str], exception_prefix: str, *args, **kwargs ) -> object: Note that: * Reducers should explicitly accept *only* those parameters they explicitly require. Ergo, a reducer requiring *only* the ``hint`` parameter should omit all of the other parameters referenced above. * Reducers do *not* need to validate the passed type hint as being of the expected sign. By design, a reducer is only ever passed a type hint of the expected sign. * Reducers should *not* be memoized (e.g., by the ``callable_cached`` decorator). Since the higher-level :func:`.reduce_hint` function that is the sole entry point to calling all lower-level reducers is itself memoized, reducers themselves neither require nor benefit from memoization. Moreover, even if they did either require or benefit from memoization, they couldn't be -- at least, not directly. Why? Because :func:`.reduce_hint` necessarily passes keyword arguments to all reducers. But memoized functions *cannot* receive keyword arguments (without destroying efficiency and thus the entire point of memoization). ''' _HINT_SIGN_TO_REDUCE_HINT_UNCACHED: _HintSignToReduceHintUncached = { # ..................{ PEP 484 }.................. # Preserve deprecated PEP 484-compliant type hints as is while emitting one # non-fatal deprecation warning for each. # # Note that, to ensure that one such warning is emitted for each such hint, # these reducers are intentionally uncached rather than cached. HintSignAbstractSet: reduce_hint_pep484_deprecated, HintSignAsyncContextManager: reduce_hint_pep484_deprecated, HintSignAsyncGenerator: reduce_hint_pep484_deprecated, HintSignAsyncIterable: reduce_hint_pep484_deprecated, HintSignAsyncIterator: reduce_hint_pep484_deprecated, HintSignAwaitable: reduce_hint_pep484_deprecated, HintSignByteString: reduce_hint_pep484_deprecated, HintSignCallable: reduce_hint_pep484_deprecated, HintSignChainMap: reduce_hint_pep484_deprecated, HintSignCollection: reduce_hint_pep484_deprecated, HintSignContainer: reduce_hint_pep484_deprecated, HintSignContextManager: reduce_hint_pep484_deprecated, HintSignCoroutine: reduce_hint_pep484_deprecated, HintSignCounter: reduce_hint_pep484_deprecated, HintSignDefaultDict: reduce_hint_pep484_deprecated, HintSignDeque: reduce_hint_pep484_deprecated, HintSignDict: reduce_hint_pep484_deprecated, HintSignFrozenSet: reduce_hint_pep484_deprecated, HintSignGenerator: reduce_hint_pep484_deprecated, HintSignHashable: reduce_hint_pep484_deprecated, HintSignItemsView: reduce_hint_pep484_deprecated, HintSignIterable: reduce_hint_pep484_deprecated, HintSignIterator: reduce_hint_pep484_deprecated, HintSignKeysView: reduce_hint_pep484_deprecated, HintSignList: reduce_hint_pep484_deprecated, HintSignMappingView: reduce_hint_pep484_deprecated, HintSignMapping: reduce_hint_pep484_deprecated, HintSignMatch: reduce_hint_pep484_deprecated, HintSignMutableMapping: reduce_hint_pep484_deprecated, HintSignMutableSequence: reduce_hint_pep484_deprecated, HintSignMutableSet: reduce_hint_pep484_deprecated, HintSignOrderedDict: reduce_hint_pep484_deprecated, HintSignPattern: reduce_hint_pep484_deprecated, HintSignReversible: reduce_hint_pep484_deprecated, HintSignSequence: reduce_hint_pep484_deprecated, HintSignSet: reduce_hint_pep484_deprecated, HintSignSized: reduce_hint_pep484_deprecated, HintSignTuple: reduce_hint_pep484_deprecated, HintSignType: reduce_hint_pep484_deprecated, HintSignValuesView: reduce_hint_pep484_deprecated, # ..................{ PEP 613 }.................. # Reduce PEP 613-compliant "typing.TypeAlias" type hints to an arbitrary # ignorable type hint *AND* emit a non-fatal deprecation warning. # # Note that, to ensure that one such warning is emitted for each such hint, # this reducer is intentionally uncached rather than cached. HintSignTypeAlias: reduce_hint_pep613, # ..................{ PEP 647 }.................. # Reduce PEP 647-compliant "typing.TypeGuard[...]" type hints to either: # * If this hint annotates the return of some callable, the "bool" type. # * Else, raise an exception. HintSignTypeGuard: reduce_hint_pep647, # ..................{ PEP 673 }.................. # Reduce PEP 673-compliant "typing.Self" type hints to either: # * If @beartype is currently decorating a class, the most deeply nested # class on the passed type stack. # * Else, raise an exception. HintSignSelf: reduce_hint_pep673, } ''' Dictionary mapping from each sign uniquely identifying various type hints to that sign's **uncached reducer** (i.e., low-level function whose reduction decision contextually depends on the currently decorated callable and thus *cannot* be efficiently memoized by the :func:`.callable_cached` decorator). See Also -------- :data:`._HINT_SIGN_TO_REDUCE_HINT_CACHED` Further details. ''' beartype-0.18.5/beartype/_check/convert/convsanify.py000066400000000000000000000431721461113517100226670ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **PEP-agnostic type hint sanitizers** (i.e., high-level callables converting type hints from one format into another, either permanently or temporarily and either losslessly or in a lossy manner). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import ( Any, Optional, ) from beartype._check.checkcall import BeartypeCall from beartype._check.convert.convcoerce import ( coerce_func_hint_root, coerce_hint_any, coerce_hint_root, ) from beartype._check.convert.convreduce import reduce_hint from beartype._conf.confcls import BeartypeConf from beartype._data.error.dataerrmagic import EXCEPTION_PLACEHOLDER from beartype._data.func.datafuncarg import ARG_NAME_RETURN from beartype._data.hint.datahinttyping import TypeStack from beartype._util.cache.map.utilmapbig import CacheUnboundedStrong from beartype._util.hint.pep.proposal.pep484585.utilpep484585func import ( reduce_hint_pep484585_func_return) from beartype._util.hint.utilhinttest import is_hint_ignorable # ....................{ SANIFIERS ~ root }.................... #FIXME: Unit test us up, please. def sanify_hint_root_func( # Mandatory parameters. hint: object, pith_name: str, bear_call: BeartypeCall, # Optional parameters. exception_prefix: str = EXCEPTION_PLACEHOLDER, ) -> object: ''' PEP-compliant type hint sanified (i.e., sanitized) from the passed **root type hint** (i.e., possibly PEP-noncompliant type hint annotating the parameter or return with the passed name of the passed callable) if this hint is reducible *or* this hint as is otherwise (i.e., if this hint is irreducible). Specifically, this function: * If this hint is a **PEP-noncompliant tuple union** (i.e., tuple of one or more standard classes and forward references to standard classes): * Coerces this tuple union into the equivalent :pep:`484`-compliant union. * Replaces this tuple union in the ``__annotations__`` dunder tuple of this callable with this :pep:`484`-compliant union. * Returns this :pep:`484`-compliant union. * Else if this hint is already PEP-compliant, preserves and returns this hint unmodified as is. * Else (i.e., if this hint is neither PEP-compliant nor -noncompliant and thus invalid as a type hint), raise an exception. Caveats ------- This sanifier *cannot* be meaningfully memoized, since the passed type hint is *not* guaranteed to be cached somewhere. Only functions passed cached type hints can be meaningfully memoized. Even if this function *could* be meaningfully memoized, there would be no benefit; this function is only called once per parameter or return of the currently decorated callable. This sanifier is intended to be called *after* all possibly :pep:`563`-compliant **deferred type hints** (i.e., type hints persisted as evaluatable strings rather than actual type hints) annotating this callable if any have been evaluated into actual type hints. Parameters ---------- hint : object Possibly PEP-noncompliant root type hint to be sanified. cls_stack : TypeStack, optional **Type stack** (i.e., either a tuple of the one or more :func:`beartype.beartype`-decorated classes lexically containing the class variable or method annotated by this hint *or* :data:`None`). Defaults to :data:`None`. pith_name : str Either: * If this hint annotates a parameter, the name of that parameter. * If this hint annotates the return, ``"return"``. bear_call : BeartypeCall Decorated callable directly annotated by this hint. exception_prefix : str, optional Human-readable label prefixing exception messages raised by this function. Defaults to :data:`EXCEPTION_PLACEHOLDER`. Returns ------- object Either: * If this hint is PEP-noncompliant, a PEP-compliant type hint converted from this hint. * If this hint is PEP-compliant, this hint unmodified as is. Raises ------ BeartypeDecorHintNonpepException If this object is neither: * A PEP-noncompliant type hint. * A supported PEP-compliant type hint. ''' #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Synchronize with the sanify_hint_root_statement() sanitizer. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # PEP-compliant type hint coerced (i.e., permanently converted in the # annotations dunder dictionary of the passed callable) from this possibly # PEP-noncompliant type hint if this hint is coercible *OR* this hint as is # otherwise. Since the passed hint is *NOT* necessarily PEP-compliant, # perform this coercion *BEFORE* validating this hint to be PEP-compliant. hint = bear_call.func_wrappee.__annotations__[pith_name] = ( coerce_func_hint_root( hint=hint, pith_name=pith_name, bear_call=bear_call, exception_prefix=exception_prefix, ) ) # If this hint annotates the return, reduce this hint to a simpler hint if # this hint is either PEP 484- or 585-compliant *AND* requires reduction # (e.g., from "Coroutine[None, None, str]" to just "str"). Raise an # exception if this hint is contextually invalid for this callable (e.g., # generator whose return is *NOT* annotated as "Generator[...]"). # # Perform this reduction *BEFORE* performing subsequent tests (e.g., to # accept "Coroutine[None, None, typing.NoReturn]" as expected). # # Note that this logic *ONLY* pertains to callables (rather than statements) # and is thus *NOT* performed by the sanify_hint_root_statement() sanitizer. if pith_name == ARG_NAME_RETURN: hint = reduce_hint_pep484585_func_return( func=bear_call.func_wrappee, exception_prefix=exception_prefix) # Else, this hint annotates a parameter. # Reduce this hint to a lower-level PEP-compliant type hint if this hint is # reducible *OR* this hint as is otherwise. Reductions simplify subsequent # logic elsewhere by transparently converting non-trivial hints (e.g., # numpy.typing.NDArray[...]) into semantically equivalent trivial hints # (e.g., beartype validators). # # Whereas the above coercion permanently persists for the duration of the # active Python process (i.e., by replacing the original type hint in the # annotations dunder dictionary of this callable), this reduction only # temporarily persists for the duration of the current call stack. Why? # Because hints explicitly coerced above are assumed to be either: # * PEP-noncompliant and thus harmful (in the general sense). # * PEP-compliant but semantically deficient and thus equally harmful (in # the general sense). # # In either case, coerced type hints are generally harmful in *ALL* possible # contexts for *ALL* possible consumers (including other competing runtime # type-checkers). Reduced type hints, however, are *NOT* harmful in any # sense whatsoever; they're simply non-trivial for @beartype to support in # their current form and thus temporarily reduced in-memory into a more # convenient form for beartype-specific type-checking purposes elsewhere. # # Note that parameters are intentionally passed positionally to both # optimize memoization efficiency and circumvent memoization warnings. hint = reduce_hint( hint=hint, conf=bear_call.conf, cls_stack=bear_call.cls_stack, pith_name=pith_name, exception_prefix=exception_prefix, ) # Return this sanified hint. return hint #FIXME: Unit test us up, please. def sanify_hint_root_statement( hint: object, conf: BeartypeConf, exception_prefix: str, ) -> object: ''' PEP-compliant type hint sanified (i.e., sanitized) from the passed **root type hint** (i.e., possibly PEP-noncompliant type hint that has *no* parent type hint) if this hint is reducible *or* this hint as is otherwise (i.e., if this hint is irreducible). This sanifier is principally intended to be called by a **statement-level type-checker factory** (i.e., a function creating and returning a runtime type-checker type-checking this hint, outside the context of any standard type hinting annotation like a user-defined class variable, callable parameter or return, or assignment statement). Such factories include: * The private :func:`beartype._check.checkmake.make_func_tester` factory, internally called by: * The public :func:`beartype.door.is_bearable` function. * The public :meth:`beartype.door.TypeHint.is_bearable` method. Parameters ---------- hint : object Possibly PEP-noncompliant root type hint to be sanified. conf : BeartypeConf **Beartype configuration** (i.e., self-caching dataclass encapsulating all settings configuring type-checking for the passed object). exception_prefix : str Human-readable label prefixing the representation of this object in the exception message. Returns ------- object Either: * If this hint is PEP-noncompliant, a PEP-compliant type hint converted from this hint. * If this hint is PEP-compliant, this hint unmodified as is. Raises ------ BeartypeDecorHintNonpepException If this object is neither: * A PEP-noncompliant type hint. * A supported PEP-compliant type hint. See Also -------- :func:`.sanify_hint_root_func` Further details. ''' #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Synchronize with the sanify_hint_root_func() sanitizer, please. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # PEP-compliant type hint coerced from this possibly PEP-noncompliant type # hint if this hint is coercible *OR* this hint as is otherwise. Since the # passed hint is *NOT* necessarily PEP-compliant, perform this coercion # *BEFORE* validating this hint to be PEP-compliant. hint = coerce_hint_root(hint=hint, exception_prefix=exception_prefix) # Reduce this hint to a lower-level PEP-compliant type hint if this hint is # reducible *OR* this hint as is otherwise. See # sanify_hint_root_func() for further commentary. hint = reduce_hint(hint=hint, conf=conf, exception_prefix=exception_prefix) # Return this sanified hint. return hint # ....................{ SANIFIERS ~ any }.................... #FIXME: Unit test us up, please. def sanify_hint_child_if_unignorable_or_none(*args, **kwargs) -> Any: ''' Type hint sanified (i.e., sanitized) from the passed **possibly insane child type hint** (i.e., hint transitively subscripting the root type hint annotating a parameter or return of the currently decorated callable) if this hint is both reducible and ignorable, this hint unmodified if this hint is both irreducible and ignorable, and :data:`None` otherwise (i.e., if this hint is ignorable). This high-level sanifier effectively chains the lower-level :func:`sanify_hilt_child` sanifier and :func:`beartype._util.hint.utilhinttest.is_hint_ignorable` tester into a single unified function, streamlining sanification and ignorability detection throughout the codebase. Note that a :data:`None` return unambiguously implies this hint to be ignorable, even if the passed hint is itself :data:`None`. Why? Because if the passed hint were :data:`None`, then a :pep:`484`-compliant reducer will internally reduce this hint to ``type(None)``. After reduction, *all* hints are guaranteed to be non-:data:`None`. Parameters ---------- All passed arguments are passed as is to the lower-level :func:`sanify_hilt_child` sanifier. Returns ------- object Either: * If the passed possibly insane child type hint is ignorable after reduction to a sane child type hint, :data:`None`. * Else, the sane child type hint to which this hint reduces. ''' # Sane child hint sanified from this possibly insane child hint if this hint # is reducible *OR* this hint as is otherwise (i.e., if irreducible). hint_child = sanify_hint_child(*args, **kwargs) # Return either "None" if this hint is ignorable or this hint otherwise. return None if is_hint_ignorable(hint_child) else hint_child def sanify_hint_child( # Mandatory parameters. hint: object, conf: BeartypeConf, exception_prefix: str, # Optional parameters. cls_stack: TypeStack = None, pith_name: Optional[str] = None, ) -> Any: ''' Type hint sanified (i.e., sanitized) from the passed **possibly insane child type hint** (i.e., hint transitively subscripting the root type hint annotating a parameter or return of the currently decorated callable) if this hint is reducible *or* this hint unmodified otherwise (i.e., if this hint is irreducible). Parameters ---------- hint : object Type hint to be sanified. conf : BeartypeConf **Beartype configuration** (i.e., self-caching dataclass encapsulating all settings configuring type-checking for the passed object). exception_prefix : str Substring prefixing exception messages raised by this function. cls_stack : TypeStack, optional **Type stack** (i.e., either a tuple of the one or more :func:`beartype.beartype`-decorated classes lexically containing the class variable or method annotated by this hint *or* :data:`None`). Defaults to :data:`None`. pith_name : Optional[str], optional Either: * If this hint directly annotates a callable parameter (as the root type hint of that parameter), the name of this parameter. * If this hint directly annotates a callable return (as the root type hint of that return), the magic string ``"return"``. * Else, :data:`None`. Defaults to :data:`None`. Returns ------- object Type hint sanified from this possibly insane child type hint. ''' # This sanifier covers the proper subset of logic performed by the # sanify_hint_root_statement() sanifier applicable to child type hints. # PEP-compliant type hint coerced (i.e., permanently converted in the # annotations dunder dictionary of the passed callable) from this possibly # PEP-noncompliant type hint if this hint is coercible *OR* this hint as is # otherwise. Since the passed hint is *NOT* necessarily PEP-compliant, # perform this coercion *BEFORE* validating this hint to be PEP-compliant. hint = coerce_hint_any(hint) # Return this hint reduced. return reduce_hint( hint=hint, conf=conf, cls_stack=cls_stack, pith_name=pith_name, exception_prefix=exception_prefix, ) # ....................{ PRIVATE ~ mappings }.................... _HINT_REPR_TO_HINT = CacheUnboundedStrong() ''' **Type hint cache** (i.e., thread-safe cache mapping from the machine-readable representations of all non-self-cached type hints to those hints).** This cache caches: * :pep:`585`-compliant type hints, which do *not* cache themselves. This cache does *not* cache: * Type hints declared by the :mod:`typing` module, which implicitly cache themselves on subscription thanks to inscrutable metaclass magic. * :pep:`563`-compliant **deferred type hints** (i.e., type hints persisted as evaluable strings rather than actual type hints). Ideally, this cache would cache the evaluations of *all* deferred type hints. Sadly, doing so is infeasible in the general case due to global and local namespace lookups (e.g., ``Dict[str, int]`` only means what you think it means if an importation resembling ``from typing import Dict`` preceded that type hint). Design ------ **This dictionary is intentionally thread-safe.** Why? Because this dictionary is used to modify the ``__attributes__`` dunder variable of arbitrary callables. Since most of those callables are either module- or class-scoped, that variable is effectively global. To prevent race conditions between competing threads contending over that global variable, this dictionary *must* be thread-safe. This dictionary is intentionally designed as a naive dictionary rather than a robust LRU cache, for the same reasons that callables accepting hints are memoized by the :func:`beartype._util.cache.utilcachecall.callable_cached` rather than the :func:`functools.lru_cache` decorator. Why? Because: * The number of different type hints instantiated across even worst-case codebases is negligible in comparison to the space consumed by those hints. * The :attr:`sys.modules` dictionary persists strong references to all callables declared by previously imported modules. In turn, the ``func.__annotations__`` dunder dictionary of each such callable persists strong references to all type hints annotating that callable. In turn, these two statements imply that type hints are *never* garbage collected but instead persisted for the lifetime of the active Python process. Ergo, temporarily caching hints in an LRU cache is pointless, as there are *no* space savings in dropping stale references to unused hints. ''' beartype-0.18.5/beartype/_check/error/000077500000000000000000000000001461113517100176005ustar00rootroot00000000000000beartype-0.18.5/beartype/_check/error/__init__.py000066400000000000000000000000001461113517100216770ustar00rootroot00000000000000beartype-0.18.5/beartype/_check/error/_errorcause.py000066400000000000000000000540321461113517100224670ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype type-checking error cause sleuth** (i.e., object recursively fabricating the human-readable string describing the failure of the pith associated with this object to satisfy this PEP-compliant type hint also associated with this object) classes. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... #FIXME: The recursive "ViolationCause" class strongly overlaps with the equally #recursive (and substantially superior) "beartype.door.TypeHint" class. Ideally: #* Define a new private "beartype.door._doorerror" submodule. #* Shift the "ViolationCause" class to # "beartype.door._doorerror._TypeHintUnbearability". #* Shift the _TypeHintUnbearability.find_cause() method to a new # *PRIVATE* TypeHint._find_cause() method. #* Preserve most of the remainder of the "_TypeHintUnbearability" class as a # dataclass encapsulating metadata describing the current type-checking # violation. That metadata (e.g., "cause_indent") is inappropriate for # general-purpose type hints. Exceptions include: # * "hint", "hint_sign", and "hint_childs" -- all of which are subsumed by the # "TypeHint" dataclass and should thus be excised. #* Refactor the TypeHint._find_cause() method to accept an instance of # the "_TypeHintUnbearability" dataclass: e.g., # class TypeHint(...): # def _get_unbearability_cause_or_none( # self, unbearability: _TypeHintUnbearability) -> Optional[str]: # ... #* Refactor existing find_cause_*() getters (e.g., # find_cause_sequence_args_1(), find_cause_union()) into # _get_unbearability_cause_or_none() methods of the corresponding "TypeHint" # subclasses, please. # #This all seems quite reasonable. Now, let's see whether it is. *gulp* #FIXME: Actually, the above comment now ties directly into feature request #235. #Resolving the above comment mostly suffices to resolve #235. That said, the #above isn't *QUITE* right. It's pretty nice -- but we can do better. See the #following comment at #235 for that better: # https://github.com/beartype/beartype/issues/235#issuecomment-1707127231 # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeCallHintPepRaiseException from beartype.typing import ( Any, Callable, Optional, Tuple, ) from beartype._cave._cavemap import NoneTypeOr from beartype._conf.confcls import BeartypeConf from beartype._data.hint.datahinttyping import TypeStack from beartype._data.hint.pep.sign.datapepsignset import ( HINT_SIGNS_SUPPORTED_DEEP, HINT_SIGNS_ORIGIN_ISINSTANCEABLE, ) from beartype._util.hint.pep.utilpepget import ( get_hint_pep_args, get_hint_pep_sign, ) from beartype._util.hint.pep.utilpeptest import ( is_hint_pep, is_hint_pep_args, ) from beartype._check.convert.convsanify import ( sanify_hint_child_if_unignorable_or_none) # ....................{ CLASSES }.................... class ViolationCause(object): ''' **Type-checking error cause sleuth** (i.e., object recursively fabricating the human-readable string describing the failure of the pith associated with this object to satisfy this PEP-compliant type hint also associated with this object). Attributes ---------- cause_indent : str **Indentation** (i.e., string of zero or more spaces) preceding each line of the string returned by this getter if this string spans multiple lines *or* ignored otherwise (i.e., if this string is instead embedded in the current line). cause_str_or_none : Optional[str] If this pith either: * Violates this hint, a human-readable string describing this violation. * Satisfies this hint, :data:`None`. cls_stack : TypeStack, optional **Type stack** (i.e., either a tuple of the one or more :func:`beartype.beartype`-decorated classes lexically containing the class variable or method annotated by this hint *or* :data:`None`). conf : BeartypeConf **Beartype configuration** (i.e., self-caching dataclass encapsulating all flags, options, settings, and other metadata configuring the current decoration of the decorated callable or class). exception_prefix : str Human-readable label describing the parameter or return value from which this object originates, typically embedded in exceptions raised from this getter in the event of unexpected runtime failure. func : Optional[Callable] Either: * If this violation originates from a decorated callable, that callable. * Else, :data:`None`. hint : Any Type hint to validate this object against. hint_sign : Any Either: * If this hint is PEP-compliant, the sign identifying this hint. * Else, :data:`None` otherwise. hint_childs : Optional[Tuple] Either: * If this hint is PEP-compliant, the possibly empty tuple of all child type hints subscripting (indexing) this hint. * Else, :data:`None`. pith : Any Arbitrary object to be validated. pith_name : Optional[str] Either: * If this hint directly annotates a callable parameter (as the root type hint of that parameter), the name of this parameter. * If this hint directly annotates a callable return (as the root type hint of that return), the magic string ``"return"``. * Else, :data:`None`. random_int : Optional[int] **Pseudo-random integer** (i.e., unsigned 32-bit integer pseudo-randomly generated by the parent :func:`beartype.beartype` wrapper function in type-checking randomly indexed container items by the current call to that function) if that function generated such an integer *or* ``None`` otherwise (i.e., if that function generated *no* such integer). See the same parameter accepted by the higher-level :func:`beartype._check.error.errorget.get_func_pith_violation` function for further details. ''' # ..................{ CLASS VARIABLES }.................. # Slot *ALL* instance variables defined on this object to both: # * Prevent accidental declaration of erroneous instance variables. # * Minimize space and time complexity. __slots__ = ( 'cause_indent', 'cause_str_or_none', 'cls_stack', 'conf', 'exception_prefix', 'func', 'hint', 'hint_sign', 'hint_childs', 'pith', 'pith_name', 'random_int', ) _INIT_PARAM_NAMES = frozenset(( 'cause_indent', 'cause_str_or_none', 'cls_stack', 'conf', 'exception_prefix', 'func', 'hint', 'pith', 'pith_name', 'random_int', )) ''' Frozen set of the names of all parameters accepted by the :meth:`init` method, defined as a set to enable efficient membership testing. ''' # ..................{ INITIALIZERS }.................. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Whenever adding, deleting, or renaming any parameter accepted by # this method, make similar changes to the "_INIT_PARAM_NAMES" set above. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! def __init__( self, # Mandatory parameters. cls_stack: TypeStack, cause_indent: str, conf: BeartypeConf, exception_prefix: str, func: Optional[Callable], hint: Any, pith: Any, pith_name: Optional[str], random_int: Optional[int], # Optional parameters. cause_str_or_none: Optional[str] = None, ) -> None: ''' Initialize this object. Parameters ---------- See the class docstring for a description of these parameters. ''' assert isinstance(cls_stack, NoneTypeOr[tuple]), ( f'{repr(cls_stack)} neither tuple nor "None".') assert isinstance(conf, BeartypeConf), ( f'{repr(conf)} not configuration.') assert func is None or callable(func), ( f'{repr(func)} neither callable nor "None".') assert isinstance(pith_name, NoneTypeOr[str]), ( f'{repr(pith_name)} not string or "None".') assert isinstance(cause_indent, str), ( f'{repr(cause_indent)} not string.') assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') assert isinstance(random_int, NoneTypeOr[int]), ( f'{repr(random_int)} not integer or "None".') assert isinstance(cause_str_or_none, NoneTypeOr[str]), ( f'{repr(cause_str_or_none)} not string or "None".') # Classify all passed parameters. self.func = func self.cls_stack = cls_stack self.conf = conf # self.hint = hint self.pith = pith self.pith_name = pith_name self.cause_indent = cause_indent self.exception_prefix = exception_prefix self.random_int = random_int self.cause_str_or_none = cause_str_or_none # Nullify all remaining parameters for safety. self.hint_sign: Any = None self.hint_childs: Tuple = None # type: ignore[assignment] # Unignorable sane hint sanified from this possibly ignorable insane # hint *OR* "None" otherwise (i.e., if this hint is ignorable). # # Note that this is a bit inefficient. Since child hints are already # sanitized below, the sanitization performed by this assignment # effectively reduces to a noop for all type hints *EXCEPT* the root # type hint. Technically, this means this could be marginally optimized # by externally sanitizing the root type hint in the "errorget" # submodule. Pragmatically, doing so would only complicate an already # overly complex workflow for little to no tangible gain. self.hint = sanify_hint_child_if_unignorable_or_none( hint=hint, conf=self.conf, cls_stack=self.cls_stack, pith_name=self.pith_name, exception_prefix=self.exception_prefix, ) # If this hint is both... if ( # Unignorable *AND*... self.hint is not None and # PEP-compliant... is_hint_pep(self.hint) ): # Arbitrary object uniquely identifying this hint. self.hint_sign = get_hint_pep_sign(self.hint) # Tuple of the zero or more arguments subscripting this hint. hint_childs_insane = get_hint_pep_args(self.hint) # List of the zero or more possibly ignorable sane child hints # subscripting this parent hint, initialized to the empty list. hint_childs_sane = [] # For each possibly ignorable insane child hints subscripting this # parent hint... for hint_child_insane in hint_childs_insane: # If this child hint is PEP-compliant... # # Note that arbitrary PEP-noncompliant arguments *CANNOT* be # safely sanitized. Why? Because arbitrary arguments are *NOT* # necessarily valid type hints. Consider the type hint # "tuple[()]", where the argument "()" is invalid as a type hint # but valid an argument to that type hint. if is_hint_pep(hint_child_insane): # Unignorable sane child hint sanified from this possibly # ignorable insane child hint *OR* "None" otherwise (i.e., # if this child hint is ignorable). hint_child_sane = sanify_hint_child_if_unignorable_or_none( hint=hint_child_insane, conf=self.conf, cls_stack=self.cls_stack, pith_name=self.pith_name, exception_prefix=self.exception_prefix, ) # Else, this child hint is PEP-noncompliant. In this case, # preserve this child hint as is. else: hint_child_sane = hint_child_insane # Append this possibly ignorable sane child hint to this list. hint_childs_sane.append(hint_child_sane) # Tuple of the zero or more possibly ignorable sane child hints # subscripting this parent hint, coerced from this list. self.hint_childs = tuple(hint_childs_sane) # Else, this hint is PEP-noncompliant (e.g., isinstanceable class). # ..................{ GETTERS }.................. def find_cause(self) -> 'ViolationCause': ''' Output cause describing whether the pith of this input cause either satisfies or violates the type hint of this input cause. Design ------ This method is intentionally generalized to support objects both satisfying and *not* satisfying hints as equally valid use cases. While the parent :func:`beartype._check.error.errorget.get_func_pith_violation` function calling this method is *always* passed an object *not* satisfying the passed hint, this method is under no such constraints. Why? Because this method is also called to find which of an arbitrary number of objects transitively nested in the object passed to :func:`beartype._check.error.errorget.get_func_pith_violation` fails to satisfy the corresponding hint transitively nested in the hint passed to that function. For example, consider the PEP-compliant type hint ``List[Union[int, str]]`` describing a list whose items are either integers or strings and the list ``list(range(256)) + [False,]`` consisting of the integers 0 through 255 followed by boolean :data:`False`. Since that list is a standard sequence, the :func:`._peperrorsequence.find_cause_sequence_args_1` function must decide the cause of this list's failure to comply with this hint by finding the list item that is neither an integer nor a string, implemented by by iteratively passing each list item to the :func:`._peperrorunion.find_cause_union` function. Since the first 256 items of this list are integers satisfying this hint, :func:`._peperrorunion.find_cause_union` returns a dataclass instance whose :attr:`cause` field is :data:`None` up to :func:`._peperrorsequence.find_cause_sequence_args_1` before finally finding the non-compliant boolean item and returning the human-readable cause. Returns ------- ViolationCause Output cause type-checking this pith against this type hint. Raises ------ _BeartypeCallHintPepRaiseException If this type hint is either: * PEP-noncompliant (e.g., tuple union). * PEP-compliant but no getter function has been implemented to handle this category of PEP-compliant type hint yet. ''' # If this hint is ignorable, all possible objects satisfy this hint. # Since this hint *CANNOT* (by definition) be the cause of this failure, # return the same cause as is. if self.hint is None: return self # Else, this hint is unignorable. # Getter function returning the desired string. cause_finder: Callable[[ViolationCause], ViolationCause] = None # type: ignore[assignment] #FIXME: Trivially simplify this method by: #* Define a new find_cause_nonpep() function elsewhere whose body is # the body of this "if" conditional branch. #* Register this function with HINT_SIGN_TO_GET_CAUSE_FUNC: e.g., # HINT_SIGN_TO_GET_CAUSE_FUNC = { # ..., # None: find_cause_nonpep, # } #* Remove this "if" conditional branch. # If *NO* sign uniquely identifies this hint, this hint is either # PEP-noncompliant *OR* only contextually PEP-compliant in certain # specific use cases. In either case... if self.hint_sign is None: # If this hint is a tuple union... if isinstance(self.hint, tuple): # Avoid circular import dependencies. from beartype._check.error._errortype import ( find_cause_instance_types_tuple) # Defer to the getter function specific to tuple unions. cause_finder = find_cause_instance_types_tuple # Else, this hint is *NOT* a tuple union. In this case, assume this # hint to be an isinstanceable class. If this is *NOT* the case, the # getter deferred to below raises a human-readable exception. else: # Avoid circular import dependencies. from beartype._check.error._errortype import ( find_cause_instance_type) # Defer to the getter function specific to classes. cause_finder = find_cause_instance_type # Else, this hint is PEP-compliant. # # If this hint... elif ( # Originates from an origin type and may thus be shallowly # type-checked against that type *AND is either... self.hint_sign in HINT_SIGNS_ORIGIN_ISINSTANCEABLE and ( # Unsubscripted *OR*... not is_hint_pep_args(self.hint) or #FIXME: Remove this branch *AFTER* deeply supporting all hints. # Currently unsupported with deep type-checking... self.hint_sign not in HINT_SIGNS_SUPPORTED_DEEP ) # Then this hint is both unsubscripted and originating from a standard # type origin. In this case, this hint was type-checked shallowly. ): # Avoid circular import dependencies. from beartype._check.error._errortype import ( find_cause_type_instance_origin) # Defer to the getter function supporting hints originating from # origin types. cause_finder = find_cause_type_instance_origin # Else, this hint is either subscripted *OR* unsubscripted but not # originating from a standard type origin. In either case, this hint was # type-checked deeply. else: # Avoid circular import dependencies. from beartype._check.error._errordata import ( HINT_SIGN_TO_GET_CAUSE_FUNC) # Getter function returning the desired string for this attribute if # any *OR* "None" otherwise. cause_finder = HINT_SIGN_TO_GET_CAUSE_FUNC.get( self.hint_sign, None) # type: ignore[arg-type] # If no such function has been implemented to handle this attribute # yet, raise an exception. if cause_finder is None: raise _BeartypeCallHintPepRaiseException( f'{self.exception_prefix} type hint ' f'{repr(self.hint)} unsupported (i.e., no ' f'"find_cause_"-prefixed getter function defined ' f'for this category of hint).' ) # Else, a getter function has been implemented to handle this # attribute. # Call this getter function with ourselves and return the string # returned by this getter. return cause_finder(self) # ..................{ PERMUTERS }.................. def permute(self, **kwargs) -> 'ViolationCause': ''' Shallow copy of this object such that each the passed keyword argument overwrites the instance variable of the same name in this copy. Parameters ---------- Keyword arguments of the same name and type as instance variables of this object (e.g., ``hint``, ``pith``). Returns ------- ViolationCause Shallow copy of this object such that each keyword argument overwrites the instance variable of the same name in this copy. Raises ------ _BeartypeCallHintPepRaiseException If the name of any passed keyword argument is *not* the name of an existing instance variable of this object. Examples -------- .. code-block:: pycon >>> sleuth = ViolationCause( ... pith=[42,] ... hint=typing.List[int], ... cause_indent='', ... exception_prefix='List of integers', ... ) >>> sleuth_copy = sleuth.permute(pith=[24,]) >>> sleuth_copy.pith [24,] >>> sleuth_copy.hint typing.List[int] ''' # For the name of each passed keyword argument... for arg_name in kwargs.keys(): # If this name is *NOT* that of a parameter accepted by the # __init__() method, raise an exception. if arg_name not in self._INIT_PARAM_NAMES: raise _BeartypeCallHintPepRaiseException( f'{self.__class__}.__init__() parameter ' f'{arg_name} unrecognized.' ) # For the name of each parameter accepted by the __init__() method... for arg_name in self._INIT_PARAM_NAMES: # If this parameter was *NOT* explicitly passed by the caller, # default this parameter to its current value from this object. if arg_name not in kwargs: kwargs[arg_name] = getattr(self, arg_name) # Return a new instance of this class initialized with these arguments. return ViolationCause(**kwargs) beartype-0.18.5/beartype/_check/error/_errordata.py000066400000000000000000000104701461113517100222760ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype exception data** (i.e., high-level globals and constants leveraged throughout the :mod:`beartype._check.error` subpackage). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import ( Callable, Dict, ) from beartype._data.hint.pep.sign.datapepsigncls import HintSign from beartype._check.error._errorcause import ViolationCause # ....................{ GLOBALS }.................... # Initialized with automated inspection below in the _init() function. HINT_SIGN_TO_GET_CAUSE_FUNC: Dict[ HintSign, Callable[[ViolationCause], ViolationCause]] = {} ''' Dictionary mapping each **sign** (i.e., arbitrary object uniquely identifying a category of type hints) to a private getter function defined by this submodule whose signature matches that of the :func:`._find_cause` function and which is dynamically dispatched by that function to describe type-checking failures specific to that unsubscripted :mod:`typing` attribute. ''' # ....................{ PRIVATE ~ initializers }.................... def _init() -> None: ''' Initialize this submodule. ''' # Defer heavyweight imports. from beartype._data.hint.pep.sign.datapepsigns import ( HintSignAnnotated, HintSignForwardRef, HintSignGeneric, HintSignLiteral, HintSignNoReturn, HintSignTuple, HintSignType, ) from beartype._data.hint.pep.sign.datapepsignset import ( HINT_SIGNS_MAPPING, HINT_SIGNS_ORIGIN_ISINSTANCEABLE, HINT_SIGNS_SEQUENCE_ARGS_1, HINT_SIGNS_UNION, ) from beartype._check.error._errortype import ( find_cause_instance_type_forwardref, find_cause_subclass_type, find_cause_type_instance_origin, ) from beartype._check.error._pep.errorpep484604union import ( find_cause_union) from beartype._check.error._pep.errorpep586 import ( find_cause_literal) from beartype._check.error._pep.errorpep593 import ( find_cause_annotated) from beartype._check.error._pep.pep484.errornoreturn import ( find_cause_noreturn) from beartype._check.error._pep.pep484585.errorgeneric import ( find_cause_generic) from beartype._check.error._pep.pep484585.errormapping import ( find_cause_mapping) from beartype._check.error._pep.pep484585.errorsequence import ( find_cause_sequence_args_1, find_cause_tuple, ) # Map each originative sign to the appropriate getter *BEFORE* any other # mappings. This is merely a generalized fallback subsequently replaced by # sign-specific getters below. for pep_sign_origin_isinstanceable in HINT_SIGNS_ORIGIN_ISINSTANCEABLE: HINT_SIGN_TO_GET_CAUSE_FUNC[pep_sign_origin_isinstanceable] = ( find_cause_type_instance_origin) # Map each mapping sign to its corresponding getter. for pep_sign_mapping in HINT_SIGNS_MAPPING: HINT_SIGN_TO_GET_CAUSE_FUNC[pep_sign_mapping] = find_cause_mapping # Map each 1-argument sequence sign to its corresponding getter. for pep_sign_sequence_args_1 in HINT_SIGNS_SEQUENCE_ARGS_1: HINT_SIGN_TO_GET_CAUSE_FUNC[pep_sign_sequence_args_1] = ( find_cause_sequence_args_1) # Map each union-specific sign to its corresponding getter. for pep_sign_type_union in HINT_SIGNS_UNION: HINT_SIGN_TO_GET_CAUSE_FUNC[pep_sign_type_union] = find_cause_union # Map each sign validated by a unique getter to that getter *AFTER* all # other mappings. These sign-specific getters are intended to replace all # other automated mappings above. HINT_SIGN_TO_GET_CAUSE_FUNC.update({ HintSignAnnotated: find_cause_annotated, HintSignForwardRef: find_cause_instance_type_forwardref, HintSignGeneric: find_cause_generic, HintSignLiteral: find_cause_literal, HintSignNoReturn: find_cause_noreturn, HintSignTuple: find_cause_tuple, HintSignType: find_cause_subclass_type, }) # Initialize this submodule. _init() beartype-0.18.5/beartype/_check/error/_errortype.py000066400000000000000000000347111461113517100223520ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype class type hint violation describers** (i.e., functions returning human-readable strings explaining violations of type hints that are standard isinstanceable classes rather than PEP-specific objects). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import ( BeartypeCallHintForwardRefException, BeartypePlugInstancecheckStrException, ) from beartype.roar._roarexc import _BeartypeCallHintPepRaiseException from beartype.typing import Optional from beartype._cave._cavefast import TestableTypes from beartype._data.hint.pep.sign.datapepsigns import ( HintSignForwardRef, HintSignType, ) from beartype._check.error._errorcause import ViolationCause from beartype._util.cls.utilclstest import is_type_subclass from beartype._util.cls.pep.utilpep3119 import ( die_unless_type_isinstanceable, die_unless_type_issubclassable, ) from beartype._util.func.arg.utilfuncargtest import ( die_unless_func_args_len_flexible_equal) from beartype._util.hint.nonpep.utilnonpeptest import ( die_unless_hint_nonpep_tuple) from beartype._util.hint.pep.proposal.pep484585.utilpep484585ref import ( import_pep484585_ref_type) from beartype._util.hint.pep.proposal.pep484585.utilpep484585type import ( get_hint_pep484585_type_superclass) from beartype._util.hint.pep.utilpepget import ( get_hint_pep_origin_type_isinstanceable_or_none) from beartype._util.text.utiltextansi import color_hint from beartype._util.text.utiltextjoin import join_delimited_disjunction_types from beartype._util.text.utiltextlabel import label_type from beartype._util.text.utiltextrepr import represent_pith # ....................{ GETTERS ~ instance : type }.................... def find_cause_instance_type(cause: ViolationCause) -> ViolationCause: ''' Output cause describing whether the pith of the passed input cause either is or is not an instance of the isinstanceable class of that cause. Parameters ---------- cause : ViolationCause Input cause providing this data. Returns ------- BeartypePlugInstancecheckStrException If the metaclass of this isinstanceable class defines the :mod:`beartype`-specific ``__instancecheck_str__()`` dunder method but either: * This method is *not* a pure-Python callable. * This method is a pure-Python callable with an unexpected signature that differs from the expected API: .. code-block:: python def __instancecheck_str__(cls, obj: typing.Any) -> str: * This method is a pure-Python callable with the expected signature that returns either: * An object that is *not* a string. * The empty string. ViolationCause Output cause type-checking this data. ''' assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.' # Isinstanceable class against which this pith was type-checked. hint = cause.hint # Pith type-checked against this isinstanceable class. pith = cause.pith # If this hint is *NOT* an isinstanceable class, raise an exception. die_unless_type_isinstanceable( cls=hint, exception_cls=_BeartypeCallHintPepRaiseException, exception_prefix=cause.exception_prefix, ) # Else, this hint is an isinstanceable class. # Output cause justification. If this pith either: # * Violates this hint, this is a human-readable substring describing this # violation. # * Satisfies this hint, "None". cause_str_or_none: Optional[str] = None # If this pith is *NOT* an instance of this class... if not isinstance(pith, hint): # Metaclass-specific __instancecheck_str__() dunder method if the # metaclass of this class defines this method *OR* "None" otherwise # (i.e., if that metaclass does *NOT* define this method). # # Note that this constitutes a plugin API. Although currently # beartype-specific, this API is intended to receive widespread adoption # as a pseudo-standard throughout the runtime type-checking community # (e.g., by typeguard and possibly Pydantic). Various third-party # packages that publish custom type hint factories currently leverage # this API to generate package-specific violation messages, including: # * @patrick-kidger's "jaxtyping" package. For the good of Google! get_hint_violation_str = getattr(hint, '__instancecheck_str__', None) # If the metaclass of this class defines this dunder method... if get_hint_violation_str: # Human-readable substring prefixing *ALL* exceptions raised below. EXCEPTION_PREFIX = ( f'{cause.exception_prefix}{repr(hint)} ' f'beartype-specific dunder method __instancecheck_str__() ' ) # If this method is *NOT* a pure-Python callable accepting exactly # two parameters, this method does *NOT* satisfy the expected API: # def __instancecheck_str__(cls, obj: typing.Any) -> str: # # In this case, raise an exception. die_unless_func_args_len_flexible_equal( func=get_hint_violation_str, func_args_len_flexible=2, exception_cls=BeartypePlugInstancecheckStrException, exception_prefix=EXCEPTION_PREFIX, ) # Else, this method satisfies the expected API. # Human-readable substring describing this violation generated by # the metaclass of this class. cause_str_or_none = get_hint_violation_str(pith) # If this string is *NOT* actually a string, raise an exception. if not isinstance(cause_str_or_none, str): raise BeartypePlugInstancecheckStrException( f'{EXCEPTION_PREFIX}return {cause_str_or_none} not string.') # Else, this string is actually a string. # # If this string is empty, raise an exception. elif not cause_str_or_none: raise BeartypePlugInstancecheckStrException( f'{EXCEPTION_PREFIX}return string empty.') # Else, this string is non-empty. # Else, the metaclass of this class does *NOT* define this method. In # this case, fallback to a standard substring describing this violation. else: cause_str_or_none = ( f'{represent_pith(pith)} not instance of ' f'{color_hint(text=label_type(hint), is_color=cause.conf.is_color)}' ) # Else, this pith is an instance of this class. # Output cause to be returned, permuted from this input cause with this # output cause justification. cause_return = cause.permute(cause_str_or_none=cause_str_or_none) # Return this output cause. return cause_return def find_cause_instance_type_forwardref( cause: ViolationCause) -> ViolationCause: ''' Output cause describing whether the pith of the passed input cause either is or is not an instance of the class referred to by the **forward reference type hint** (i.e., string whose value is the either absolute *or* relative name of a user-defined type which has yet to be defined) of that cause. Parameters ---------- cause : ViolationCause Input cause providing this data. Returns ------- ViolationCause Output cause type-checking this data. ''' assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.' assert cause.hint_sign is HintSignForwardRef, ( f'{cause.hint_sign} not forward reference.') # Class referred to by this absolute or relative forward reference. hint_ref_type = import_pep484585_ref_type( hint=cause.hint, cls_stack=cause.cls_stack, func=cause.func, exception_cls=BeartypeCallHintForwardRefException, exception_prefix=cause.exception_prefix, ) # Defer to the function handling isinstanceable classes. Neato! return find_cause_instance_type(cause.permute(hint=hint_ref_type)) def find_cause_type_instance_origin(cause: ViolationCause) -> ViolationCause: ''' Output cause describing whether the pith of the passed input cause either is or is not an instance of the isinstanceable type underlying the **originative type hint** (i.e., PEP-compliant type hint originating from a non-:mod:`typing` class, typically due to being either a :pep:`585`-compliant type hint *or* a third-party type hint subclassing the :class:`types.GenericAlias` superclass defined by :pep:`585`) of that cause. Parameters ---------- cause : ViolationCause Input cause providing this data. Returns ------- ViolationCause Output cause type-checking this data. ''' assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.' # Isinstanceable origin type originating this hint if any *OR* "None". hint_type = get_hint_pep_origin_type_isinstanceable_or_none(cause.hint) # If this hint does *NOT* originate from such a type, raise an exception. if hint_type is None: raise _BeartypeCallHintPepRaiseException( f'{cause.exception_prefix}type hint ' f'{repr(cause.hint)} not originated from ' f'isinstanceable origin type.' ) # Else, this hint originates from such a type. # Defer to the getter function handling non-"typing" classes. Presto! return find_cause_instance_type(cause.permute(hint=hint_type)) # ....................{ GETTERS ~ instance : types }.................... def find_cause_instance_types_tuple(cause: ViolationCause) -> ViolationCause: ''' Output cause describing whether the pith of the passed input cause either is or is not an instance of one or more isinstanceable types in the tuple of these types of that cause. Parameters ---------- cause : ViolationCause Input cause providing this data. Returns ------- ViolationCause Output cause type-checking this data. ''' assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.' # If this hint is *NOT* a tuple union, raise an exception. die_unless_hint_nonpep_tuple( hint=cause.hint, exception_prefix=cause.exception_prefix, exception_cls=_BeartypeCallHintPepRaiseException, ) # Else, this hint is a tuple union. # Output cause to be returned, permuted from this input cause such that the # output cause justification is either... cause_return = cause.permute(cause_str_or_none=( # If this pith is an instance of one or more types in this tuple union, # "None"; None if isinstance(cause.pith, cause.hint) else # Else, this pith is an instance of *NO* types in this tuple union. In # this case, a substring describing this failure to be embedded in a # longer string. ( f'{represent_pith(cause.pith)} not instance of ' f'{color_hint(text=join_delimited_disjunction_types(cause.hint), is_color=cause.conf.is_color)}' ) )) # Return this cause. return cause_return # ....................{ GETTERS ~ subclass : type }.................... def find_cause_subclass_type(cause: ViolationCause) -> ViolationCause: ''' Output cause describing whether the pith of the passed input cause either is or is not a subclass of the issubclassable type of that cause. Parameters ---------- cause : ViolationCause Input cause providing this data. Returns ------- ViolationCause Output cause type-checking this data. ''' assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.' assert cause.hint_sign is HintSignType, ( f'{cause.hint_sign} not HintSignType.') # Superclass this pith is required to be a subclass of. hint_superclass = get_hint_pep484585_type_superclass( hint=cause.hint, exception_prefix=cause.exception_prefix) # If this superclass is neither a class nor tuple of classes, this # superclass *MUST* by process of elimination and the validation already # performed above by the get_hint_pep484585_type_superclass() getter be a # forward reference to a class. In this case... if not isinstance(hint_superclass, TestableTypes): # Reduce this superclass to the class referred to by this absolute or # relative forward reference. hint_superclass = import_pep484585_ref_type( hint=hint_superclass, # type: ignore[arg-type] cls_stack=cause.cls_stack, func=cause.func, exception_cls=BeartypeCallHintForwardRefException, exception_prefix=cause.exception_prefix, ) # If this superclass is *NOT* issubclassable, raise an exception. die_unless_type_issubclassable( cls=hint_superclass, exception_cls=_BeartypeCallHintPepRaiseException, exception_prefix=cause.exception_prefix, ) # Else, this superclass is issubclassable. # In either case, this superclass is now issubclassable. # Output cause to be returned, permuted from this input cause. cause_return = cause.permute() # If this pith subclasses this superclass, set the output cause # justification to "None". if is_type_subclass(cause_return.pith, hint_superclass): cause_return.cause_str_or_none = None # Else, this pith does *NOT* subclass this superclass. In this case... else: # Description of this superclasses, defined as either... hint_superclass_label = ( # If this superclass is a class, a description of this class; label_type(hint_superclass) if isinstance(hint_superclass, type) else # Else, this superclass is a tuple of classes. In this case, a # description of these classes... join_delimited_disjunction_types(hint_superclass) ) # Human-readable string describing this failure. cause_return.cause_str_or_none = ( f'{represent_pith(cause_return.pith)} not subclass of ' f'{hint_superclass_label}' ) # Return this cause. return cause_return beartype-0.18.5/beartype/_check/error/_pep/000077500000000000000000000000001461113517100205235ustar00rootroot00000000000000beartype-0.18.5/beartype/_check/error/_pep/__init__.py000066400000000000000000000000001461113517100226220ustar00rootroot00000000000000beartype-0.18.5/beartype/_check/error/_pep/errorpep484604union.py000066400000000000000000000257301461113517100245050ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype** :pep:`484`-compliant **union type hint violation describers** (i.e., functions returning human-readable strings explaining violations of :pep:`484`-compliant union type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeCallHintPepRaiseException from beartype._data.hint.pep.sign.datapepsignset import HINT_SIGNS_UNION from beartype._check.error._errorcause import ViolationCause from beartype._util.hint.pep.utilpepget import ( get_hint_pep_origin_type_isinstanceable_or_none) from beartype._util.hint.pep.utilpeptest import is_hint_pep from beartype._util.text.utiltextansi import color_hint from beartype._util.text.utiltextjoin import join_delimited_disjunction_types from beartype._util.text.utiltextmunge import ( suffix_str_unless_suffixed, uppercase_str_char_first, ) from beartype._util.text.utiltextrepr import represent_pith # ....................{ GETTERS }.................... def find_cause_union(cause: ViolationCause) -> ViolationCause: ''' Output cause describing whether the pith of the passed input cause either satisfies or violates the PEP-compliant union type hint of that cause. Parameters ---------- cause : ViolationCause Input cause providing this data. Returns ------- ViolationCause Output cause type-checking this data. ''' assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.' assert cause.hint_sign in HINT_SIGNS_UNION, ( f'{repr(cause.hint)} not union sign.') # Indentation preceding each line of the strings returned by child getter # functions called by this parent getter function, offset to visually # demarcate child from parent causes in multiline strings. CAUSE_INDENT_CHILD = cause.cause_indent + ' ' # List of all human-readable strings describing the failure of this pith to # satisfy each of these child hints. cause_strs = [] # Subset of all classes shallowly associated with these child hints (i.e., # by being either these child hints in the case of non-"typing" classes # *OR* the classes originating these child hints in the case of # PEP-compliant type hints) that this pith fails to shallowly satisfy. hint_types_violated = set() # Truncated object representation of this pith. pith_repr = represent_pith(cause.pith) # 0-based index of the first non-whitespace character following this # representation in violation causes collected below. Look. Just accept it. PITH_REPR_INDEX = len(pith_repr) + 1 # For each subscripted argument of this union... for hint_child in cause.hint_childs: # If this child hint is ignorable, continue to the next. if hint_child is None: continue # Else, this child hint is unignorable. # If this child hint is PEP-compliant... if is_hint_pep(hint_child): # Non-"typing" class originating this child hint if any *OR* "None" # otherwise. hint_child_origin_type = ( get_hint_pep_origin_type_isinstanceable_or_none(hint_child)) # If... if ( # This child hint originates from a non-"typing" class *AND*... hint_child_origin_type is not None and # This pith is *NOT* an instance of this class... not isinstance(cause.pith, hint_child_origin_type) # Then this pith fails to satisfy this child hint. In this case... ): # Add this class to the subset of all classes this pith does # *NOT* satisfy. hint_types_violated.add(hint_child_origin_type) # Continue to the next child hint. continue # Else, this pith is an instance of this class and thus shallowly # (but *NOT* necessarily deeply) satisfies this child hint. # Child hint output cause to be returned, type-checking only whether # this pith deeply satisfies this child hint. cause_child = cause.permute( hint=hint_child, cause_indent=CAUSE_INDENT_CHILD, ).find_cause() # If this pith deeply satisfies this child hint, return this cause # as is. if cause_child.cause_str_or_none is None: # print('Union child {!r} pith {!r} deeply satisfied!'.format(hint_child, pith)) return cause # Else, this pith deeply violates this child hint. # Cause of this violation. cause_str = cause_child.cause_str_or_none # If this cause is prefixed by the truncated object representation # of this pith... # # Note that this should *ALWAYS* be the case. Nonetheless, let's # *NOT* assume anything to avoid exploding everything. if cause_str.startswith(pith_repr): # Strip the prefixing # representation of this pith from this cause (e.g., the prefix # "MuhClass " from the cause # 'MuhClass not instance of # '). Why? Because the block # of text preceding the bulleted list containing this cause is # already redundantly prefixed by this representation. cause_str = cause_str[PITH_REPR_INDEX:] # Else, this cause is *NOT* prefixed by the truncated object # representation of this pith. In this case, silently accept that # Bad Things have happened and that we should move to a Bad Future. # Append the cause of this violation as a bullet-prefixed line to # the running list of these lines. cause_strs.append(cause_str) # Else, this child hint is PEP-noncompliant. In this case... else: # Assert this child hint to be a non-"typing" class. Note that # the "typing" module should have already guaranteed that all # subscripted arguments of unions are either PEP-compliant type # hints or non-"typing" classes. assert isinstance(hint_child, type), ( f'{cause.exception_prefix}union type hint ' f'{repr(cause.hint)} child hint {repr(hint_child)} invalid ' f'(i.e., neither type hint nor non-"typing" class).') # Else, this child hint is a non-"typing" type. # If this pith is an instance of this class, this pith satisfies # this hint. In this case, return this cause as is. if isinstance(cause.pith, hint_child): return cause # Else, this pith is *NOT* an instance of this class, implying this # pith to *NOT* satisfy this hint. In this case, add this class to # the subset of all classes this pith does *NOT* satisfy. hint_types_violated.add(hint_child) # If this pith fails to shallowly satisfy one or more of the types of this # union, concatenate these failures onto one discrete bullet-prefixed line. if hint_types_violated: # Human-readable comma-delimited disjunction of the names of these # classes (e.g., "bool, float, int, or str"). cause_types_unsatisfied = join_delimited_disjunction_types( hint_types_violated) # Prepend this cause as a discrete bullet-prefixed line. # # Note that this cause is intentionally prependend rather than appended # to this list. Since this cause applies *ONLY* to the shallow type of # the current pith rather than any items contained in this pith, # listing this shallow cause *BEFORE* other deeper causes typically # applying to items contained in this pith produces substantially more # human-readable exception messages: e.g., # # This reads well. # @beartyped pep_hinted() parameter pep_hinted_param=(1,) violates # PEP type hint typing.Union[int, typing.Sequence[str]], as (1,): # * Not int. # * Tuple item 0 value "1" not str. # # # This does not. # @beartyped pep_hinted() parameter pep_hinted_param=(1,) violates # PEP type hint typing.Union[int, typing.Sequence[str]], as (1,): # * Tuple item 0 value "1" not str. # * Not int. # # Note that prepending to lists is an O(n) operation, but that this # cost is negligible in this case both due to the negligible number of # child hints of the average "typing.Union" in general *AND* due to the # fact that this function is only called when a catastrophic type-check # failure has already occurred. cause_strs.insert(0, ( f'not {color_hint(text=cause_types_unsatisfied, is_color=cause.conf.is_color)}' )) # Else, this pith shallowly satisfies *ALL* the types of this union. # If prior logic appended *NO* causes, raise an exception. if not cause_strs: raise _BeartypeCallHintPepRaiseException( f'{cause.exception_prefix}type hint ' f'{repr(cause.hint)} failure causes unknown.' ) # Else, prior logic appended one or more strings describing these failures. # Output cause to be returned, permuted from this input cause such that the # output cause justification is either... cause_return = cause.permute(cause_str_or_none=( # If prior logic appended one cause, a single-line # substring intended to be embedded in a longer string; f'{pith_repr} {cause_strs[0]}' if len(cause_strs) == 1 else # Else, prior logic appended two or more causes. In this case, a # multiline string comprised of... '{}:\n{}'.format( # This truncated object representation followed by... pith_repr, # The newline-delimited concatenation of each cause as a discrete # bullet-prefixed line... '\n'.join( '{}* {}'.format( # Indented by the current indent... cause.cause_indent, # Whose first character is uppercased... uppercase_str_char_first( # Suffixed by a period if not yet suffixed by a period. suffix_str_unless_suffixed(text=cause_str, suffix='.') ) ) # '{}* {}.'.format(cause_indent, uppercase_str_char_first(cause_union)) for cause_str in cause_strs ) ) )) # Return this cause. return cause_return beartype-0.18.5/beartype/_check/error/_pep/errorpep586.py000066400000000000000000000104551461113517100232030ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype** :pep:`586`-compliant **type hint violation describers** (i.e., functions returning human-readable strings explaining violations of :pep:`586`-compliant :attr:`typing.Literal` type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._check.error._errorcause import ViolationCause from beartype._data.hint.pep.sign.datapepsigns import HintSignLiteral from beartype._util.hint.pep.proposal.utilpep586 import ( get_hint_pep586_literals) from beartype._util.text.utiltextjoin import join_delimited_disjunction from beartype._util.text.utiltextrepr import represent_pith # ....................{ GETTERS }.................... def find_cause_literal(cause: ViolationCause) -> ViolationCause: ''' Output cause describing whether the pith of the passed input cause either satisfies or violates the :pep:`586`-compliant :mod:`beartype`-specific **literal** (i.e., :attr:`typing.Literal` type hint) of that cause. Parameters ---------- cause : ViolationCause Input cause providing this data. Returns ---------- ViolationCause Output cause type-checking this data. ''' assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.' assert cause.hint_sign is HintSignLiteral, ( f'{repr(cause.hint_sign)} not "HintSignLiteral".') # Tuple of zero or more literal objects subscripting this hint, # intentionally replacing the current such tuple due to the non-standard # implementation of the third-party "typing_extensions.Literal" factory. hint_childs = get_hint_pep586_literals( hint=cause.hint, exception_prefix=cause.exception_prefix) # If this pith is equal to any literal object subscripting this hint, this # pith satisfies this hint. Specifically, if there exists at least one... if any( # Literal object subscripting this hint such that... ( # This pith is of the same type as that of this literal *AND*... # # Note that PEP 586 explicitly requires this pith to be validated # to be an instance of the same type as this literal *BEFORE* # validated as equal to this literal, due to subtle edge cases in # equality comparison that could yield false positives. isinstance(cause.pith, type(hint_literal)) and # This pith is equal to this literal. cause.pith == hint_literal ) # For each literal object subscripting this hint... for hint_literal in hint_childs ): # Then return this cause unmodified, as this pith deeply satisfies this # hint. return cause # Else, this pith fails to satisfy this hint. # Tuple union of the types of all literals subscripting this hint. hint_literal_types = tuple( type(hint_literal) for hint_literal in hint_childs) # Shallow output cause to be returned, type-checking only whether this pith # is an instance of one or more of these types. cause_shallow = cause.permute(hint=hint_literal_types).find_cause() # If this pith is *NOT* such an instance, return this string. if cause_shallow.cause_str_or_none is not None: return cause_shallow # Else, this pith is such an instance and thus shallowly satisfies this # hint. Since this pith fails to satisfy this hint, this pith must by # deduction be unequal to all literals subscripting this hint. # Human-readable comma-delimited disjunction of the machine-readable # representations of all literal objects subscripting this hint. cause_literals_unsatisfied = join_delimited_disjunction( repr(hint_literal) for hint_literal in hint_childs) # Deep output cause to be returned, permuted from this input cause such that # the justification is a human-readable string describing this failure. cause_deep = cause.permute(cause_str_or_none=( f'{represent_pith(cause.pith)} != {cause_literals_unsatisfied}.')) # Return this cause. return cause_deep beartype-0.18.5/beartype/_check/error/_pep/errorpep593.py000066400000000000000000000107071461113517100232010ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype :pep:`593`-compliant **type hint violation describers** (i.e., functions returning human-readable strings explaining violations of :pep:`593`-compliant :attr:`typing.Annotated` type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeCallHintPepRaiseException from beartype._check.error._errorcause import ViolationCause from beartype._data.hint.pep.sign.datapepsigns import HintSignAnnotated from beartype._util.hint.pep.proposal.utilpep593 import ( get_hint_pep593_metadata, get_hint_pep593_metahint, ) from beartype._data.code.datacodeindent import CODE_INDENT_1 from beartype._util.text.utiltextrepr import represent_pith # ....................{ GETTERS }.................... def find_cause_annotated(cause: ViolationCause) -> ViolationCause: ''' Output cause describing whether the pith of the passed input cause either satisfies or violates the :pep:`593`-compliant :mod:`beartype`-specific **metahint** (i.e., type hint annotating a standard class with one or more :class:`beartype.vale._core._valecore.BeartypeValidator` objects, each produced by subscripting the :class:`beartype.vale.Is` class or a subclass of that class) of that cause. Parameters ---------- cause : ViolationCause Input cause providing this data. Returns ------- ViolationCause Output cause type-checking this data. ''' assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.' assert cause.hint_sign is HintSignAnnotated, ( f'{cause.hint_sign} not "HintSignAnnotated".') # Defer heavyweight imports. from beartype.vale._core._valecore import BeartypeValidator # Type hint annotated by this metahint. metahint = get_hint_pep593_metahint(cause.hint) # Tuple of zero or more arbitrary objects annotating this metahint. hint_validators = get_hint_pep593_metadata(cause.hint) # Shallow output cause to be returned, type-checking only whether this pith # satisfies this metahint. cause_shallow = cause.permute(hint=metahint).find_cause() # If this pith fails to satisfy this metahint, return this cause as is. if cause_shallow.cause_str_or_none is not None: return cause_shallow # Else, this pith satisfies this metahint. # Deep output cause to be returned, permuted from this input cause. cause_deep = cause.permute() # For each beartype validator annotating this metahint... for hint_validator in hint_validators: # If this is *NOT* a beartype validator, raise an exception. # # Note that this object should already be a beartype validator, as the # @beartype decorator enforces this constraint at decoration time. if not isinstance(hint_validator, BeartypeValidator): raise _BeartypeCallHintPepRaiseException( f'{cause_deep.exception_prefix}PEP 593 type hint ' f'{repr(cause_deep.hint)} argument {repr(hint_validator)} ' f'not beartype validator ' f'(i.e., "beartype.vale.Is*[...]" object).' ) # Else, this is a beartype validator. # # If this pith fails to satisfy this validator and is thus the cause of # this failure... elif not hint_validator.is_valid(cause_deep.pith): #FIXME: Unit test this up, please. # Human-readable string diagnosing this failure. hint_diagnosis = hint_validator.get_diagnosis( obj=cause_deep.pith, indent_level_outer=CODE_INDENT_1, indent_level_inner='', ) # Human-readable string describing this failure. cause_deep.cause_str_or_none = ( f'{represent_pith(cause_deep.pith)} violates validator ' f'{repr(hint_validator)}:\n' f'{hint_diagnosis}' ) # Immediately halt iteration. break # Else, this pith satisfies this validator. Ergo, this validator is # *NOT* the cause of this failure. Silently continue to the next. # Return this output cause. return cause_deep beartype-0.18.5/beartype/_check/error/_pep/pep484/000077500000000000000000000000001461113517100215475ustar00rootroot00000000000000beartype-0.18.5/beartype/_check/error/_pep/pep484/__init__.py000066400000000000000000000000001461113517100236460ustar00rootroot00000000000000beartype-0.18.5/beartype/_check/error/_pep/pep484/errornoreturn.py000066400000000000000000000040731461113517100250530ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype** :pep:`484`-compliant :attr:`typing.NoReturn` **type hint violation describers** (i.e., functions returning human-readable strings explaining violations of :pep:`484`-compliant :attr:`typing.NoReturn` type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import Callable from beartype._data.hint.pep.sign.datapepsigns import HintSignNoReturn from beartype._check.error._errorcause import ViolationCause from beartype._util.text.utiltextlabel import label_callable from beartype._util.text.utiltextrepr import represent_pith # ....................{ GETTERS }.................... def find_cause_noreturn(cause: ViolationCause) -> ViolationCause: ''' Output cause describing describing the failure of the decorated callable to *not* return a value in violation of the :pep:`484`-compliant :attr:`typing.NoReturn` type hint. Parameters ---------- cause : ViolationCause Input cause providing this data. Returns ------- ViolationCause Output cause type-checking this data. ''' assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.' assert cause.hint_sign is HintSignNoReturn, ( f'{repr(cause.hint)} not "HintSignNoReturn".') # Decorated callable originating this violation. func: Callable = cause.func # type: ignore[assignment] # Output cause to be returned, permuted from this input cause such that the # justification is a human-readable string describing this failure. cause_return = cause.permute(cause_str_or_none=( f'{label_callable(func)} annotated by PEP 484 return type hint ' f'"typing.NoReturn" returned {represent_pith(cause.pith)}' )) # Return this cause. return cause_return beartype-0.18.5/beartype/_check/error/_pep/pep484585/000077500000000000000000000000001461113517100220115ustar00rootroot00000000000000beartype-0.18.5/beartype/_check/error/_pep/pep484585/__init__.py000066400000000000000000000000001461113517100241100ustar00rootroot00000000000000beartype-0.18.5/beartype/_check/error/_pep/pep484585/errorgeneric.py000066400000000000000000000102501461113517100250470ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype PEP-compliant generic type hint exception raisers** (i.e., functions raising human-readable exceptions called by :mod:`beartype`-decorated callables on the first invalid parameter or return value failing a type-check against the PEP-compliant generic type hint annotating that parameter or return). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._data.hint.pep.sign.datapepsigns import HintSignGeneric from beartype._check.error._errorcause import ViolationCause from beartype._check.error._errortype import find_cause_instance_type from beartype._util.hint.pep.proposal.pep484585.utilpep484585generic import ( get_hint_pep484585_generic_type, iter_hint_pep484585_generic_bases_unerased_tree, ) # ....................{ GETTERS }.................... def find_cause_generic(cause: ViolationCause) -> ViolationCause: ''' Output cause describing whether the pith of the passed input cause either satisfies or violates the :pep:`484`- or :pep:`585`-compliant **generic** (i.e., type hint subclassing a combination of one or more of the :mod:`typing.Generic` superclass, the :mod:`typing.Protocol` superclass, and/or other :mod:`typing` non-class pseudo-superclasses) of that cause. Parameters ---------- cause : ViolationCause Input cause providing this data. Returns ---------- ViolationCause Output cause type-checking this data. ''' assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.' assert cause.hint_sign is HintSignGeneric, ( f'{repr(cause.hint_sign)} not generic.') # print(f'[find_cause_generic] cause.pith: {cause.pith}') # print(f'[find_cause_generic] cause.hint [pre-reduction]: {cause.hint}') # Origin type originating this generic, deduced by stripping all child type # hints subscripting this hint from this hint. hint_type = get_hint_pep484585_generic_type( hint=cause.hint, exception_prefix=cause.exception_prefix) # Shallow output cause to be returned, type-checking only whether this pith # is instance of this origin type. cause_shallow = cause.permute(hint=hint_type) cause_shallow = find_cause_instance_type(cause_shallow) # print(f'[find_cause_generic] cause.hint [post-reduction]: {cause.hint}') # If this pith is *NOT* an instance of this type, return this cause. if cause_shallow.cause_str_or_none is not None: return cause_shallow # Else, this pith is an instance of this type. # For each unignorable unerased transitive pseudo-superclass originally # declared as an erased superclass of this generic... for hint_child in iter_hint_pep484585_generic_bases_unerased_tree( hint=cause.hint, conf=cause.conf, exception_prefix=cause.exception_prefix, ): # Deep output cause to be returned, permuted from this input cause. cause_deep = cause.permute(hint=hint_child).find_cause() # print(f'tuple pith: {pith_item}\ntuple hint child: {hint_child}') # If this pseudo-superclass is the cause of this failure... if cause_deep.cause_str_or_none is not None: # Human-readable string prefixing this failure with additional # metadata describing this pseudo-superclass. cause_deep.cause_str_or_none = ( f'generic base {repr(hint_child)} ' f'{cause_deep.cause_str_or_none}' ) # Return this cause. return cause_deep # Else, this pseudo-superclass is *NOT* the cause of this failure. # Silently continue to the next. # print(f'[find_cause_generic] Ignoring satisfied base {hint_child}...') # Return this cause as is. This pith satisfies both this generic itself # *AND* all pseudo-superclasses subclassed by this generic, implying this # pith to deeply satisfy this hint. return cause beartype-0.18.5/beartype/_check/error/_pep/pep484585/errormapping.py000066400000000000000000000151471461113517100251000ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype :pep:`484`- and :pep:`585`-compliant **mapping type hint violation describers** (i.e., functions returning human-readable strings explaining violations of :pep:`484`- and :pep:`585`-compliant mapping type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype import BeartypeStrategy from beartype.typing import ( Iterable, Tuple, Hashable, ) from beartype._data.hint.pep.sign.datapepsignset import HINT_SIGNS_MAPPING from beartype._check.error._errorcause import ViolationCause from beartype._check.error._errortype import find_cause_type_instance_origin from beartype._util.text.utiltextprefix import prefix_pith_type from beartype._util.text.utiltextrepr import represent_pith # ....................{ FINDERS }.................... def find_cause_mapping(cause: ViolationCause) -> ViolationCause: ''' Output cause describing whether the pith of the passed input cause either satisfies or violates the **mapping type hint** (i.e., PEP-compliant type hint accepting exactly two subscripted arguments constraining *all* key-value pairs of this pith, which necessarily satisfies the :class:`collections.abc.Mapping` protocol) of that cause. Parameters ---------- cause : ViolationCause Input cause providing this data. Returns ------- ViolationCause Output cause type-checking this data. ''' assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.' assert cause.hint_sign in HINT_SIGNS_MAPPING, ( f'{repr(cause.hint)} not mapping hint.') # Assert this mapping was subscripted by exactly two arguments. Note that # the "typing" module should have already guaranteed this on our behalf. assert len(cause.hint_childs) == 2, ( f'Mapping hint {repr(cause.hint)} subscripted by ' f'{len(cause.hint_childs)} != 2.') # print(f'Validating mapping {repr(cause.pith)}...') # Shallow output cause to be returned, type-checking only whether this path # is an instance of the type originating this hint (e.g., "list" for # "list[str]"). cause_shallow = find_cause_type_instance_origin(cause) # If this pith is *NOT* an instance of this type, return this shallow cause. if cause_shallow.cause_str_or_none is not None: return cause_shallow # Else, this pith is an instance of this type and is thus a mapping. # # If this mapping is empty, all items of this mapping (of which there are # none) are valid. This mapping satisfies this hint. Just go with it! elif not cause.pith: return cause # Else, this mapping is non-empty. # Child key and value hints subscripting this mapping hint. hint_key = cause.hint_childs[0] hint_value = cause.hint_childs[1] # True only if these hints are unignorable. hint_key_unignorable = hint_key is not None hint_value_unignorable = hint_value is not None # Arbitrary iterator vaguely satisfying the dict.items() protocol, # yielding zero or more 2-tuples of the form "(key, value)", where: # * "key" is the key of the current key-value pair. # * "value" is the value of the current key-value pair. pith_items: Iterable[Tuple[Hashable, object]] = None # type: ignore[assignment] # If the only the first key-value pair of this mapping was # type-checked by the the parent @beartype-generated wrapper # function in O(1) time, type-check only this key-value pair of this # mapping in O(1) time as well. if cause.conf.strategy is BeartypeStrategy.O1: # First key-value pair of this mapping. pith_item = next(iter(cause.pith.items())) # Tuple containing only this pair. pith_items = (pith_item,) # print(f'Checking item {pith_item_index} in O(1) time!') # Else, all keys of this mapping were type-checked by the parent # @beartype-generated wrapper function in O(n) time. In this case, # type-check *ALL* indices of this mapping in O(n) time as well. else: # Iterator yielding all key-value pairs of this mapping. pith_items = cause.pith.items() # print('Checking mapping in O(n) time!') # For each key-value pair of this mapping... for pith_key, pith_value in pith_items: # If this child key hint is unignorable... if hint_key_unignorable: # Deep output cause, type-checking whether this key satisfies # this child key hint. cause_deep = cause.permute( pith=pith_key, hint=hint_key).find_cause() # If this key is the cause of this failure... if cause_deep.cause_str_or_none is not None: # Human-readable substring prefixing this failure with # metadata describing this key. cause_deep.cause_str_or_none = ( f'{prefix_pith_type(pith=cause.pith, is_color=True)}' f'key {cause_deep.cause_str_or_none}' ) # Return this cause. return cause_deep # Else, this key is *NOT* the cause of this failure. Silently # continue to this value. # Else, this child key hint is ignorable. # If this child value hint is unignorable... if hint_value_unignorable: # Deep output cause, type-checking whether this value satisfies # this child value hint. cause_deep = cause.permute( pith=pith_value, hint=hint_value).find_cause() # If this value is the cause of this failure... if cause_deep.cause_str_or_none is not None: # Human-readable substring prefixing this failure with # metadata describing this value. cause_deep.cause_str_or_none = ( f'{prefix_pith_type(pith=cause.pith, is_color=True)}' f'key {represent_pith(pith_key)} ' f'value {cause_deep.cause_str_or_none}' ) # Return this cause. return cause_deep # Else, this value is *NOT* the cause of this failure. Silently # continue to the key-value pair. # Else, this child value hint is ignorable. # Return this cause as is; all items of this mapping are valid, implying # this mapping to deeply satisfy this hint. return cause beartype-0.18.5/beartype/_check/error/_pep/pep484585/errorsequence.py000066400000000000000000000325651461113517100252600ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype :pep:`484`- and :pep:`585`-compliant **sequence type hint violation describers** (i.e., functions returning human-readable strings explaining violations of :pep:`484`- and :pep:`585`-compliant sequence type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._data.hint.pep.sign.datapepsigns import HintSignTuple from beartype._data.hint.pep.sign.datapepsignset import ( HINT_SIGNS_SEQUENCE_ARGS_1) from beartype._check.error._errorcause import ViolationCause from beartype._check.error._errortype import find_cause_type_instance_origin from beartype._util.hint.pep.proposal.pep484585.utilpep484585 import ( is_hint_pep484585_tuple_empty) from beartype._util.text.utiltextansi import color_type from beartype._util.text.utiltextprefix import prefix_pith_type from beartype._util.text.utiltextrepr import represent_pith # ....................{ FINDERS }.................... def find_cause_sequence_args_1(cause: ViolationCause) -> ViolationCause: ''' Output cause describing whether the pith of the passed input cause either satisfies or violates the **single-argument variadic sequence type hint** (i.e., PEP-compliant type hint accepting exactly one subscripted argument constraining *all* items of this pith, which necessarily satisfies the :class:`collections.abc.Sequence` protocol with guaranteed :math:`O(1)` indexation across all sequence items) of that cause. Parameters ---------- cause : ViolationCause Input cause providing this data. Returns ------- ViolationCause Output cause type-checking this data. ''' assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.' assert cause.hint_sign in HINT_SIGNS_SEQUENCE_ARGS_1, ( f'{repr(cause.hint)} not 1-argument sequence hint.') # Assert this sequence was subscripted by exactly one argument. Note that # the "typing" module should have already guaranteed this on our behalf. assert len(cause.hint_childs) == 1, ( f'1-argument sequence hint {repr(cause.hint)} subscripted by ' f'{len(cause.hint_childs)} != 1.') # Shallow output cause to be returned, type-checking only whether this path # is an instance of the type originating this hint (e.g., "list" for # "list[str]"). cause_shallow = find_cause_type_instance_origin(cause) # Return either... return ( # If this pith is *NOT* an instance of this type, this shallow cause; cause_shallow if cause_shallow.cause_str_or_none is not None else # Else, this pith is an instance of this type and is thus a sequence. # In this case, defer to this function supporting arbitrary sequences. _find_cause_sequence(cause) ) def find_cause_tuple(cause: ViolationCause) -> ViolationCause: ''' Output cause describing whether the pith of the passed input cause either satisfies or violates the **tuple type hint** (i.e., PEP-compliant type hint accepting either zero or more subscripted arguments iteratively constraining each item of this fixed-length tuple *or* exactly one subscripted arguments constraining *all* items of this variadic tuple) of that cause. Parameters ---------- cause : ViolationCause Input cause providing this data. Returns ------- ViolationCause Output cause type-checking this data. ''' assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.' assert cause.hint_sign is HintSignTuple, ( f'{repr(cause.hint_sign)} not "HintSignTuple".') # Shallow output cause to be returned, type-checking only whether this path # is an instance of the type originating this hint (e.g., "list" for # "list[str]"). cause_shallow = find_cause_type_instance_origin(cause) # If this pith is *NOT* a tuple, return this shallow cause. if cause_shallow.cause_str_or_none is not None: return cause_shallow # Else, this pith is a tuple. # # If this hint is a tuple... elif ( # Subscripted by exactly two child hints *AND*... len(cause.hint_childs) == 2 and # The second child hint is just an unquoted ellipsis... cause.hint_childs[1] is Ellipsis ): # Then this hint is of the variadic form "Tuple[{typename}, ...]", typing a # tuple accepting a variadic number of items all satisfying the # child hint "{typename}". Since this case semantically reduces to a simple # sequence, defer to this function supporting arbitrary sequences. return _find_cause_sequence(cause) # Else, this hint is of the fixed-length form "Tuple[{typename1}, ..., # {typenameN}]", typing a tuple accepting a fixed number of items each # satisfying a unique child hint. # # If this hint is the empty fixed-length tuple, validate this pith to be # the empty tuple. elif is_hint_pep484585_tuple_empty(cause.hint): # If this pith is the empty tuple, this path satisfies this hint. if not cause.pith: return cause # Else, this tuple is non-empty and thus fails to satisfy this hint. # Deep output cause to be returned, permuted from this input cause # with a human-readable string describing this failure. cause_deep = cause.permute(cause_str_or_none=( f'tuple {represent_pith(cause.pith)} non-empty')) # Return this cause. return cause_deep # Else, this hint is a standard fixed-length tuple. # If this pith and hint are of differing lengths, this tuple fails to # satisfy this hint. In this case... if len(cause.pith) != len(cause.hint_childs): # Deep output cause to be returned, permuted from this input cause # with a human-readable string describing this failure. cause_deep = cause.permute(cause_str_or_none=( f'tuple {represent_pith(cause.pith)} length ' f'{len(cause.pith)} != {len(cause.hint_childs)}' )) # Return this cause. return cause_deep # Else, this pith and hint are of the same length. # For each enumerated item of this tuple... for pith_item_index, pith_item in enumerate(cause.pith): # Child hint corresponding to this tuple item. Since this pith and # hint are of the same length, this child hint exists. hint_child = cause.hint_childs[pith_item_index] # print(f'tuple pith: {repr(pith_item)}\ntuple hint child: {repr(hint_child)}') # If this child hint is ignorable, continue to the next. if hint_child is None: continue # Else, this child hint is unignorable. # Deep output cause to be returned, type-checking whether this tuple # item satisfies this child hint. # sleuth_copy = cause.permute(pith=pith_item, hint=hint_child) # pith_item_cause = sleuth_copy.find_cause() cause_deep = cause.permute( pith=pith_item, hint=hint_child).find_cause() # If this item is the cause of this failure... if cause_deep.cause_str_or_none is not None: # print(f'tuple pith: {sleuth_copy.pith}\ntuple hint child: {sleuth_copy.hint}\ncause: {pith_item_cause}') # Human-readable substring prefixing this failure with metadata # describing this item. cause_deep.cause_str_or_none = ( f'{prefix_pith_type(pith=cause.pith, is_color=cause.conf.is_color)}' f'index {color_type(text=str(pith_item_index), is_color=cause.conf.is_color)} ' f'item {cause_deep.cause_str_or_none}' ) # Return this cause. return cause_deep # Else, this item is *NOT* the cause of this failure. Silently # continue to the next. # Return this cause as is; all items of this fixed-length tuple are valid, # implying this pith to deeply satisfy this hint. return cause # ....................{ PRIVATE ~ finders }.................... def _find_cause_sequence(cause: ViolationCause) -> ViolationCause: ''' Output cause describing whether the pith of the passed input cause either satisfies or violates the **variadic sequence type hint** (i.e., PEP-compliant type hint accepting one or more subscripted arguments constraining *all* items of this object, which necessarily satisfies the :class:`collections.abc.Sequence` protocol with guaranteed ``O(1)`` indexation across all sequence items) of that cause. Parameters ---------- cause : ViolationCause Input cause providing this data. Returns ------- ViolationCause Output cause type-checking this data. ''' # Assert this type hint to describe a variadic sequence. See the parent # find_cause_sequence_args_1() and find_cause_tuple() # functions for derivative logic. # # Note that this pith need *NOT* be validated to be an instance of the # expected variadic sequence, as the caller guarantees this to be the case. assert isinstance(cause, ViolationCause), f'{repr(cause)} not cause.' assert ( cause.hint_sign in HINT_SIGNS_SEQUENCE_ARGS_1 or ( cause.hint_sign is HintSignTuple and len(cause.hint_childs) == 2 and cause.hint_childs[1] is Ellipsis ) ), (f'{repr(cause.hint)} neither ' f'standard sequence nor variadic tuple hint.') # If this sequence is empty, all items of this sequence (of which there are # none) are valid. This sequence satisfies this hint. Just go with it! if not cause.pith: return cause # Else, this sequence is non-empty. # First child hint subscripting this sequence hint. All remaining child # hints if any are ignorable. Specifically, if this hint is: # * A standard sequence (e.g., "typing.List[str]"), this hint is # subscripted by only one child hint. # * A variadic tuple (e.g., "typing.Tuple[str, ...]"), this hint is # subscripted by only two child hints the latter of which is # ignorable syntactic chuff. hint_child = cause.hint_childs[0] # If this child hint is ignorable, this sequence satisfies this hint. if hint_child is None: return cause # Else, this child hint is unignorable. # Arbitrary iterator satisfying the enumerate() protocol, yielding zero or # more 2-tuples of the form "(item_index, item)", where: # * "item_index" is the 0-based index of this item. # * "item" is an arbitrary item of this sequence. pith_enumerator = None # If this sequence was indexed by the parent @beartype-generated wrapper # function by a pseudo-random integer in O(1) time, type-check *ONLY* the # same index of this sequence also in O(1) time. Since the current call to # that function failed a type-check, either this index is the index # responsible for that failure *OR* this sequence is valid and another # container is responsible for that failure. In either case, no other # indices of this sequence need be checked. if cause.random_int is not None: # 0-based index of this item calculated from this random integer in the # *SAME EXACT WAY* as in the parent @beartype-generated wrapper. pith_item_index = cause.random_int % len(cause.pith) # Pseudo-random item with this index in this sequence. pith_item = cause.pith[pith_item_index] # 2-tuple of this index and item in the same order as the 2-tuples # returned by the enumerate() builtin. pith_enumeratable = (pith_item_index, pith_item) # Iterator yielding only this 2-tuple. pith_enumerator = iter((pith_enumeratable,)) # print(f'Checking item {pith_item_index} in O(1) time!') # Else, this sequence was iterated by the parent @beartype-generated wrapper # function in O(n) time. In this case, type-check *ALL* indices of this # sequence in O(n) time as well. else: # Iterator yielding all indices and items of this sequence. pith_enumerator = enumerate(cause.pith) # print('Checking sequence in O(n) time!') # For each enumerated item of this sequence... for pith_item_index, pith_item in pith_enumerator: # Deep output cause, type-checking whether this item satisfies this # child hint. cause_deep = cause.permute( pith=pith_item, hint=hint_child).find_cause() # If this item is the cause of this failure... if cause_deep.cause_str_or_none is not None: # Human-readable substring prefixing this failure with metadata # describing this item. cause_deep.cause_str_or_none = ( f'{prefix_pith_type(pith=cause.pith, is_color=cause.conf.is_color)}' f'index {color_type(text=str(pith_item_index), is_color=cause.conf.is_color)} ' f'item {cause_deep.cause_str_or_none}' ) # Return this cause. return cause_deep # Else, this item is *NOT* the cause of this failure. Silently continue # to the next. # Return this cause as is; all items of this sequence are valid, implying # this sequence to deeply satisfy this hint. return cause beartype-0.18.5/beartype/_check/error/errorget.py000066400000000000000000000667421461113517100220220ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype exception getters** (i.e., high-level callables creating and returning human-readable exceptions, called by various runtime type-checkers published by :mod:`beartype` when an arbitrary object violates a type hint). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... #FIXME: [ACCESS] Generalizing the "random_int" concept (i.e., the optional #"random_int" parameter accepted by the get_func_pith_violation() function) that #enables O(1) exception handling to containers that do *NOT* provide efficient #random access like mappings and sets will be highly non-trivial. While there #exist a number of alternative means of implementing that generalization, the #most reasonable *BY FAR* is probably to: # #* Embed additional assignment expressions in the type-checking tests generated # by the make_func_pith_code() function that uniquely store the value of # each item, key, or value returned by each access of a non-indexable container # iterator into a new unique local variable. Note this unavoidably requires: # * Adding a new index to the "hint_curr_meta" tuples internally created by # that function -- named, say, "_HINT_META_INDEX_ITERATOR_NAME". The value # of the tuple item at this index should either be: # * If the currently iterated type hint is a non-indexable container, the # name of the new unique local variable assigned to by this assignment # expression whose value is obtained from the iterator cached for that # container. # * Else, "None". # Actually... hmm. Perhaps we only need a new local variable # "iterator_nonsequence_names" whose value is a cached "FixedList" of # sufficiently large size (so, "FIXED_LIST_SIZE_MEDIUM"?). We could then simply # iteratively insert the names of the wrapper-specific new unique local # variables into this list. # Actually... *WAIT.* Is all we need a single counter initialized to, say: # iterators_nonsequence_len = 0 # We then both use that counter to: # * Uniquify the names of these wrapper-specific new unique local variables # during iteration over type hints. # * Trivially generate a code snippet passing a list of these names to the # "iterators_nonsequence" parameter of get_func_pith_violation() function # after iteration over type hints. # Right. That looks like The Way, doesn't it? This would seem to be quite a # bit easier than we'd initially thought, which is always nice. Oi! # * Python >= 3.8, but that's largely fine. Python 3.6 and 3.7 are # increasingly obsolete in 2021. #* Add a new optional "iterators_nonsequence" parameter to the # get_func_pith_violation() function, accepting either: # * If the current parameter or return of the parent wrapper function was # annotated with one or more non-indexable container type hints, a *LIST* of # the *VALUES* of all unique local variables assigned to by assignment # expressions in that parent wrapper function. These values were obtained # from the iterators cached for those containers. To enable these exception # handlers to efficiently treat this list like a FIFO stack (e.g., with the # list.pop() method), this list should be sorted in the reverse order that # these assignment expressions are defined in. #* Refactor exception handlers to then preferentially retrieve non-indexable # container items in O(1) time from this stack rather than simply iterating # over all container items in O(n) brute-force time. Obviously, extreme care # must be taken here to ensure that this exception handling algorithm visits # containers in the exact same order as visited by our testing algorithm. #FIXME: [COLOR] The call to the strip_text_ansi() function below is inefficient #and thus non-ideal. Since efficiency isn't a pressing concern in an exception #raiser, this is more a matter of design purity than anything. Still, it would #be preferable to avoid embedding ANSI escape sequences when the user requests #that rather than forcibly stripping those sequences out after the fact via an #inefficient regex. To do so, we'll want to: #* Augment the color_*() family of functions with a mandatory "conf: # BeartypeConf" parameter. #* Pass that parameter to *EVERY* call to one of those functions. #* Refactor those functions to respect that parameter. The ideal means of # doing so would probably be define in the # "beartype._util.text.utiltextansi" submodule: # * A new "_BeartypeTheme" dataclass mapping from style names to format # strings embedding the ANSI escape sequences styling those styles. # * A new pair of private "_THEME_MONOCHROME" and "_THEME_PRISMATIC" # instances of that dataclass. The values of the "_THEME_MONOCHROME" # dictionary should all just be the default format string: e.g., # _THEME_MONOCHROME = _BeartypeTheme( # format_error='{text}', # ... # ) # # _THEME_PRISMATIC = _BeartypeTheme( # format_error=f'{_STYLE_BOLD}{_COLOUR_RED}{{text}}{_COLOUR_RESET}', # ... # ) # * A new "_THEME_DEFAULT" instance of that dataclass conditionally defined # as either "_THEME_MONOCHROME" or "_THEME_PRISMATIC" depending on # whether stdout is attached to a TTY or not. Alternately, to avoid # performing that somewhat expensive logic at module scope (and thus on # initial beartype importation), it might be preferable to instead define # a new cached private getter resembling: # # @callable_cached # def _get_theme_default() -> _BeartypeTheme: # return ( # _THEME_PRISMATIC # if is_stdout_terminal() else # _THEME_MONOCHROME # ) # ....................{ IMPORTS }.................... from beartype.meta import URL_ISSUES from beartype.roar._roarexc import ( _BeartypeCallHintPepRaiseDesynchronizationException, _BeartypeCallHintPepRaiseException, ) from beartype.typing import Optional from beartype._check.error._errorcause import ViolationCause from beartype._conf.confcls import ( BEARTYPE_CONF_DEFAULT, BeartypeConf, ) from beartype._conf.confenum import BeartypeViolationVerbosity from beartype._data.func.datafuncarg import ARG_NAME_RETURN from beartype._data.hint.datahinttyping import ( TypeException, TypeStack, ) from beartype._util.text.utiltextansi import ( color_hint, strip_str_ansi, ) from beartype._util.text.utiltextmunge import ( suffix_str_unless_suffixed, uppercase_str_char_first, ) from beartype._util.text.utiltextprefix import ( prefix_callable_return_value, prefix_callable_arg_value, prefix_pith_value, ) from beartype._util.text.utiltextrepr import represent_object from collections.abc import Callable as CallableABC # ....................{ GETTERS }.................... def get_func_pith_violation( # Mandatory parameters. func: CallableABC, conf: BeartypeConf, pith_name: str, pith_value: object, # Optional keyword parameters. **kwargs ) -> Exception: ''' Human-readable exception detailing the failure of the parameter with the passed name *or* return if this name is the magic string ``return`` of the passed decorated function fails to satisfy the type hint annotating this parameter or return. Parameters ---------- func : CallableTypes Decorated callable to raise this exception from. conf : BeartypeConf **Beartype configuration** (i.e., self-caching dataclass encapsulating all flags, options, settings, and other metadata configuring the current decoration of the decorated callable or class). pith_name : str Either: * If the object failing to satisfy this hint is a passed parameter, the name of this parameter. * Else, the magic string ``"return"`` implying this object to be the value returned from this callable. pith_value : object Passed parameter or returned value violating this hint. All remaining keyword parameters are passed as is to the :func:`.get_hint_object_violation` getter. Returns ------- Exception Human-readable exception detailing the failure of this parameter or return to satisfy the type hint annotating this parameter or return. This is guaranteed to be an instance of either: * If this is a parameter, :attr:`.BeartypeConf.violation_param_type`. * If this is a return, :attr:`.BeartypeConf.violation_return_type`. Raises ------ All exceptions raised by the lower-level :func:`.get_hint_object_violation` getter as well as: _BeartypeCallHintPepRaiseException If the parameter or return with the passed name is unannotated. See Also -------- :func:`.get_hint_object_violation` Further details. ''' assert callable(func), f'{repr(func)} uncallable.' assert isinstance(pith_name, str), f'{repr(pith_name)} not string.' # If this parameter or return value is unannotated, raise an exception. # # Note that this should *NEVER* occur, as the caller guarantees this # parameter or return to be annotated. However, since malicious callers # *COULD* deface the "__annotations__" dunder dictionary without our # knowledge or permission, precautions are warranted. if pith_name not in func.__annotations__: raise _BeartypeCallHintPepRaiseException(f'{repr(func)} unannotated.') # Else, this parameter or return value is annotated. # Type hint annotating this parameter or return value. # # Note that we intentionally avoid calling the __annotations__.get() method # to obtain this hint. Since "None" is a valid type hint, calling that # method gains us nothing over the current approach. hint = func.__annotations__[pith_name] # Defer to this lower-level violation factory. return get_hint_object_violation( obj=pith_value, hint=hint, conf=conf, func=func, pith_name=pith_name, **kwargs ) def get_hint_object_violation( # Mandatory parameters. obj: object, hint: object, conf: BeartypeConf, # Optional parameters. func: Optional[CallableABC] = None, cls_stack: TypeStack = None, exception_prefix: Optional[str] = None, pith_name: Optional[str] = None, random_int: Optional[int] = None, ) -> Exception: ''' Human-readable exception detailing the failure of the passed object to satisfy the passed type hint under the passed beartype configuration. This function intentionally returns rather than raises this exception. Why? Because the ignorable stack frame encapsulating the call of the parent type-checking wrapper function generated by the :mod:`beartype.beartype` decorator complicates inspection of type-checking violations in tracebacks (especially from :mod:`pytest`, which unhelpfully recapitulates the full definition of this function including this docstring in those tracebacks). Instead, that wrapper function raises this exception directly from itself. Design ------ The :mod:`beartype` package actually implements two parallel PEP-compliant runtime type-checkers, each complementing the other by providing functionality unsuited for the other. These are: * The :mod:`beartype._check.code` submodule, dynamically generating optimized PEP-compliant runtime type-checking code embedded in the body of the wrapper function wrapping the decorated callable. For both efficiency and maintainability, that code only tests whether or not a parameter passed to that callable or value returned from that callable satisfies a PEP-compliant annotation on that callable; that code does *not* raise human-readable exceptions in the event that value fails to satisfy that annotation. Instead, that code defers to... * This function, performing unoptimized PEP-compliant runtime type-checking generically applicable to all wrapper functions. The aforementioned code calls this function only in the event that value fails to satisfy that annotation, in which case this function then returns a human-readable exception after discovering the underlying cause of this type failure by recursively traversing that value and annotation. While efficiency is the foremost focus of this package, efficiency is irrelevant during exception handling -- which typically only occurs under infrequent edge cases. Likewise, while raising this exception *would* technically be feasible from the aforementioned code, doing so proved sufficiently non-trivial, fragile, and ultimately unmaintainable to warrant offloading to this function universally callable from all wrapper functions. Parameters ---------- obj : object Arbitrary object to be type-checked against this type hint. hint : object Type hint against which to type-check this object. conf : BeartypeConf **Beartype configuration** (i.e., self-caching dataclass encapsulating all flags, options, settings, and other metadata configuring the validation of this object against this type hint). func : Optional[CallableABC] Either: * If this violation originates from a decorated callable, that callable. * Else, :data:`None`. Defaults to :data:`None`. cls_stack : TypeStack, optional **Type stack** (i.e., either a tuple of the one or more :func:`beartype.beartype`-decorated classes lexically containing the class variable or method annotated by this hint *or* :data:`None`). Defaults to :data:`None`. exception_prefix : Optional[str] Either: * If the caller prefers specifying an explicit human-readable label prefixing the representation of this object in the exception message, that labal. * Else, :data:`None`. In this case, this getter automatically synthesizes this label from the other passed parameters that are required to be non-:data:`None`. If any such parameter is :data:`None`, an exception is raised. These parameters include: * The passed ``func`` parameter, required to be non-:data:`None`. * The passed ``pith_name`` parameter, required to be non-:data:`None`. pith_name : Optional[str] Either: * If this hint annotates a parameter of some callable, the name of that parameter. * If this hint annotates the return of some callable, ``"return"``. * Else, :data:`None`. Defaults to :data:`None`. random_int: Optional[int], optional **Pseudo-random integer** (i.e., unsigned 32-bit integer pseudo-randomly generated by the parent :func:`beartype.beartype` wrapper function in type-checking randomly indexed container items by the current call to that function) if that function generated such an integer *or* :data:`None` otherwise (i.e., if that function generated *no* such integer). Note that this parameter critically governs whether this exception handler runs in constant or linear time. Specifically, if this parameter is: * An integer, this handler runs in **constant time.** Since there exists a one-to-one relation between this integer and the random container item(s) type-checked by the parent :func:`beartype.beartype` wrapper function, receiving this integer enables this handler to efficiently re-type-check the same random container item(s) type-checked by the parent in constant time rather type-checking all container items in linear time. * :data:`None`, this handler runs in **linear time.** Defaults to :data:`None`, implying this exception handler runs in linear time by default. Returns ------- Exception Human-readable exception detailing the failure of this object to satisfy the type hint. This is guaranteed to be an instance of either: * If this is a parameter, :attr:`.BeartypeConf.violation_param_type`. * If this is a return, :attr:`.BeartypeConf.violation_return_type`. * Else, :attr:`.BeartypeConf.violation_door_type`. Raises ------ BeartypeDecorHintPepException If the type hint annotating this object is *not* PEP-compliant. _BeartypeCallHintPepRaiseException If all three of the ``exception_prefix``,``func``, and ``pith_name`` parameters are :data:`None`. _BeartypeCallHintPepRaiseDesynchronizationException If this pith actually satisfies this hint, implying either: * The parent wrapper function generated by the :mod:`beartype.beartype` decorator type-checking this pith triggered a false negative by erroneously misdetecting this pith as failing this type check. * This child helper function re-type-checking this pith triggered a false positive by erroneously misdetecting this pith as satisfying this type check when in fact this pith fails to do so. ''' # print('''get_hint_object_violation( # func={!r}, # hint={!r}, # conf={!r}, # pith_name={!r}, # pith_value={!r}', # )'''.format(func, hint, conf, pith_name, pith_value)) # ....................{ LOCALS }.................... # Type of violation to be raised. exception_cls: TypeException = None # type: ignore[assignment] # If the caller passed *NO* parameter name, the passed object is neither a # parameter nor return of a decorated callable. By elimination, this object # *MUST* have been directly passed to the beartype.door.die_if_unbearable() # type-checker. In this case... if pith_name is None: # If the caller also passed *NO* exception prefix, raise an exception. if exception_prefix is None: raise _BeartypeCallHintPepRaiseException( 'get_hint_object_violation() passed neither ' '"exception_prefix" nor "pith_name" parameters.' ) # Else, the caller passed an exception prefix. # Default the exception class appropriately. exception_cls = conf.violation_door_type # Suffix this exception prefix with an additional noun for disambiguity. exception_prefix = ( f'{exception_prefix}value ' f'{prefix_pith_value(pith=obj, is_color=conf.is_color)}' ) # Else, the caller passed a parameter name. In this case... else: # If the caller also passed an exception prefix, raise an exception. if exception_prefix is not None: raise _BeartypeCallHintPepRaiseException( 'get_hint_object_violation() passed both ' '"exception_prefix" and "pith_name" parameters.' ) # Else, the caller passed *NO* exception prefix. # If the name of this parameter is the magic string implying the passed # object to be a return value... if pith_name == ARG_NAME_RETURN: # Default these exception locals appropriately exception_cls = conf.violation_return_type exception_prefix = prefix_callable_return_value( func=func, # type: ignore[arg-type] return_value=obj, is_color=conf.is_color, ) # Else, the passed object is a parameter. In this case... else: # Default these exception locals appropriately exception_cls = conf.violation_param_type exception_prefix = prefix_callable_arg_value( func=func, # type: ignore[arg-type] arg_name=pith_name, arg_value=obj, is_color=conf.is_color, ) # Uppercase the first character of this violation prefix for readability. exception_prefix = uppercase_str_char_first(exception_prefix) # ....................{ CAUSE }.................... # Cause describing the failure of this pith to satisfy this hint. violation_cause = ViolationCause( cause_indent='', cls_stack=cls_stack, conf=conf, exception_prefix=exception_prefix, func=func, hint=hint, pith=obj, pith_name=pith_name, random_int=random_int, ).find_cause() # If this pith satisfies this hint, *SOMETHING HAS GONE TERRIBLY AWRY.* # # In theory, this should never happen, as the parent wrapper function # performing type checking should *ONLY* call this child helper function # when this pith does *NOT* satisfy this hint. In this case, raise an # exception encouraging the end user to submit an upstream issue with us. if not violation_cause.cause_str_or_none: pith_value_repr = represent_object( obj=obj, max_len=_CAUSE_TRIM_OBJECT_REPR_MAX_LEN) raise _BeartypeCallHintPepRaiseDesynchronizationException( f'{exception_prefix}violates type hint {repr(hint)}, ' f'but violation factory get_hint_object_violation() ' f'erroneously suggests this object satisfies this hint. ' f'Please report this desynchronization failure to ' f'the beartype issue tracker ({URL_ISSUES}) with ' f'the accompanying exception traceback and ' f'the representation of this object:\n' f' {pith_value_repr}\n' f'The bear groans in disappointment. If you feel similarly, ' f'know that you are not alone.' ) # Else, this pith violates this hint as expected and as required for sanity. # This failure suffixed by a period if *NOT* yet suffixed by a period. violation_cause_suffixed = suffix_str_unless_suffixed( text=violation_cause.cause_str_or_none, suffix='.') # List of the one or more culprits responsible for this violation, # initialized to the passed parameter or returned value violating this hint. violation_culprits = [obj,] # If the actual object directly responsible for this violation is *NOT* the # passed parameter or returned value indirectly violating this hint, then # the latter is almost certainly a container transitively containing the # former as an item. In this case, add this item to this list as well. if obj is not violation_cause.pith: violation_culprits.append(violation_cause.pith) # Else, the actual object directly responsible for this violation is the # passed parameter or returned value indirectly violating this hint. In this # case, avoid adding duplicate items to this list. # ....................{ VERBOSITY }.................... # Violation verbosity, localized for negligible efficiency. *vomits* violation_verbosity = conf.violation_verbosity # Machine-readable representation of this hint embellished with colour. hint_repr = f'{color_hint(text=repr(hint), is_color=conf.is_color)}' # Dictionary mapping from each possibly violation verbosity to a # corresponding substring prepending this exception message. VIOLATION_VERBOSITY_TO_PREFIX = { BeartypeViolationVerbosity.MINIMAL: ( f'{exception_prefix}was expected to be of type {hint_repr}'), BeartypeViolationVerbosity.DEFAULT: ( f'{exception_prefix}violates type hint {hint_repr}'), } VIOLATION_VERBOSITY_TO_PREFIX[BeartypeViolationVerbosity.MAXIMAL] = ( # <-- alias! VIOLATION_VERBOSITY_TO_PREFIX[BeartypeViolationVerbosity.DEFAULT]) # Dictionary mapping from each possibly violation verbosity to a # corresponding substring embedded in the middle of this exception message. VIOLATION_VERBOSITY_TO_INFIX = { BeartypeViolationVerbosity.MINIMAL: '', BeartypeViolationVerbosity.DEFAULT: '', BeartypeViolationVerbosity.MAXIMAL: ( # If this configuration is the default configuration, avoid # needlessly representing this default configuration. '' if conf == BEARTYPE_CONF_DEFAULT else # Else, this configuration is *NOT* the default configuration. In # this case, append the machine-readable representation of this # non-default configuration to this exception message for # disambiguity and clarity. f' under non-default configuration {repr(conf)}' ), } # Dictionary mapping from each possibly violation verbosity to a # corresponding substring appending this exception message. VIOLATION_VERBOSITY_TO_SUFFIX = { BeartypeViolationVerbosity.MINIMAL: '.', BeartypeViolationVerbosity.DEFAULT: f', as {violation_cause_suffixed}', } VIOLATION_VERBOSITY_TO_SUFFIX[BeartypeViolationVerbosity.MAXIMAL] = ( # <-- alias! VIOLATION_VERBOSITY_TO_SUFFIX[BeartypeViolationVerbosity.DEFAULT]) # ....................{ EXCEPTION }.................... # Human-readable violation message to be raised. exception_message = ( f'{VIOLATION_VERBOSITY_TO_PREFIX[violation_verbosity]}' f'{VIOLATION_VERBOSITY_TO_INFIX[violation_verbosity]}' f'{VIOLATION_VERBOSITY_TO_SUFFIX[violation_verbosity]}' ) #FIXME: In theory, this should no longer be needed. Consider: #* Refactoring all instances of "is_color=True" throughout this subpackage # to instead read "is_color=cause.conf.is_color". #* Refactoring all calls to the represent_pith() function throughout this # subpackage to additionally pass a new optional # "is_color=cause.conf.is_color" parameter. #* Refactoring this call away. #* Validating with unit tests that violation messages contain *NO* ANSI when # configured such that "BeartypeConf(is_color=False)". # Strip all ANSI escape sequences from this message if requested by this # external user-defined configuration. exception_message = strip_str_ansi( text=exception_message, is_color=conf.is_color) # Exception of the desired class embedding this cause. By default, attempt # to pass @beartype-specific parameters to this exception subclass. try: exception = exception_cls( # type: ignore[call-arg] message=exception_message, # pyright: ignore culprits=tuple(violation_culprits), # pyright: ignore ) # If this exception subclass fails to support @beartype-specific parameters, # fallback to the standard exception idiom of a positionally passed message. except TypeError: exception = exception_cls(exception_message) # Return this exception to the @beartype-generated type-checking wrapper # (which directly calls this function), which will then squelch the # ignorable stack frame encapsulating that call to this function by raising # this exception directly from that wrapper. return exception # ....................{ PRIVATE ~ constants }.................... # Assuming a line length of 80 characters, this magic number truncates # arbitrary object representations to 100 lines (i.e., 8000/80), which seems # more than reasonable and (possibly) not overly excessive. _CAUSE_TRIM_OBJECT_REPR_MAX_LEN = 8000 ''' Maximum length of arbitrary object representations suffixing human-readable strings returned by the :func:`_find_cause` getter function, intended to be sufficiently long to assist in identifying type-check failures but not so excessively long as to prevent human-readability. ''' beartype-0.18.5/beartype/_check/forward/000077500000000000000000000000001461113517100201135ustar00rootroot00000000000000beartype-0.18.5/beartype/_check/forward/__init__.py000066400000000000000000000000001461113517100222120ustar00rootroot00000000000000beartype-0.18.5/beartype/_check/forward/fwdmain.py000066400000000000000000000777221461113517100221310ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **stringified type hint utilities** (i.e., low-level callables handling **stringified type hints** (i.e., declared as :pep:`484`- or :pep:`563`-compliant forward references referring to actual type hints that have yet to be declared in the local and global scopes declaring a callable or class)). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from __future__ import annotations from beartype.roar import ( BeartypeDecorHintForwardRefException, BeartypeDecorHintPep604Exception, ) from beartype.roar._roarexc import _BeartypeUtilCallableScopeNotFoundException from beartype.typing import Optional from beartype._check.checkcall import BeartypeCall from beartype._check.forward.fwdscope import BeartypeForwardScope from beartype._data.hint.datahinttyping import TypeException from beartype._data.kind.datakinddict import DICT_EMPTY from beartype._data.kind.datakindset import FROZENSET_EMPTY from beartype._util.cls.utilclsget import get_type_locals from beartype._util.func.utilfuncscope import ( get_func_globals, get_func_locals, ) from beartype._util.module.utilmodget import get_object_module_name from beartype._util.py.utilpyversion import IS_PYTHON_AT_MOST_3_9 from builtins import __dict__ as func_builtins # type: ignore[attr-defined] # ....................{ RESOLVERS }.................... #FIXME: Unit test us up, please. def resolve_hint( # Mandatory parameters. hint: str, bear_call: BeartypeCall, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintForwardRefException, exception_prefix: str = '', ) -> object: ''' Resolve the passed **stringified type hint** (i.e., declared as a :pep:`484`- or :pep:`563`-compliant forward reference referring to an actual type hint that has yet to be declared in the local and global scopes declaring the currently decorated class or callable) to the non-string type hint to which this stringified type hint refers. This resolver is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator). Resolving both absolute *and* relative forward references assumes contextual context (e.g., the fully-qualified name of the object to which relative forward references are relative to) that *cannot* be safely and context-freely memoized away. Parameters ---------- hint : str Stringified type hint to be resolved. bear_call : BeartypeCall Decorated callable annotated by this hint. exception_cls : Type[Exception], optional Type of exception to be raised in the event of a fatal error. Defaults to :exc:`.BeartypeDecorHintForwardRefException`. exception_prefix : str Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- object Either: * If this possibly PEP-noncompliant hint is coercible, a PEP-compliant type hint coerced from this hint. * Else, this hint as is unmodified. Raises ------ exception_cls If attempting to dynamically evaluate this stringified type hint into a non-string type hint against both the global and local scopes of the decorated callable raises an exception, typically due to this stringified type hint being syntactically invalid. BeartypeDecorHintPep604Exception If the active Python interpreter is Python <= 3.9 and this stringified type hint is a :pep:`604`-compliant new-style union, which requires Python >= 3.10. ''' assert isinstance(hint, str), f'{repr(hint)} not stringified type hint.' assert isinstance(bear_call, BeartypeCall), ( f'{repr(bear_call)} not @beartype call.') # print(f'Resolving stringified type hint {repr(hint)}...') # ..................{ LOCALS }.................. # Decorated callable and metadata associated with that callable, localized # to improve both readability and negligible efficiency when accessed below. func = bear_call.func_wrappee_wrappee # If the frozen set of the unqualified names of all parent callables # lexically containing this decorated callable has yet to be decided... if bear_call.func_wrappee_scope_nested_names is None: # Decide this frozen set as either... bear_call.func_wrappee_scope_nested_names = ( # If the decorated callable is nested, the non-empty frozen set of # the unqualified names of all parent callables lexically containing # this nested decorated callable (including this nested decorated # callable itself); frozenset(func.__qualname__.rsplit(sep='.')) if bear_call.func_wrappee_is_nested else # Else, the decorated callable is a global function. In this # case, the empty frozen set. FROZENSET_EMPTY ) # Else, this frozen set has already been decided. # # In either case, this frozen set is now decided. I choose you! # If this hint is the unqualified name of a parent callable or class of the # decorated callable, then this hint is a relative forward reference to a # parent callable or class of the decorated callable that is currently being # defined but has yet to be defined in full. If PEP 563 postponed this type # hint under "from __future__ import annotations", this hint *MUST* have # been a locally or globally scoped attribute of the decorated callable # before being postponed by PEP 563 into a relative forward reference to # that attribute: e.g., # from __future__ import annotations # # # If this is a PEP 563-postponed type hint... # class MuhClass: # @beartype # def muh_method(self) -> 'MuhClass': ... # # # ...then the original type hints prior to being postponed *MUST* # # have annotated this pre-PEP 563 method signature. # class MuhClass: # @beartype # def muh_method(self) -> MuhClass: ... # # In this case, avoid attempting to resolve this forward reference. Why? # Disambiguity. Although the "MuhClass" class has yet to be defined at the # time @beartype decorates the muh_method() method, an attribute of the same # name may already have been defined at that time: e.g., # # While bad form, PEP 563 postpones this valid logic... # MuhClass = "Just kidding! Had you going there, didn't I?" # class MuhClass: # @beartype # def muh_method(self) -> MuhClass: ... # # # ...into this relative forward reference. # MuhClass = "Just kidding! Had you going there, didn't I?" # class MuhClass: # @beartype # def muh_method(self) -> 'MuhClass': ... # # Naively resolving this forward reference would erroneously replace this # hint with the previously declared attribute rather than the class # currently being declared: e.g., # # Naive PEP 563 resolution would replace the above by this! # MuhClass = "Just kidding! Had you going there, didn't I?" # class MuhClass: # @beartype # def muh_method(self) -> ( # "Just kidding! Had you going there, didn't I?"): ... # # This isn't just an edge-case disambiguity, however. This situation # commonly arises when reloading modules containing @beartype-decorated # callables annotated with self-references (e.g., by passing those modules # to the standard importlib.reload() function). Why? Because module # reloading is ill-defined and mostly broken under Python. Since the # importlib.reload() function fails to delete any of the attributes of the # module to be reloaded before reloading that module, the parent callable or # class referred to by this hint will be briefly defined for the duration of # @beartype's decoration of the decorated callable as the prior version of # that parent callable or class! # # Resolving this hint would thus superficially succeed, while actually # erroneously replacing this hint with the prior rather than current version # of that parent callable or class. @beartype would then wrap the decorated # callable with a wrapper expecting the prior rather than current version of # that parent callable or class. All subsequent calls to that wrapper would # then fail. Since this actually happened, we ensure it never does again. # # Lastly, note that this edge case *ONLY* supports top-level relative # forward references (i.e., syntactically valid Python identifier names # subscripting *NO* parent type hints). Child relative forward references # will continue to raise exceptions. As resolving PEP 563-postponed type # hints effectively reduces to a single "all or nothing" call of the # low-level eval() builtin accepting *NO* meaningful configuration, there # exists *NO* means of only partially resolving parent type hints while # preserving relative forward references subscripting those hints. The # solution in those cases is for end users to either: # # * Decorate classes rather than methods: e.g., # # Users should replace this method decoration, which will fail at # # runtime... # class MuhClass: # @beartype # def muh_method(self) -> list[MuhClass]: ... # # # ...with this class decoration, which will work. # @beartype # class MuhClass: # def muh_method(self) -> list[MuhClass]: ... # * Replace implicit with explicit forward references: e.g., # # Users should replace this implicit forward reference, which will # # fail at runtime... # class MuhClass: # @beartype # def muh_method(self) -> list[MuhClass]: ... # # # ...with this explicit forward reference, which will work. # class MuhClass: # @beartype # def muh_method(self) -> list['MuhClass']: ... # # Indeed, the *ONLY* reasons we support this common edge case are: # * This edge case is indeed common. # * This edge case is both trivial and efficient to support. # # tl;dr: Preserve this hint for disambiguity by reducing to a noop. if hint in bear_call.func_wrappee_scope_nested_names: # type: ignore[operator] return hint # Else, this hint is *NOT* the unqualified name of a parent callable or # class of the decorated callable. In this case, this hint *COULD* require # dynamic evaluation under the eval() builtin. Why? Because this hint could # simply be the stringified name of a PEP 563-postponed unsubscripted # "typing" non-class attribute imported at module scope. While valid as a # type hint, this attribute is *NOT* a class. Returning this stringified # hint as is would erroneously instruct our code generation algorithm to # treat this stringified hint as a relative forward reference to a class. # Instead, evaluate this stringified hint into its referent below: e.g., # from __future__ import annotations # from typing import Hashable # # # PEP 563 postpones this into: # # def muh_func() -> 'Hashable': # def muh_func() -> Hashable: # return 'This is hashable, yo.' # If the forward scope of the decorated callable has yet to be decided... if bear_call.func_wrappee_scope_forward is None: # Localize metadata for readability and efficiency. Look. Just do it. cls_stack = bear_call.cls_stack # Fully-qualified name of the module declaring the decorated callable, # which also serves as the name of this module and thus global scope. func_module_name = get_object_module_name(func) # type: ignore[operator] # Global scope of the decorated callable. func_globals = get_func_globals(func=func, exception_cls=exception_cls) # If the decorated callable is nested (rather than global) and thus # *MAY* have a non-empty local nested scope... if bear_call.func_wrappee_is_nested: # Attempt to... try: # Local scope of the decorated callable, localized to improve # readability and negligible efficiency when accessed below. func_locals = get_func_locals( func=func, # Ignore all lexical scopes in the fully-qualified name of # the decorated callable corresponding to parent classes # lexically nesting the current decorated class containing # that callable (including that class). Why? Because these # classes are *ALL* currently being decorated and thus have # yet to be encapsulated by new stack frames on the call # stack. If these lexical scopes are *NOT* ignored, this # call to get_func_locals() will fail to find the parent # lexical scope of the decorated callable and then raise an # unexpected exception. # # Consider, for example, this nested class decoration of a # fully-qualified "muh_package.Outer" class: # @beartype # class Outer(object): # class Middle(object): # class Inner(object): # def muh_method(self) -> str: # return 'Painful API is painful.' # # When @beartype finally recurses into decorating the nested # muh_package.Outer.Middle.Inner.muh_method() method, this # call to get_func_locals() if *NOT* passed this parameter # would naively assume that the parent lexical scope of the # current muh_method() method on the call stack is named # "Inner". Instead, the parent lexical scope of that method # on the call stack is named "muh_package" -- the first # lexical scope enclosing that method that exists on the # call stack. The non-existent "Outer", "Middle", and # "Inner" lexical scopes must *ALL* be silently ignored. func_scope_names_ignore=( 0 if cls_stack is None else len(cls_stack)), #FIXME: Consider dynamically calculating exactly how many #additional @beartype-specific frames are ignorable on the #first call to this function, caching that number, and then #reusing that cached number on all subsequent calls to this #function. The current approach employed below of naively #hard-coding a number of frames to ignore was incredibly #fragile and had to be effectively disabled, which hampers #runtime efficiency. # Ignore additional frames on the call stack embodying: # * The current call to this function. # # Note that, for safety, we currently avoid ignoring # additional frames that we could technically ignore. These # include: # * The call to the parent # beartype._check.checkcall.BeartypeCall.reinit() method. # * The call to the parent @beartype.beartype() decorator. # # Why? Because the @beartype codebase has been sufficiently # refactored so as to render any such attempts non-trivial, # fragile, and frankly dangerous. func_stack_frames_ignore=1, exception_cls=exception_cls, ) # If this local scope cannot be found (i.e., if this getter found # the lexical scope of the module declaring the decorated callable # *BEFORE* that of the parent callable or class declaring that # callable), then this resolve_hint() function was called *AFTER* # rather than *DURING* the declaration of the decorated callable. # This implies that that callable is not, in fact, currently being # decorated. Instead, that callable was *NEVER* decorated by # @beartype but has instead subsequently been passed to this # resolve_hint() function after its initial declaration -- typically # due to an external caller passing that callable to our public # beartype.peps.resolve_pep563() function. # # In this case, the call stack frame providing this local scope has # (almost certainly) already been deleted and is no longer # accessible. We have no recourse but to default this local scope to # the empty dictionary -- which might be subsequently modified and # *CANNOT* thus default to the singleton empty dictionary # "DICT_EMPTY" (unlike below). except _BeartypeUtilCallableScopeNotFoundException: func_locals = {} # If the decorated callable is a method transitively defined by a # root decorated class, add a pair of local attributes exposing: # # * The unqualified basename of the root decorated class. Why? # Because this class may be recursively referenced in postponed # type hints and *MUST* thus be exposed to *ALL* postponed type # hints. However, this class is currently being decorated and thus # has yet to be defined in either: # * If this class is module-scoped, the global attribute # dictionary of that module and thus the "func_globals" # dictionary. # * If this class is closure-scoped, the local attribute # dictionary of that closure and thus the "func_locals" # dictionary. # * The unqualified basename of the current decorated class. Why? # For similar reasons. Since the current decorated class may be # lexically nested in the root decorated class, the current # decorated class is *NOT* already accessible as either a global # or local. Exposing the current decorated class to a stringified # type hint referencing that class thus requires adding a local # attribute exposing that class. # # Note that: # * *ALL* intermediary classes (i.e., excluding the root decorated # class) lexically nesting the current decorated class are # irrelevant. Intermediary classes are neither module-scoped nor # closure-scoped and thus inaccessible as either globals or locals # in the nested lexical scope of the current decorated class: # e.g., # # This raises a parser error and is thus *NOT* fine: # # NameError: name 'muh_type' is not defined # class Outer(object): # class Middle(object): # muh_type = str # # class Inner(object): # def muh_method(self) -> muh_type: # return 'Dumpster fires are all I see.' # * This implicitly overrides any previously declared locals of the # same name. Although non-ideal, this constitutes syntactically # valid Python and is thus *NOT* worth emitting even a non-fatal # warning over: e.g., # # This is fine... technically. # from beartype import beartype # def muh_closure() -> None: # MuhClass = 'This is horrible, yet fine.' # # @beartype # class MuhClass(object): # def muh_method(self) -> str: # return 'Look away and cringe, everyone!' if cls_stack: # Root and current decorated classes. cls_root = cls_stack[0] cls_curr = cls_stack[-1] # Add new locals exposing these classes to type hints, # overwriting any locals of the same names in the higher-level # local scope for any closure declaring this class if any. These # classes are currently being decorated and thus guaranteed to # be the most recent declarations of these attributes. # # Note that the current class assumes lexical precedence over # the root class and is thus added *AFTER* the latter. func_locals[cls_root.__name__] = cls_root func_locals[cls_curr.__name__] = cls_curr # Local scope for the class directly defining this method. # # Note that callables *ONLY* have direct access to attributes # declared by the classes directly defining those callables. # Ergo, the local scopes for parent classes of this class # (including the root decorated class) are irrelevant. cls_curr_locals = get_type_locals( cls=cls_curr, exception_cls=exception_cls, ) # Forcefully merge this local scope into the current # local scope, implicitly overwriting any locals of the # same name. Class locals necessarily assume lexical # precedence over: # * These classes themselves. # * Locals defined by higher-level parent classes. # * Locals defined by closures defining these classes. func_locals.update(cls_curr_locals) # Else, the decorated callable is *NOT* a method transitively # declared by a root decorated class. # Else, the decorated callable is global and thus guaranteed to have an # empty local scope. In this case, default to the empty dictionary. else: func_locals = DICT_EMPTY # Forward scope compositing this global and local scope of the decorated # callable as well as dynamically replacing each unresolved attribute of # this stringified type hint with a forward reference proxy resolving # this attribute on the first attempt to pass this attribute as the # second parameter to an isinstance()-based runtime type-check: e.g., # from beartype import beartype # from beartype.typing import Dict, Generic, TypeVar # # T = TypeVar('T') # # # @beartype resolves this stringified type hint as follows: # # * The "Dict", "str", and "int" attributes are globals and thus # # trivially resolved to those objects via the "func_globals" # # scope decided above. # # * The "MuhGeneric" attribute is neither a global nor local and # # thus remains unresolved. This forward scope replaces this # # unresolved attribute with a forward reference proxy. # @beartype # def muh_func(muh_arg: 'Dict[str, MuhGeneric[int]]') -> None: ... # # class MuhGeneric(Generic[T]): ... # # Initialize this forward scope to the set of all builtin attributes # (e.g., "str", "Exception"). Although the eval() builtin does, of # course, implicitly evaluate this stringified type hint against all # builtin attributes, it does so only *AFTER* invoking the # BeartypeForwardScope.__missing__() dunder method with each such # builtin attribute referenced in this hint. Since handling that # eccentricity would be less efficient and trivial than simply # initializing this forward scope with all builtin attributes, we prefer # the current (admittedly sus af) approach. Do not squint at this. bear_call.func_wrappee_scope_forward = BeartypeForwardScope( scope_dict=func_builtins, scope_name=func_module_name) # Composite this global and local scope into this forward scope (in that # order), implicitly overwriting first each builtin attribute and then # each global attribute previously copied into this forward scope with # each global and then local attribute of the same name. Since locals # *ALWAYS* assume precedence over globals *ALWAYS* assume precedence # over builtins, order of operations is *EXTREMELY* significant here. bear_call.func_wrappee_scope_forward.update(func_globals) bear_call.func_wrappee_scope_forward.update(func_locals) # print(f'Forward scope: {bear_call.func_wrappee_scope_forward}') # Else, this forward scope has already been decided. # # In either case, this forward scope should now all have been decided. # ..................{ RESOLVE }.................. # Attempt to resolve this stringified type hint into a non-string type hint # against both the global and local scopes of the decorated callable. try: hint_resolved = eval(hint, bear_call.func_wrappee_scope_forward) # print(f'Resolved stringified type hint {repr(hint)} to {repr(hint_resolved)}...') # If doing so failed for *ANY* reason whatsoever... except Exception as exception: assert isinstance(exception_cls, type), ( f'{repr(exception_cls)} not exception class.') assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') # Human-readable message to be raised if this message has been defined # *OR* "None" otherwise (i.e., if this message has yet to be defined). exception_message: Optional[str] = None # If the following conditions all hold: # * The active Python interpreter targets Python < 3.10 *AND*... # * The external module defining this stringified type hint was prefixed # by the "from __future__ import annotations" pragma enabling PEP 563 # *AND*... # * This hint contains one or more PEP 604-compliant new unions (e.g., # "int | str")... # # ...then this interpreter fails to syntactically support this hint at # runtime (because only Python >= 3.10 supports PEP 604) but nonetheless # superficially appears to do so under PEP 563 by simply stringifying # this otherwise unsupported hint into a string. Indeed, PEP 563 # superficially appears to support a countably infinite set of # syntactically and semantically invalid type hints -- including but # certainly not limited to PEP 604 under Python < 3.10: e.g., # from __future__ import annotations # <-- enable PEP 563 # def bad() -> int | str: # <-- invalid under Python < 3.10, but # pass # silently ignored by PEP 563 # def BAD() -> int ** str: # <-- invalid under all Python versions, # pass # but silently ignored by PEP 563 # # Clearly, exponentiating one type by another is both syntactically and # semantically invalid -- but PEP 563 blindly accepts and stringifies # that invalid type hint into the string "int ** str". This is nonsense. # # This branch detects this discrepancy between PEP 563 and 604 and, when # detected, raises a human-readable exception advising the caller with # recommendations of how to resolve this. Although we could also simply # do nothing, doing nothing results in non-human-readable exceptions # resembling the following, which only generates confusion: e.g., # $ python3.9 # >>> int | str # Traceback (most recent call last): # File "", line 1, in # TypeError: unsupported operand type(s) for |: 'type' and 'type' # # Specifically, if... if ( # The active Python interpreter targets Python <= 3.9 *AND*... IS_PYTHON_AT_MOST_3_9 and # Evaluating this stringified type hint raised a "TypeError"... isinstance(exception, TypeError) ): # If the exception message raised by this "TypeError" is prefixed by # a well-known substring implying this exception to have been # produced by a discrepancy between PEP 563 and 604... if str(exception).startswith( 'unsupported operand type(s) for |: '): # PEP 604-specific exception type, forcefully overriding the # passed exception type (for disambiguity). exception_cls = BeartypeDecorHintPep604Exception # Human-readable message providing various recommendations. exception_message = ( f'{exception_prefix}stringified PEP 604 type hint ' f'{repr(hint)} syntactically invalid under Python < 3.10 ' f'(i.e., {repr(exception)}). Consider either:\n' f'* Requiring Python >= 3.10. Abandon Python < 3.10 all ' f'ye who code here.\n' f'* Refactoring PEP 604 type hints into ' f'equivalent PEP 484 type hints: e.g.,\n' f' # Instead of this...\n' f' from __future__ import annotations\n' f' def bad_func() -> int | str: ...\n' f'\n' f' # Do this. Ugly, yet it works. Worky >>>> pretty.\n' f' from typing import Union\n' f' def bad_func() -> Union[int, str]: ...' ) # Else, this another kind of "TypeError" entirely. In this case, # defer to the default message defined below. # Else, either the active Python interpreter targets Python >= 3.10 *OR* # another type of exception was raised. In either case, defer to the # default message defined below. # If a human-readable message has yet to be defined, fallback to a # default message generically applicable to *ALL* stringified hints. if exception_message is None: # Human-readable message to be raised. exception_message = ( f'{exception_prefix}stringified type hint ' f'{repr(hint)} syntactically invalid ' f'(i.e., {repr(exception)}).' ) # If the beartype configuration associated with the decorated # callable enabled debugging, append debug-specific metadata to this # message. if bear_call.conf.is_debug: exception_message += ( f' Composite global and local scope enclosing this hint:\n\n' f'{repr(bear_call.func_wrappee_scope_forward)}' ) # Else, the beartype configuration associated with the decorated # callable disabled debugging. In this case, avoid appending # debug-specific metadata to this message. # Else, a human-readable message has already been defined. # Raise a human-readable exception wrapping the typically # non-human-readable exception raised above. raise exception_cls(exception_message) from exception # ..................{ RETURN }.................. # Return this resolved hint. return hint_resolved beartype-0.18.5/beartype/_check/forward/fwdscope.py000066400000000000000000000207311461113517100223020ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **forward scope classes** (i.e., dictionary subclasses deferring the resolutions of local and global scopes of classes and callables decorated by the :func:`beartype.beartype` decorator when dynamically evaluating stringified type hints for those classes and callables). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeDecorHintForwardRefException from beartype.typing import Type from beartype._data.hint.datahinttyping import LexicalScope from beartype._check.forward.reference.fwdrefabc import ( _BeartypeForwardRefIndexableABC) from beartype._check.forward.reference.fwdrefmake import ( make_forwardref_indexable_subtype) from beartype._util.text.utiltextidentifier import die_unless_identifier # ....................{ SUBCLASSES }.................... #FIXME: Unit test us up, please. class BeartypeForwardScope(LexicalScope): ''' **Forward scope** (i.e., dictionary mapping from the name to value of each locally and globally accessible attribute in the local and global scope of a class or callable as well as deferring the resolution of each currently undeclared attribute in that scope by replacing that attribute with a forward reference proxy resolved only when that attribute is passed as the second parameter to an :func:`isinstance`-based runtime type-check). This dictionary is principally employed to dynamically evaluate stringified type hints, including: * :pep:`484`-compliant forward references. * :pep:`563`-postponed type hints. Attributes ---------- _scope_dict : LexicalScope **Composite local and global scope** (i.e., dictionary mapping from the name to value of each locally and globally accessible attribute in the local and global scope of some class or callable) underlying this forward scope. See the :meth:`__init__` method for details. _scope_name : str Fully-qualified name of this forward scope. See the :meth:`__init__` method for details. ''' # ..................{ CLASS VARIABLES }.................. # Slot all instance variables defined on this object to minimize the time # complexity of both reading and writing variables across frequently # called @beartype decorations. Slotting has been shown to reduce read and # write costs by approximately ~10%, which is non-trivial. __slots__ = ( '_scope_dict', '_scope_name', ) # ..................{ INITIALIZERS }.................. def __init__(self, scope_dict: LexicalScope, scope_name: str) -> None: ''' Initialize this forward scope. Attributes ---------- scope_dict : LexicalScope **Composite local and global scope** (i.e., dictionary mapping from the name to value of each locally and globally accessible attribute in the local and global scope of some class or callable) underlying this forward scope. Crucially, **this dictionary must composite both the local and global scopes for that class or callable.** This dictionary must *not* provide only the local or global scope; this dictionary must provide both. Why? Because this forward scope is principally intended to be passed as the second and last parameter to the :func:`eval` builtin, called by the :func:`beartype._check.forward.fwdmain.resolve_hint` function. For unknown reasons, :func:`eval` only calls the :meth:`__missing__` dunder method of this forward scope when passed only two parameters (i.e., when passed only a global scope); :func:`eval` does *not* call the :meth:`__missing__` dunder method of this forward scope when passed three parameters (i.e., when passed both a global and local scope). Presumably, this edge case pertains to the official :func:`eval` docstring -- which reads: The globals must be a dictionary and locals can be any mapping, defaulting to the current globals and locals. If only globals is given, locals defaults to it. Clearly, :func:`eval` treats globals and locals fundamentally differently (probably for efficiency or obscure C implementation details). Since :func:`eval` only supports a single unified globals dictionary for our use case, the caller *must* composite together the global and local scopes into this dictionary. Praise to Guido. scope_name : str Fully-qualified name of this forward scope. For example: * ``"some_package.some_module"`` for a module scope (e.g., to resolve a global class or callable against this scope). * ``"some_package.some_module.SomeClass"`` for a class scope (e.g., to resolve a nested class or callable against this scope). Raises ------ BeartypeDecorHintForwardRefException If this scope name is *not* a valid Python attribute name. ''' assert isinstance(scope_dict, dict), ( f'{repr(scope_dict)} not dictionary.') # Initialize our superclass with this lexical scope, efficiently # pre-populating this dictionary with all previously declared attributes # underlying this forward scope. super().__init__(scope_dict) # If this scope name is syntactically invalid, raise an exception. die_unless_identifier( text=scope_name, exception_cls=BeartypeDecorHintForwardRefException, exception_prefix='Forward scope name ', ) # Else, this scope name is syntactically valid. # Classify all passed parameters. self._scope_dict = scope_dict self._scope_name = scope_name # ..................{ DUNDERS }.................. def __missing__(self, hint_name: str) -> Type[ _BeartypeForwardRefIndexableABC]: ''' Dunder method explicitly called by the superclass :meth:`dict.__getitem__` method implicitly called on each ``[``- and ``]``-delimited attempt to access an **unresolved type hint** (i.e., *not* currently defined in this scope) with the passed name. This method transparently replaces this unresolved type hint with a **forward reference proxy** (i.e., concrete subclass of the private :class:`beartype._check.forward.reference.fwdrefabc.BeartypeForwardRefABC` abstract base class (ABC), which resolves this type hint on the first call to the :func:`isinstance` builtin whose second argument is that subclass). This method assumes that: * This scope is only partially initialized. * This type hint has yet to be declared in this scope. * This type hint will be declared in this scope by the later time that this method is called. Parameters ---------- hint_name : str Relative (i.e., unqualified) or absolute (i.e., fully-qualified) name of this unresolved type hint. Returns ------- Type[_BeartypeForwardRefIndexableABC] Forward reference proxy deferring the resolution of this unresolved type hint. Raises ------ BeartypeDecorHintForwardRefException If this type hint name is *not* a valid Python attribute name. ''' # print(f'Missing type hint: {repr(hint_name)}') # If this type hint name is syntactically invalid, raise an exception. die_unless_identifier( text=hint_name, exception_cls=BeartypeDecorHintForwardRefException, exception_prefix='Forward reference ', ) # Else, this type hint name is syntactically valid. # Forward reference proxy to be returned. forwardref_subtype = make_forwardref_indexable_subtype( self._scope_name, hint_name) # Cache this proxy. self[hint_name] = forwardref_subtype # Return this proxy. return forwardref_subtype beartype-0.18.5/beartype/_check/forward/reference/000077500000000000000000000000001461113517100220515ustar00rootroot00000000000000beartype-0.18.5/beartype/_check/forward/reference/__init__.py000066400000000000000000000000001461113517100241500ustar00rootroot00000000000000beartype-0.18.5/beartype/_check/forward/reference/fwdrefabc.py000066400000000000000000000242231461113517100243510ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **forward reference abstract base classes (ABCs)** (i.e., low-level class hierarchy deferring the resolution of a stringified type hint referencing an attribute that has yet to be defined and annotating a class or callable decorated by the :func:`beartype.beartype` decorator). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeDecorHintForwardRefException from beartype.typing import ( NoReturn, Optional, Type, ) from beartype._data.hint.datahinttyping import ( LexicalScope, ) from beartype._check.forward.reference.fwdrefmeta import BeartypeForwardRefMeta # ....................{ SUPERCLASSES }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: The names of *ALL* class variables declared below *MUST* be both: # * Prefixed by "__beartype_". # * Suffixed by "__". # If this is *NOT* done, these variables could induce a namespace conflict with # user-defined subpackages, submodules, and classes of the same names # concatenated via the BeartypeForwardRefMeta.__getattr__() dunder method. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! #FIXME: Unit test us up, please. class BeartypeForwardRefABC(object, metaclass=BeartypeForwardRefMeta): ''' Abstract base class (ABC) of all **forward reference subclasses** (i.e., classes whose :class:`.BeartypeForwardRefMeta` metaclass defers the resolution of stringified type hints referencing actual type hints that have yet to be defined). Caveats ------- **This ABC prohibits instantiation.** This ABC *only* exists to sanitize, simplify, and streamline the definition of subclasses passed as the second parameter to the :func:`isinstance` builtin, whose :class:`.BeartypeForwardRefMeta.__instancecheck__` dunder method then implicitly resolves the forward references encapsulated by those subclasses. The :func:`.make_forwardref_subtype` function dynamically creates and returns one concrete subclass of this ABC for each unique forward reference required by the :func:`beartype.beartype` decorator, whose :attr:`hint_name` class variable is the name of the attribute referenced by that reference. ''' # ....................{ PRIVATE ~ class vars }.................... __name_beartype__: str = None # type: ignore[assignment] ''' Absolute (i.e., fully-qualified) or relative (i.e., unqualified) name of the type hint referenced by this forward reference subclass. ''' __scope_name_beartype__: Optional[str] = None ''' Fully-qualified name of the lexical scope to which the type hint referenced by this forward reference subclass is relative if that type hint is relative (i.e., if :attr:`__name_beartype__` is relative) *or* ignored otherwise (i.e., if :attr:`__name_beartype__` is absolute). ''' # ....................{ INITIALIZERS }.................... def __new__(cls, *args, **kwargs) -> NoReturn: ''' Prohibit instantiation by unconditionally raising an exception. ''' # Instantiatable. It's a word or my username isn't @UncleBobOnAStick. raise BeartypeDecorHintForwardRefException( f'{repr(BeartypeForwardRefABC)} subclass ' f'{repr(cls)} not instantiatable.' ) # ....................{ PRIVATE ~ testers }.................... @classmethod def __is_instance_beartype__(cls, obj: object) -> bool: ''' :data:`True` only if the passed object is an instance of the external class referred to by this forward reference. Parameters ---------- obj : object Arbitrary object to be tested. Returns ------- bool :data:`True` only if this object is an instance of the external class referred to by this forward reference subclass. ''' # # Resolve the external class referred to by this forward reference and # # permanently store that class in the "__type_beartype__" variable. # cls.__beartype_resolve_type__() # Return true only if this object is an instance of the external class # referenced by this forward reference. return isinstance(obj, cls.__type_beartype__) # type: ignore[arg-type] @classmethod def __is_subclass_beartype__(cls, obj: object) -> bool: ''' :data:`True` only if the passed object is a subclass of the external class referred to by this forward reference. Parameters ---------- obj : object Arbitrary object to be tested. Returns ------- bool :data:`True` only if this object is a subclass of the external class referred to by this forward reference subclass. ''' # # Resolve the external class referred to by this forward reference and # # permanently store that class in the "__type_beartype__" variable. # cls.__beartype_resolve_type__() # Return true only if this object is a subclass of the external class # referenced by this forward reference. return issubclass(obj, cls.__type_beartype__) # type: ignore[arg-type] # ....................{ SUPERCLASSES ~ index }.................... #FIXME: Unit test us up, please. class _BeartypeForwardRefIndexedABC(BeartypeForwardRefABC): ''' Abstract base class (ABC) of all **subscripted forward reference subclasses** (i.e., classes whose :class:`.BeartypeForwardRefMeta` metaclass defers the resolution of stringified type hints referencing actual type hints that have yet to be defined, subscripted by any arbitrary positional and keyword parameters). Subclasses of this ABC typically encapsulate user-defined generics that have yet to be declared (e.g., ``"MuhGeneric[int]"``). Caveats ------- **This ABC currently ignores subscription.** Technically, this ABC *does* store all positional and keyword parameters subscripting this forward reference. Pragmatically, this ABC otherwise silently ignores these parameters by deferring to the superclass :meth:`.is_instance` method (which reduces to the trivial :func:`isinstance` call). Why? Because **generics** (i.e., :class:`typing.Generic` subclasses) themselves behave in the exact same way at runtime. ''' # ....................{ PRIVATE ~ class vars }.................... __args_beartype__: tuple = None # type: ignore[assignment] ''' Tuple of all positional arguments subscripting this forward reference. ''' __kwargs_beartype__: LexicalScope = None # type: ignore[assignment] ''' Dictionary of all keyword arguments subscripting this forward reference. ''' #FIXME: Unit test us up, please. class _BeartypeForwardRefIndexableABC(BeartypeForwardRefABC): ''' Abstract base class (ABC) of all **subscriptable forward reference subclasses** (i.e., classes whose :class:`.BeartypeForwardRefMeta` metaclass defers the resolution of stringified type hints referencing actual type hints that have yet to be defined, transparently permitting these type hints to be subscripted by any arbitrary positional and keyword parameters). ''' # ....................{ DUNDERS }.................... @classmethod def __class_getitem__(cls, *args, **kwargs) -> ( Type[_BeartypeForwardRefIndexedABC]): ''' Create and return a new **subscripted forward reference subclass** (i.e., concrete subclass of the :class:`._BeartypeForwardRefIndexedABC` abstract base class (ABC) deferring the resolution of the type hint with the passed name, subscripted by the passed positional and keyword arguments). This dunder method enables this forward reference subclass to transparently masquerade as any subscriptable type hint factory, including subscriptable user-defined generics that have yet to be declared (e.g., ``"MuhGeneric[int]"``). This dunder method is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator). Ideally, this dunder method *would* be memoized. Sadly, there exists no means of efficiently caching either non-variadic or variadic keyword arguments. Although technically feasible, doing so imposes practical costs defeating the entire point of memoization. ''' # Avoid circular import dependencies. from beartype._check.forward.reference.fwdrefmake import ( _make_forwardref_subtype) # Subscripted forward reference to be returned. forwardref_indexed_subtype: Type[_BeartypeForwardRefIndexedABC] = ( _make_forwardref_subtype( # type: ignore[assignment] hint_name=cls.__name_beartype__, scope_name=cls.__scope_name_beartype__, type_bases=_BeartypeForwardRefIndexedABC_BASES, )) # Classify the arguments subscripting this forward reference. forwardref_indexed_subtype.__args_beartype__ = args # pyright: ignore[reportGeneralTypeIssues] forwardref_indexed_subtype.__kwargs_beartype__ = kwargs # pyright: ignore[reportGeneralTypeIssues] # Return this subscripted forward reference. return forwardref_indexed_subtype # ....................{ PRIVATE ~ tuples }.................... _BeartypeForwardRefIndexableABC_BASES = (_BeartypeForwardRefIndexableABC,) ''' 1-tuple containing *only* the :class:`._BeartypeForwardRefIndexableABC` superclass to reduce space and time consumption. ''' _BeartypeForwardRefIndexedABC_BASES = (_BeartypeForwardRefIndexedABC,) ''' 1-tuple containing *only* the :class:`._BeartypeForwardRefIndexedABC` superclass to reduce space and time consumption. ''' beartype-0.18.5/beartype/_check/forward/reference/fwdrefmake.py000066400000000000000000000216161461113517100245440ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **forward reference factories** (i.e., low-level callables creating and returning forward reference proxy subclasses deferring the resolution of a stringified type hint referencing an attribute that has yet to be defined and annotating a class or callable decorated by the :func:`beartype.beartype` decorator). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeDecorHintForwardRefException from beartype.typing import ( Dict, Optional, Type, ) from beartype._cave._cavemap import NoneTypeOr from beartype._data.hint.datahinttyping import ( BeartypeForwardRef, BeartypeForwardRefArgs, TupleTypes, ) from beartype._check.forward.reference.fwdrefabc import ( _BeartypeForwardRefIndexableABC, _BeartypeForwardRefIndexableABC_BASES, ) from beartype._util.cls.utilclsmake import make_type from beartype._util.text.utiltextidentifier import die_unless_identifier # ....................{ FACTORIES }.................... def make_forwardref_indexable_subtype( scope_name: Optional[str], hint_name: str, ) -> Type[_BeartypeForwardRefIndexableABC]: ''' Create and return a new **subscriptable forward reference subclass** (i.e., concrete subclass of the :class:`._BeartypeForwardRefIndexableABC` abstract base class (ABC) deferring the resolution of the unresolved type hint with the passed name, transparently permitting this type hint to be subscripted by any arbitrary positional and keyword parameters). This factory is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the lower-level private :func:`._make_forwardref_subtype` factory called by this higher-level public factory is itself memoized. Parameters ---------- scope_name : Optional[str] Possibly ignored lexical scope name. Specifically: * If ``hint_name`` is absolute (i.e., contains one or more ``.`` delimiters), this parameter is silently ignored in favour of the fully-qualified name of the module prefixing ``hint_name``. * If ``hint_name`` is relative (i.e., contains *no* ``.`` delimiters), this parameter declares the absolute (i.e., fully-qualified) name of the lexical scope to which this unresolved type hint is relative. The fully-qualified name of the module prefixing ``hint_name`` (if any) thus *always* takes precedence over this lexical scope name, which only provides a fallback to resolve relative forward references. While unintuitive, this is needed to resolve absolute forward references. hint_name : str Relative (i.e., unqualified) or absolute (i.e., fully-qualified) name of this unresolved type hint to be referenced. Returns ------- Type[_BeartypeForwardRefIndexableABC] Subscriptable forward reference subclass referencing this type hint. Raises ------ BeartypeDecorHintForwardRefException If either: * ``hint_name`` is *not* a syntactically valid Python identifier. * ``scope_name`` is neither: * A syntactically valid Python identifier. * :data:`None`. ''' # Subscriptable forward reference to be returned. return _make_forwardref_subtype( # type: ignore[return-value] scope_name=scope_name, hint_name=hint_name, type_bases=_BeartypeForwardRefIndexableABC_BASES, ) # ....................{ PRIVATE ~ factories }.................... def _make_forwardref_subtype( scope_name: Optional[str], hint_name: str, type_bases: TupleTypes, ) -> BeartypeForwardRef: ''' Create and return a new **forward reference subclass** (i.e., concrete subclass of the passed abstract base class (ABC) deferring the resolution of the type hint with the passed name transparently). This factory is internally memoized for efficiency. Parameters ---------- scope_name : Optional[str] Possibly ignored lexical scope name. See :func:`.make_forwardref_indexable_subtype` for further details. hint_name : str Absolute (i.e., fully-qualified) or relative (i.e., unqualified) name of the type hint referenced by this forward reference subclass. type_bases : Tuple[type, ...] Tuple of all base classes to be inherited by this forward reference subclass. For simplicity, this *must* be a 1-tuple ``(type_base,)`` where ``type_base`` is a :class:`._BeartypeForwardRefIndexableABC` subclass. Returns ------- BeartypeForwardRef Forward reference subclass referencing this type hint. Raises ------ BeartypeDecorHintForwardRefException If either: * ``hint_name`` is *not* a syntactically valid Python identifier. * ``scope_name`` is neither: * A syntactically valid Python identifier. * :data:`None`. ''' # Tuple of all passed parameters (in arbitrary order). args: BeartypeForwardRefArgs = (scope_name, hint_name, type_bases) # Forward reference proxy previously created and returned by a prior call to # this function passed these parameters if any *OR* "None" otherwise (i.e., # if this is the first call to this function passed these parameters). # forwardref_subtype: Optional[BeartypeForwardRef] = ( forwardref_subtype = _forwardref_args_to_forwardref.get(args, None) # If this proxy has already been created, reuse and return this proxy as is. if forwardref_subtype is not None: return forwardref_subtype # Else, this proxy has yet to be created. assert isinstance(scope_name, NoneTypeOr[str]), ( f'{repr(scope_name)} neither string nor "None".') assert isinstance(hint_name, str), f'{repr(hint_name)} not string.' assert len(type_bases) == 1, ( f'{repr(type_bases)} not 1-tuple of a single superclass.') # If this attribute name is *NOT* a syntactically valid Python identifier, # raise an exception. die_unless_identifier( text=hint_name, exception_cls=BeartypeDecorHintForwardRefException, exception_prefix='Forward reference ', ) # Else, this attribute name is a syntactically valid Python identifier. # Possibly empty fully-qualified module name and unqualified basename of the # type referred to by this forward reference. type_module_name, _, type_name = hint_name.rpartition('.') # If this module name is empty, fallback to the passed module name if any. # # Note that we intentionally perform *NO* additional validation. Why? # Builtin types. Notably, it is valid to pass an unqualified "hint_name" # and a "scope_name" that is "None" only if "hint_name" is the name of a # builtin type (e.g., "int", "str"). Since validating this edge case is # non-trivial, we defer this validation to subsequent importation logic. if not type_module_name: type_module_name = scope_name # Else, this module name is non-empty. # Forward reference proxy to be returned. forwardref_subtype = make_type( type_name=type_name, type_module_name=type_module_name, type_bases=type_bases, exception_cls=BeartypeDecorHintForwardRefException, exception_prefix='Forward reference ', ) # Classify passed parameters with this proxy. forwardref_subtype.__name_beartype__ = hint_name # pyright: ignore forwardref_subtype.__scope_name_beartype__ = scope_name # pyright: ignore # Cache this proxy for reuse by subsequent calls to this factory function # passed the same parameters. _forwardref_args_to_forwardref[args] = forwardref_subtype # Return this proxy. return forwardref_subtype # ....................{ PRIVATE ~ globals }.................... _forwardref_args_to_forwardref: Dict[ BeartypeForwardRefArgs, BeartypeForwardRef] = {} ''' **Forward reference proxy cache** (i.e., dictionary mapping from the tuple of all parameters passed to each prior call of the :func:`._make_forwardref_subtype` factory function to the forward reference proxy dynamically created and returned by that call). This cache serves a dual purpose. Notably, this cache both enables: * External callers to iterate over all previously instantiated forward reference proxies. This is particularly useful when responding to module reloading, which requires that *all* previously cached types be uncached. * :func:`._make_forwardref_subtype` to internally memoize itself over its passed parameters. Since the existing ``callable_cached`` decorator could trivially do so as well, however, this is only a negligible side effect. ''' beartype-0.18.5/beartype/_check/forward/reference/fwdrefmeta.py000066400000000000000000000375651461113517100245670ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **forward reference metaclasses** (i.e., low-level metaclasses of classes deferring the resolution of a stringified type hint referencing an attribute that has yet to be defined and annotating a class or callable decorated by the :func:`beartype.beartype` decorator). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeCallHintForwardRefException from beartype.typing import Dict from beartype._data.hint.datahinttyping import BeartypeForwardRef from beartype._util.cls.pep.utilpep3119 import ( die_unless_object_isinstanceable) from beartype._util.hint.pep.proposal.pep484585.utilpep484585generic import ( is_hint_pep484585_generic, get_hint_pep484585_generic_type, ) from beartype._util.module.utilmodimport import import_module_attr from beartype._util.text.utiltextidentifier import is_dunder # ....................{ METACLASSES }.................... class BeartypeForwardRefMeta(type): ''' **Forward reference metaclass** (i.e., metaclass of the :class:`.BeartypeForwardRefABC` superclass deferring the resolution of a stringified type hint referencing an attribute that has yet to be defined and annotating a class or callable decorated by the :func:`beartype.beartype` decorator). This metaclass memoizes each **forward reference** (i.e., :class:`.BeartypeForwardRefABC` instance) according to the fully-qualified name of the attribute referenced by that forward reference. Doing so ensures that only the first :class:`.BeartypeForwardRefABC` instance referring to a unique attribute is required to dynamically resolve that attribute at runtime; all subsequent :class:`.BeartypeForwardRefABC` instances referring to the same attribute transparently reuse the attribute previously resolved by the first such instance, effectively reducing the time cost of resolving forward references to a constant-time operation with negligible constants. This metaclass dynamically and efficiently resolves each forward reference in a just-in-time (JIT) manner on the first :func:`isinstance` call whose second argument is that forward reference. Forward references *never* passed to the :func:`isinstance` builtin are *never* resolved, which is good. ''' # ....................{ DUNDERS }.................... def __getattr__(cls: BeartypeForwardRef, hint_name: str) -> ( # type: ignore[misc] BeartypeForwardRef): ''' **Fully-qualified forward reference subclass** (i.e., :class:`.BeartypeForwardRefABC` subclass whose metaclass is this metaclass and whose :attr:`.BeartypeForwardRefABC.__name_beartype__` class variable is the fully-qualified name of an external class). This dunder method creates and returns a new forward reference subclass referring to an external class whose name is concatenated from (in order): #. The fully-qualified name of the external package or module referred to by the passed forward reference subclass. #. The passed unqualified basename, presumably referring to a subpackage, submodule, or class of that external package or module. Parameters ---------- cls : Type[BeartypeForwardRefABC] Forward reference subclass to concatenate this basename against. hint_name : str Unqualified basename to be concatenated against this forward reference subclass. Returns ------- Type['_BeartypeForwardRefIndexableABC'] Fully-qualified forward reference subclass concatenated as described above. ''' # Avoid circular import dependencies. from beartype._check.forward.reference.fwdrefmake import ( make_forwardref_indexable_subtype) # If this unqualified basename is that of a non-existent dunder # attribute, raise the standard "AttributeError" exception. # # Note that we intentionally avoid suffixing the exception message by a # "." character here. Why? Because Python treats "AttributeError" # exceptions as special. Notably, Python appears to actually: # 1. Parse apart the messages of these exceptions for the double-quoted # attribute name embedded in these messages. # 2. Suffix these messages by a "." character followed by a sentence # suggesting an existing attribute with a similar name to that of the # attribute name previously parsed from these messages. # # For example, given an erroneous lookup of a non-existent dunder # attribute "__nomnom_beartype__", Python expands the exception message # raised below into: # AttributeError: Forward reference proxy "MuhRef" dunder attribute # "__nomnom_beartype__" not found. Did you mean: # '__name_beartype__'? if is_dunder(hint_name): raise AttributeError( f'Forward reference proxy "{cls.__name__}" dunder attribute ' f'"{hint_name}" not found' ) # Else, this unqualified basename is *NOT* that of a non-existent dunder # attribute. # Return a new fully-qualified forward reference subclass concatenated # as described above. return make_forwardref_indexable_subtype( cls.__scope_name_beartype__, # type: ignore[arg-type] f'{cls.__name_beartype__}.{hint_name}', ) def __instancecheck__(cls: BeartypeForwardRef, obj: object) -> bool: # type: ignore[misc] ''' :data:`True` only if the passed object is an instance of the external class referenced by the passed **forward reference subclass** (i.e., :class:`.BeartypeForwardRefABC` subclass whose metaclass is this metaclass and whose :attr:`.BeartypeForwardRefABC.__name_beartype__` class variable is the fully-qualified name of that external class). Parameters ---------- cls : Type[BeartypeForwardRefABC] Forward reference subclass to test this object against. obj : object Arbitrary object to be tested as an instance of the external class referenced by this forward reference subclass. Returns ------- bool :data:`True` only if this object is an instance of the external class referenced by this forward reference subclass. ''' # Return true only if this forward reference subclass insists that this # object satisfies the external class referenced by this subclass. return cls.__is_instance_beartype__(obj) def __subclasscheck__(cls: BeartypeForwardRef, obj: object) -> bool: # type: ignore[misc] ''' :data:`True` only if the passed object is a subclass of the external class referenced by the passed **forward reference subclass** (i.e., :class:`.BeartypeForwardRefABC` subclass whose metaclass is this metaclass and whose :attr:`.BeartypeForwardRefABC.__name_beartype__` class variable is the fully-qualified name of that external class). Parameters ---------- cls : Type[BeartypeForwardRefABC] Forward reference subclass to test this object against. obj : object Arbitrary object to be tested as a subclass of the external class referenced by this forward reference subclass. Returns ------- bool :data:`True` only if this object is a subclass of the external class referenced by this forward reference subclass. ''' # Return true only if this forward reference subclass insists that this # object is an instance of the external class referenced by this # subclass. return cls.__is_subclass_beartype__(obj) def __repr__(cls: BeartypeForwardRef) -> str: # type: ignore[misc] ''' Machine-readable string representing this forward reference subclass. ''' # Machine-readable representation to be returned. # # Note that this representation is intentionally prefixed by the # @beartype-specific substring ""). Why? Because # various other @beartype submodules ignore objects whose # representations are prefixed by the "<" character, which are usefully # treated as having a standard representation that is ignorable for most # intents and purposes. This includes: # * The die_if_hint_pep604_inconsistent() raiser. cls_repr = ( f' type: # type: ignore[misc] ''' **Forward referee** (i.e., type hint referenced by this forward reference subclass, which is usually but *not* necessarily a class). This class property is manually memoized for efficiency. However, note this class property is *not* automatically memoized (e.g., by the ``property_cached`` decorator). Why? Because manual memoization enables other functionality in the beartype codebase to explicitly unmemoize all previously memoized forward referees across all forward reference proxies, effectively forcing all subsequent calls of this property across all forward reference proxies to reimport their forward referees. Why is that desirable? Because other functionality in the beartype codebase detects when the user has manually reloaded user-defined modules defining user-defined types annotating user-defined callables previously decorated by the :mod:`beartype.beartype` decorator. Since reloading those modules redefines those types, all previously cached types (including those memoized by this property) *must* then be assumed to be invalid and thus uncached. In short, manual memoization allows beartype to avoid desynchronization between memoized and actual types. Raises ------ BeartypeCallHintForwardRefException If either: * This forward referee is unimportable. * This forward referee is importable but either: * Not a type. * A type that is this forward reference proxy, implying this proxy circularly proxies itself. ''' # Forward referee referred to by this forward reference proxy if a prior # access of this property has already resolved this referee *OR* "None" # otherwise (i.e., if this is the first access of this property). referee = _forwardref_to_referee.get(cls) # If this forward referee has yet to be resolved, this is the first call # to this property. In this case... if referee is None: # type: ignore[has-type] # print(f'Importing forward ref "{cls.__name_beartype__}" from module "{cls.__scope_name_beartype__}"...') # Forward referee dynamically imported from this module. referee = import_module_attr( attr_name=cls.__name_beartype__, module_name=cls.__scope_name_beartype__, exception_cls=BeartypeCallHintForwardRefException, exception_prefix='Forward reference ', ) # If this referee is this forward reference subclass, then this # subclass circularly proxies itself. Since allowing this edge case # would openly invite infinite recursion, we detect this edge case # and instead raise a human-readable exception. if referee is cls: raise BeartypeCallHintForwardRefException( f'Forward reference proxy {repr(cls)} circularly ' f'(i.e., infinitely recursively) references itself.' ) # Else, this referee is *NOT* this forward reference subclass. # # If this referee is a subscripted generic (e.g., # "MuhGeneric[int]"), reduce this referee to the class subscripting # this generic (e.g., "int"). elif is_hint_pep484585_generic(referee): referee = get_hint_pep484585_generic_type( hint=referee, exception_cls=BeartypeCallHintForwardRefException, exception_prefix='Forward reference ', ) # Else, this referee is *NOT* a subscripted generic. # If this referee is *NOT* an isinstanceable class, raise an # exception. die_unless_object_isinstanceable( obj=referee, exception_cls=BeartypeCallHintForwardRefException, exception_prefix='Forward reference ', ) # Else, this referee is an isinstanceable class. # Cache this referee for subsequent lookup by this property. _forwardref_to_referee[cls] = referee # Else, this referee has already been resolved. # # In either case, this referee is now resolved. # Return this previously resolved referee. return referee # type: ignore[return-value] # ....................{ PRIVATE ~ globals }.................... _forwardref_to_referee: Dict[BeartypeForwardRef, type] = {} ''' **Forward reference referee cache** (i.e., dictionary mapping from each forward reference proxy to the arbitrary class referred to by that proxy). This cache serves a dual purpose. Notably, this cache both enables: * External callers to iterate over all previously instantiated forward reference proxies. This is particularly useful when responding to module reloading, which requires that *all* previously cached types be uncached. * The :attr:`.BeartypeForwardRefMeta.__type_beartype__` property to internally memoize the arbitrary class referred to by this referee. Since the existing ``property_cached`` decorator could trivially do so as well, however, this is only a negligible side effect. ''' beartype-0.18.5/beartype/_check/forward/reference/fwdreftest.py000066400000000000000000000027031461113517100246020ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **forward reference testers** (i.e., low-level callables testing various properties of forward reference proxy subclasses deferring the resolution of a stringified type hint referencing an attribute that has yet to be defined and annotating a class or callable decorated by the :func:`beartype.beartype` decorator). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._check.forward.reference.fwdrefmeta import BeartypeForwardRefMeta # ....................{ TESTERS }.................... #FIXME: Unit test us up, please. def is_forwardref(obj: object) -> bool: ''' :data:`True` only if the passed object is a **forward reference subclass** (i.e., class whose metaclass is class:`.BeartypeForwardRefMeta`). Parameters ---------- obj : object Object to be tested. Returns ------- bool :data:`True` only if this object is a forward reference subclass. ''' # Return true only if the class of this object is the metaclass of all # forward reference subclasses, implying this object to be such a subclass. return obj.__class__ is BeartypeForwardRefMeta beartype-0.18.5/beartype/_check/util/000077500000000000000000000000001461113517100174245ustar00rootroot00000000000000beartype-0.18.5/beartype/_check/util/__init__.py000066400000000000000000000000001461113517100215230ustar00rootroot00000000000000beartype-0.18.5/beartype/_check/util/_checkutilsnip.py000066400000000000000000000156571461113517100230200ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **type-checking function utility code snippets** (i.e., triple-quoted pure-Python string constants formatted and concatenated together to dynamically generate the implementations of functions type-checking arbitrary objects against arbitrary PEP-compliant type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._check.checkmagic import ( ARG_NAME_GETRANDBITS, VAR_NAME_RANDOM_INT, ) from beartype._data.code.datacodeindent import CODE_INDENT_1 # ....................{ CODE }.................... CODE_SIGNATURE_ARG = ( # Indentation prefixing all wrapper parameters. f'{CODE_INDENT_1}' # Default this parameter to the current value of the module-scoped attribute # of the same name, passed to the make_func() function by the parent # @beartype decorator. While awkward, this is the optimally efficient means # of exposing arbitrary attributes to the body of this wrapper function. f'{{arg_name}}={{arg_name}},{{arg_comment}}' # Newline for readability. f'\n' ) ''' Code snippet declaring a **hidden parameter** (i.e., parameter whose name is prefixed by ``"__beartype_"`` and whose value is that of an external attribute internally referenced in the body of a type-checking callable) in the signature of that callable. ''' # ....................{ CODE ~ init }.................... #FIXME: Note that NumPy provides an efficient means of generating a large #number of pseudo-random integers all-at-once. The core issue there, of #course, is that we then need to optionally depend upon and detect NumPy, #which then requires us to split our random integer generation logic into two #parallel code paths that we'll then have to maintain -- and the two will be #rather different. In any case, here's how one generates a NumPy array #containing 100 pseudo-random integers in the range [0, 127]: # random_ints = numpy.random.randint(128, size=100) # #To leverage that sanely, we'd need to: #* Globally cache that array somewhere. #* Globally cache the current index into that array. #* When NumPy is unimportable, fallback to generating a Python list containing # the same number of pseudo-random integers in the same range. #* In either case, we'd probably want to wrap that logic in a globally # accessible infinite generator singleton that returns another pseudo-random # integer every time you iterate it. This assumes, of course, that iterating # generators is reasonably fast in Python. (If not, just make that a getter # method of a standard singleton object.) #* Replace the code snippet below with something resembling: # ''' # __beartype_random_int = next(__beartype_random_int_generator) # ''' #Note that thread concurrency issues are probable ignorable here, but that #there's still a great deal of maintenance and refactoring that would need to #happen to sanely support this. In other words, ain't happenin' anytime soon. #FIXME: To support both NumPy and non-NumPy code paths transparently, design a #novel private data structure named "_BeartypeRNJesus" whose __next__() dunder #method transparently returns a new random integer. The implementation of that #method then handles all of the low-level minutiae like: #* Storing and iterating the 0-based index of the next index into an internally # cached NumPy array created by calling numpy.random.randint(). #* Creating a new cached NumPy array after exhausting the prior cached array. CODE_INIT_RANDOM_INT = f''' # Generate and localize a sufficiently large pseudo-random integer for # subsequent indexation in type-checking randomly selected container items. {VAR_NAME_RANDOM_INT} = {ARG_NAME_GETRANDBITS}(32)''' ''' PEP-specific code snippet generating and localizing a pseudo-random unsigned 32-bit integer for subsequent use in type-checking randomly indexed container items. This bit length was intentionally chosen to correspond to the number of bits generated by each call to Python's C-based Mersenne Twister underlying the :func:`random.getrandbits` function called here. Exceeding this number of bits would cause that function to inefficiently call the Twister multiple times. This bit length produces unsigned 32-bit integers efficiently representable as C-based atomic integers rather than **big numbers** (i.e., aggregations of C-based atomic integers) ranging 0–``2**32 - 1`` regardless of the word size of the active Python interpreter. Since the cost of generating integers to this maximum bit length is approximately the same as generating integers of much smaller bit lengths, this maximum is preferred. Although big numbers transparently support the same operations as non-big integers, the latter are dramatically more efficient with respect to both space and time consumption and thus preferred. Usage ----- Since *most* containers are likely to contain substantially fewer items than the maximum integer in this range, pseudo-random container indices are efficiently selectable by simply taking the modulo of this local variable with the lengths of those containers. Any container containing more than this maximum number of items is typically defined as a disk-backed data structure (e.g., Pandas dataframe) rather than an in-memory standard object (e.g., :class:`list`). Since :mod:`beartype` currently ignores the former with respect to deep type-checking, this local typically suffices for real-world in-memory containers. For edge-case containers containing more than this maximum number of items, :mod:`beartype` will only deeply type-check items with indices in this range; all trailing items will *not* be deeply type-checked, which we consider an acceptable tradeoff, given the infeasibility of even storing such objects in memory. Caveats ------- **The only safely callable function declared by the stdlib** :mod:`random` **module is** :func:`random.getrandbits`. While that function is efficiently implemented in C, all other functions declared by that module are inefficiently implemented in Python. In fact, their implementations are sufficiently inefficient that there exist numerous online articles lamenting the fact. See Also -------- https://stackoverflow.com/a/11704178/2809027 StackOverflow answer demonstrating Python's C-based Mersenne Twister underlying the :func:`random.getrandbits` function to generate 32 bits of pseudo-randomness at a time. https://gist.github.com/terrdavis/1b23b7ff8023f55f627199b09cfa6b24#gistcomment-3237209 Self GitHub comment introducing the core concepts embodied by this snippet. https://eli.thegreenplace.net/2018/slow-and-fast-methods-for-generating-random-integers-in-python Authoritative article profiling various :mod:`random` callables. ''' beartype-0.18.5/beartype/_check/util/checkutilmake.py000066400000000000000000000147001461113517100226110ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype type-checking function code utility factories** (i.e., low-level callables dynamically generating pure-Python code snippets type-checking arbitrary objects passed to arbitrary callables against PEP-compliant type hints passed to those same callables). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import Callable from beartype._check.checkmagic import ( ARG_NAME_GETRANDBITS, ) from beartype._check.util._checkutilsnip import ( CODE_SIGNATURE_ARG, CODE_INIT_RANDOM_INT, ) from beartype._conf.confcls import BeartypeConf from beartype._data.hint.datahinttyping import ( LexicalScope, ) from beartype._util.text.utiltextrepr import represent_object # ....................{ MAKERS ~ signature }.................... #FIXME: Unit test us up, please. def make_func_signature( # Mandatory parameters. func_name: str, func_scope: LexicalScope, code_signature_format: str, conf: BeartypeConf, # Optional parameters. code_signature_prefix: str = '', # String globals required only for their bound str.format() methods. CODE_SIGNATURE_ARG_format: Callable = ( CODE_SIGNATURE_ARG.format), ) -> str: ''' **Type-checking signature factory** (i.e., low-level function dynamically generating and returning the **signature** (i.e., callable declaration prefixing the body of that callable) of a callable type-checking arbitrary objects against arbitrary PEP-compliant type hints to be subsequently defined, described by the passed parameters. Parameters ---------- func_name : str Unqualified basename of the callable declared by this signature. func_scope : LexicalScope **Local scope** (i.e., dictionary mapping from the name to value of each hidden parameter declared in this signature) of that callable, where a "hidden parameter" is a parameter whose name is prefixed by ``"__beartype_"`` and whose value is that of an external attribute internally referenced in the body of that callable. code_signature_format : str Code snippet declaring the unformatted signature of that callable, which this factory then formats by replacing these format variables in this code snippet: * ``{func_name}``, replaced by the value of the ``func_name`` parameter. * ``{code_signature_prefix}``, replaced by the value of the ``code_signature_prefix`` parameter. * ``{code_signature_args}``, replaced by the declaration of all hidden parameters in the passed ``func_scope`` parameter. conf : BeartypeConf, optional **Beartype configuration** (i.e., self-caching dataclass encapsulating all settings configuring type-checking for the passed object). code_signature_prefix : str, optional Code snippet prefixing this signature, typically either: * For synchronous callables, the empty string. * For asynchronous callables (e.g., asynchronous generators, coroutines), the space-suffixed keyword ``"async "``. Defaults to the empty string and thus synchronous behaviour. Yields ------ str Signature of this callable. ''' assert isinstance(func_name, str), f'{repr(func_name)} not string.' assert isinstance(func_scope, dict), f'{repr(func_scope)} not dictionary.' assert isinstance(conf, BeartypeConf), f'{repr(conf)} not configuration.' assert isinstance(code_signature_format, str), ( f'{repr(code_signature_format)} not string.') assert isinstance(code_signature_prefix, str), ( f'{repr(code_signature_prefix)} not string.') # Python code snippet declaring all optional private beartype-specific # parameters directly derived from the local scope established by the above # calls to the _code_check_args() and _code_check_return() functions. code_signature_args = '' # For the name and value of each such parameter... for arg_name, arg_value in func_scope.items(): # Machine-readable representation of this parameter's initial value, # stripped of newline and truncated to a (hopefully) sensible length. # Since the represent_object() function called below to sanitize this # value is incredibly slow, this representation is conditionally # appended as a human-readable comment to the declaration of this # parameter below *ONLY* if the caller explicitly requested debugging. arg_comment = ( f' # is {represent_object(arg_value)}' if conf.is_debug else '' ) # Compose the declaration of this parameter in the signature of this # wrapper from... code_signature_args += CODE_SIGNATURE_ARG_format( arg_name=arg_name, arg_comment=arg_comment, ) #FIXME: *YIKES.* We need to pass a unique tester function signature here #resembling: # def {{func_name}}(obj: object) -> bool: #To do so sanely, let's generalize this factory to accept an additional #mandatory "func_signature" parameter, please. We'll need to note in the #docstring exactly what format variables that parameter is expected to #contain, of course. # Python code snippet declaring the signature of this wrapper. code_signature = code_signature_format.format( func_name=func_name, code_signature_prefix=code_signature_prefix, code_signature_args=code_signature_args, ) # Python code snippet of preliminary statements (e.g., local variable # assignments) if any *AFTER* generating snippets type-checking parameters # and returns (which modifies dataclass variables tested below). code_body_init = ( # If the body of this wrapper requires a pseudo-random integer, append # code generating and localizing such an integer to this signature. CODE_INIT_RANDOM_INT if ARG_NAME_GETRANDBITS in func_scope else # Else, this body requires *NO* such integer. In this case, preserve # this signature as is. '' ) # Return this signature suffixed by zero or more preliminary statements. return f'{code_signature}{code_body_init}' beartype-0.18.5/beartype/_conf/000077500000000000000000000000001461113517100163175ustar00rootroot00000000000000beartype-0.18.5/beartype/_conf/__init__.py000066400000000000000000000000001461113517100204160ustar00rootroot00000000000000beartype-0.18.5/beartype/_conf/_confget.py000066400000000000000000000145231461113517100204620ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **configuration class getters** (i.e., low-level callables inspecting and introspecting various metadata of interest to the high-level :class:`beartype.BeartypeConf` dataclass). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeConfShellVarException from beartype.roar._roarwarn import BeartypeConfShellVarWarning from beartype._data.func.datafuncarg import ARG_VALUE_UNPASSED from beartype._data.hint.datahinttyping import ( BoolTristateUnpassable, BoolTristate, ) from beartype._data.os.dataosshell import ( SHELL_VAR_CONF_IS_COLOR_NAME, SHELL_VAR_CONF_IS_COLOR_VALUE_TO_OBJ, ) from beartype._util.error.utilerrwarn import issue_warning from beartype._util.os.utilosshell import get_shell_var_value_or_none from beartype._util.text.utiltextjoin import join_delimited_disjunction # ....................{ GETTERS }.................... def get_is_color(is_color: BoolTristateUnpassable) -> BoolTristate: ''' Final value of the ``is_color`` tri-state boolean parameter accepted by the :meth:`beartype.BeartypeConf.__init__` constructor, derived from the passed parameter originally passed to that constructor as well as the external ``${BEARTYPE_IS_COLOR}`` shell environment variable. This getter derives the value of the ``is_color`` parameter as follows: * If the external ``${BEARTYPE_IS_COLOR}`` environment variable is set, this getter: * If the caller also explicitly passed the ``is_color`` parameter a different and thus conflicting value to that environment variable, emits a non-fatal warning informing the caller of this conflict. * Returns the value of that variable coerced from a useless string to the corresponding native Python object (e.g., from ``BEARTYPE_IS_COLOR="True"`` to :data:`True`). * Else, this getter returns the value of the ``is_color`` parameter as is. Parameters ---------- is_color : BoolTristateUnpassable Original ``is_color`` parameter passed to that constructor. Returns ---------- BoolTristate Final ``is_color`` parameter to be used inside that constructor. Raises ---------- BeartypeConfParamException If the original``is_color`` parameter is *not* a tri-state boolean. BeartypeConfShellVarException If the external ``${BEARTYPE_IS_COLOR}`` shell environment variable is set to an unrecognized string (i.e., neither ``"True"``, ``"False"``, nor ``"None"``). ''' # String value of the external shell environment variable # "${BEARTYPE_IS_COLOR}" globally overriding the passed "is_color" parameter # if the caller set this environment variable *OR* "None" otherwise. is_color_shell_var_value = get_shell_var_value_or_none( SHELL_VAR_CONF_IS_COLOR_NAME) # If the caller set this environment variable... if is_color_shell_var_value is not None: # If the string value of this environment variable is unrecognized... if (is_color_shell_var_value not in SHELL_VAR_CONF_IS_COLOR_VALUE_TO_OBJ): # Human-readable string listing the names of all valid string values # of this environment variable, double-quoting each such name for # additional readability. IS_COLOR_SHELL_VAR_VALUES = join_delimited_disjunction( strs=SHELL_VAR_CONF_IS_COLOR_VALUE_TO_OBJ.keys(), is_double_quoted=True, ) # Raise an exception embedding this string. raise BeartypeConfShellVarException( f'Beartype configuration environment variable ' f'"${{{SHELL_VAR_CONF_IS_COLOR_NAME}}}" ' f'value {repr(is_color_shell_var_value)} invalid ' f'(i.e., neither {IS_COLOR_SHELL_VAR_VALUES}).' ) # Else, the string value of this environment variable is recognized. # Value of the "is_color" parameter represented by this string value # (e.g., boolean True for the string "True"). By the above validation, # this value is now guaranteed to be valid. is_color_override = SHELL_VAR_CONF_IS_COLOR_VALUE_TO_OBJ.get( is_color_shell_var_value) # If... if ( # The value of the "is_color" parameter is *NOT* that of our # unpassed argument placeholder, then the caller explicitly passed # some value for this parameter. If this is the case *AND*... is_color != ARG_VALUE_UNPASSED and # The value of this parameter differs from (and thus conflicts with) # the value of this environment variable... is_color != is_color_override ): # Warn the caller that @beartype non-fatally resolved this conflict # by ignoring this parameter in favour of this environment variable. issue_warning( cls=BeartypeConfShellVarWarning, message=( f'Beartype configuration parameter "is_color" ' f'value {repr(is_color)} ignored in favour of ' f'environment variable ' f'"${{{SHELL_VAR_CONF_IS_COLOR_NAME}}}" ' f'value {repr(is_color_override)}.' ), ) # Override the value of the passed "is_color" parameter with # that of this environment variable. is_color = is_color_override # Else, the caller did *NOT* set this environment variable. # # If the value of the "is_color" parameter is that of our unpassed argument # placeholder, then the caller did *NOT* explicitly pass some value for this # parameter. In this case, default this parameter to "None". elif is_color == ARG_VALUE_UNPASSED: is_color = None # Else, the value of the "is_color" parameter is *NOT* that of our unpassed # argument placeholder. In this case, the caller did explicitly passed some # value for this parameter. Preserve this value as is. # Return this boolean. return is_color beartype-0.18.5/beartype/_conf/confcls.py000066400000000000000000001530241461113517100203250ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **configuration class hierarchy** (i.e., public dataclasses enabling users to configure :mod:`beartype` with optional runtime behaviours). Most of the public attributes defined by this private submodule are explicitly exported to external users in our top-level :mod:`beartype.__init__` submodule. This private submodule is *not* intended for direct importation by downstream callers. ''' # ....................{ TODO }.................... #FIXME: Generalize "warning_cls_on_decorator_exception", please. Specifically: #* Deprecate "warning_cls_on_decorator_exception". #* Define a new "decoration_exception_type: Optional[TypeException] = None" # parameter accepting *ANY* arbitrary exception rather than merely a warning. #FIXME: [DOCOS] Document all newly defined configuration parameters in our #reST-formatted docos, please -- including: #* "claw_is_pep526". #* "hint_overrides". #* "violation_door_type". #* "violation_param_type". #* "violation_return_type". #* "violation_type". #* "violation_verbosity". #* "warning_cls_on_decorator_exception". # ....................{ IMPORTS }.................... from beartype.roar._roarwarn import ( _BeartypeConfReduceDecoratorExceptionToWarningDefault, ) from beartype.typing import ( TYPE_CHECKING, Dict, Optional, ) from beartype._conf.confenum import ( BeartypeStrategy, BeartypeViolationVerbosity, ) from beartype._conf.confoverrides import ( BEARTYPE_HINT_OVERRIDES_EMPTY, BeartypeHintOverrides, ) from beartype._conf.conftest import ( default_conf_kwargs_after, default_conf_kwargs_before, die_if_conf_kwargs_invalid, ) from beartype._conf._confget import get_is_color from beartype._data.hint.datahinttyping import ( BoolTristateUnpassable, DictStrToAny, TypeException, TypeWarning, ) from beartype._data.func.datafuncarg import ARG_VALUE_UNPASSED from beartype._util.utilobject import get_object_type_basename from threading import Lock # ....................{ CLASSES }.................... class BeartypeConf(object): ''' **Beartype configuration** (i.e., self-caching dataclass encapsulating all flags, options, settings, and other metadata configuring each type-checking operation performed by :mod:`beartype` -- including each decoration of a callable or class by the :func:`beartype.beartype` decorator). Attributes ---------- _claw_is_pep526 : bool :data:`True` only if type-checking **annotated variable assignments** (i.e., :pep:`526`-compliant assignments to local, global, class, and instance variables annotated by type hints) when importing modules under import hooks published by the :mod:`beartype.claw` subpackage. See also the :meth:`__new__` method docstring. _conf_args : tuple Tuple of the values of *all* possible keyword parameters (in arbitrary order) configuring this configuration. _conf_kwargs : Dict[str, object] Dictionary mapping from the names to values of *all* possible keyword parameters configuring this configuration. _hash : int Precomputed configuration hash returned by the :meth:`__hash__` dunder method for efficiency. _hint_overrides : Dict **Type hint overrides** (i.e., frozen dictionary mapping from arbitrary source to target type hints), enabling callers to lie to both their users and all other packages other than :mod:`beartype`. This dictionary enables callers to externally present a public API annotated by simplified type hints while internally instructing :mod:`beartype` to privately type-check that API under a completely different set of (typically more complicated) type hints. _is_color : Optional[bool] Tri-state boolean governing how and whether beartype colours **type-checking violations** (i.e., :class:`beartype.roar.BeartypeCallHintViolation` exceptions) with POSIX-compliant ANSI escape sequences for readability. Specifically, if this boolean is: * :data:`False`, beartype *never* colours type-checking violations raised by callables configured with this configuration. * :data:`True`, beartype *always* colours type-checking violations raised by callables configured with this configuration. * :data:`None`, beartype conditionally colours type-checking violations raised by callables configured with this configuration only when standard output is attached to an interactive terminal. _is_debug : bool :data:`True` only if debugging :mod:`beartype`. See also the :meth:`__new__` method docstring. _is_pep484_tower : bool :data:`True` only if enabling support for the :pep:`484`-compliant implicit numeric tower. See also the :meth:`__new__` method docstring. _is_violation_door_warn : bool :data:`True` only if :attr:`violation_door_type` is a warning subclass. Note that this is stored only as a negligible optimization to avoid needless recomputation of this boolean during code generation. _is_violation_param_warn : bool :data:`True` only if :attr:`violation_param_type` is a warning subclass. Note that this is stored only as a negligible optimization to avoid needless recomputation of this boolean during code generation. _is_violation_return_warn : bool :data:`True` only if :attr:`violation_return_type` is a warning subclass. Note that this is stored only as a negligible optimization to avoid needless recomputation of this boolean during code generation. _is_warning_cls_on_decorator_exception_set : bool :data:`True` only if the caller explicitly passed the :attr:`_warning_cls_on_decorator_exception` parameter. See also the :meth:`__new__` method docstring. _repr : Optional[str] Either: * If the :func:`repr` builtin has yet to call the :meth:`__repr__` dunder method, :data:`None`. * Else, the machine-readable representation of this configuration, _strategy : BeartypeStrategy **Type-checking strategy** (i.e., :class:`BeartypeStrategy` enumeration member) with which to implement all type-checks in the wrapper function dynamically generated by the :func:`beartype.beartype` decorator for the decorated callable. violation_door_type : TypeException **DOOR violation type** (i.e., type of exception raised by the :func:`beartype.door.die_if_unbearable` type-checker when the object passed to that type-checker violates the type hint passed to that type-checker). See also the :meth:`__new__` method docstring. _violation_param_type : TypeException **Parameter violation type** (i.e., type of exception raised by callables generated by the :func:`beartype.beartype` decorator when those callables receive parameters violating the type hints annotating those parameters). See also the :meth:`__new__` method docstring. _violation_return_type : TypeException **Return violation type** (i.e., type of exception raised by callables generated by the :func:`beartype.beartype` decorator when those callables return values violating the type hints annotating those returns). See also the :meth:`__new__` method docstring. _violation_type : Optional[TypeException] **Default violation type** (i.e., type of exception to default whichever of the ``violation_door_type``, ``violation_param_type``, and ``violation_return_type`` exception types are unpassed and thus :data:`None`). See also the :meth:`__new__` method docstring. _violation_verbosity : BeartypeViolationVerbosity **Violation verbosity** (i.e., positive integer in the inclusive range ``[1, 5]`` governing the verbosity of exception messages raised by type-checking wrappers generated by the :func:`beartype.beartype` decorator when either receiving parameters *or* returning values violating their annotated type hints). See also the :meth:`__new__` method docstring. _warning_cls_on_decorator_exception : Optional[TypeWarning] Configuration parameter governing whether the :func:`beartype.beartype` decorator reduces otherwise fatal exceptions raised at decoration time to equivalent non-fatal warnings of this warning category. See also the :meth:`__new__` method docstring. ''' # ..................{ CLASS VARIABLES }.................. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Synchronize this slots list with the implementations of: # * The __new__() dunder method. # * The __eq__() dunder method. # * The __hash__() dunder method. # * The __repr__() dunder method. # CAUTION: Subclasses declaring uniquely subclass-specific instance # variables *MUST* additionally slot those variables. Subclasses violating # this constraint will be usable but unslotted, which defeats our purposes. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Slot all instance variables defined on this object to minimize the time # complexity of both reading and writing variables across frequently called # cache dunder methods. Slotting has been shown to reduce read and write # costs by approximately ~10%, which is non-trivial. __slots__ = ( '_claw_is_pep526', '_conf_args', '_conf_kwargs', '_hash', '_hint_overrides', '_is_color', '_is_debug', '_is_pep484_tower', '_is_violation_door_warn', '_is_violation_param_warn', '_is_violation_return_warn', '_is_warning_cls_on_decorator_exception_set', '_repr', '_strategy', '_violation_door_type', '_violation_param_type', '_violation_return_type', '_violation_type', '_violation_verbosity', '_warning_cls_on_decorator_exception', ) # Squelch false negatives from mypy. This is absurd. This is mypy. See: # https://github.com/python/mypy/issues/5941 if TYPE_CHECKING: _claw_is_pep526: bool _conf_args: tuple _conf_kwargs: DictStrToAny _hash: int _hint_overrides: BeartypeHintOverrides _is_color: Optional[bool] _is_debug: bool _is_pep484_tower: bool _is_violation_door_warn: bool _is_violation_param_warn: bool _is_violation_return_warn: bool _is_warning_cls_on_decorator_exception_set: bool _repr: Optional[str] _strategy: BeartypeStrategy _violation_door_type: TypeException _violation_param_type: TypeException _violation_return_type: TypeException _violation_type: Optional[TypeException] _violation_verbosity: BeartypeViolationVerbosity _warning_cls_on_decorator_exception: Optional[TypeWarning] # ..................{ INSTANTIATORS }.................. # Note that this __new__() dunder method implements the superset of the # functionality typically implemented by the __init__() dunder method. Due # to Python instantiation semantics, the __init__() dunder method is # intentionally left undefined. Why? Because Python unconditionally invokes # __init__() if defined, even when the initialization performed by that # __init__() has already been performed for the cached instance returned by # __new__(). In short, __init__() and __new__() are largely mutually # exclusive; one typically defines one or the other but *NOT* both. def __new__( cls, # Optional keyword-only parameters. *, # Uncomment us when implementing O(n) type-checking, please. # check_time_max_multiplier: Union[int, None] = 1000, claw_is_pep526: bool = True, hint_overrides: BeartypeHintOverrides = BEARTYPE_HINT_OVERRIDES_EMPTY, is_color: BoolTristateUnpassable = ARG_VALUE_UNPASSED, is_debug: bool = False, is_pep484_tower: bool = False, strategy: BeartypeStrategy = BeartypeStrategy.O1, violation_door_type: Optional[TypeException] = None, violation_param_type: Optional[TypeException] = None, violation_return_type: Optional[TypeException] = None, violation_type: Optional[TypeException] = None, violation_verbosity: BeartypeViolationVerbosity = ( BeartypeViolationVerbosity.DEFAULT), warning_cls_on_decorator_exception: Optional[TypeWarning] = ( _BeartypeConfReduceDecoratorExceptionToWarningDefault), ) -> 'BeartypeConf': ''' Instantiate this configuration if needed (i.e., if *no* prior configuration with these same parameters was previously instantiated) *or* reuse that previously instantiated configuration otherwise. This dunder methods guarantees beartype configurations to be memoized: .. code-block:: python >>> from beartype import BeartypeConf >>> BeartypeConf() is BeartypeConf() True This memoization is *not* merely an optimization. The :func:`beartype.beartype` decorator internally memoizes the private closure it creates and returns on the basis of this configuration, which *must* thus also be memoized. Parameters ---------- check_time_max_multiplier : Union[int, None] = 1000 **Deadline multiplier** (i.e., positive integer instructing :mod:`beartype` to prematurely halt the current type-check when the total running time of the active Python interpreter exceeds this integer multiplied by the running time consumed by both the current type-check and all prior type-checks *and* the caller also passed a non-default ``strategy``) *or* :data:`None` if :mod:`beartype` should never prematurely halt runtime type-checks. Increasing this integer increases the number of container items that :mod:`beartype` type-checks at a cost of decreasing application responsiveness. Likewise, decreasing this integer increases application responsiveness at a cost of decreasing the number of container items that :mod:`beartype` type-checks. Ignored when ``strategy`` is :attr:`BeartypeStrategy.O1`, as that strategy is already effectively instantaneous; imposing deadlines and thus bureaucratic bookkeeping on that strategy would only reduce its efficiency for no good reason, which is a bad reason. Defaults to 1000, in which case a maximum of 0.10% of the total runtime of the active Python process will be devoted to performing non-constant :mod:`beartype` type-checks over container items. This default has been carefully tuned to strike a reasonable balance between runtime type-check coverage and application responsiveness, typically enabling smaller containers to be fully type-checked without noticeably impacting codebase performance. **Theory time.** Let: * :math:`T` be the total time this interpreter has been running. * :math:``b` be the total time :mod:`beartype` has spent type-checking in this interpreter. Clearly, :math:`b <= T`. Generally, :math:`b <<<<<<< T` (i.e., type-checks consume much less time than the total time consumed by the process). However, it's all too easy to exhibit worst-case behaviour of :math:`b ~= T` (i.e., type-checks consume most of the total time). How? By passing the :func:`beartype.door.is_bearable` tester an absurdly large nested container subject to the non-default ``strategy`` of :attr:`BeartypeStrategy.On`. This deadline multiplier mitigates that worst-case behaviour. Specifically, :mod:`beartype` will prematurely halt any iterative type-check across a container when this constraint is triggered: .. code-block:: python b * check_time_max_multiplier >= T claw_is_pep526 : bool, optional :data:`True` only if implicitly type-checking **annotated variable assignments** (i.e., :pep:`526`-compliant assignments to local, global, class, and instance variables annotated by type hints) when importing modules under import hooks published by the :mod:`beartype.claw` subpackage by injecting calls to the :func:`beartype.door.die_if_unbearable` function immediately *after* those assignments in those modules. Enabling this boolean: * Effectively augments :mod:`beartype` into a full-blown **hybrid runtime-static type-checker** (i.e., performing both standard runtime type-checking *and* non-standard static type-checking at runtime). * Adds negligible runtime overhead to all annotated variable assignments in all modules imported under those import hooks. Although the *individual* cost of this overhead for any given assignment is negligible, the *aggregate* cost across all such assignments could be non-negligible in worst-case use cases. Ideally, this boolean should only be disabled for a small subset of performance-sensitive modules *after* profiling those modules to suffer performance regressions under import hooks published by the :mod:`beartype.claw` subpackage. Defaults to :data:`True`. hint_overrides : BeartypeHintOverrides **Type hint overrides** (i.e., frozen dictionary mapping from arbitrary source to target type hints), enabling callers to lie to both their users and all other packages other than :mod:`beartype`. This dictionary enables callers to externally present a public API annotated by simplified type hints while internally instructing :mod:`beartype` to privately type-check that API under a completely different set of (typically more complicated) type hints. Doing so preserves a facade of simplicity for downstream consumers like end users, static type-checkers, and document generators. Defaults to the empty dictionary. Specifically, for each source type hint annotating each callable, class, or variable assignment observed by :mod:`beartype`, if that source type hint is a key of this dictionary, :mod:`beartype` maps that source type hint to the corresponding target type hint in this dictionary. That target type hint then globally "overrides" (i.e., replaces, substitutes for) that source type hint. :mod:`beartype` then uses that target type hint in place of that source type hint. For example, consider this Abomination Unto the Eyes of Guido: .. code-block:: python from beartype, BeartypeConf, BeartypeHintOverrides # @beartype decorator configured to expand all "float" type hints # to "int | float" type hints. lyingbeartype = beartype(conf=BeartypeConf( hint_overrides=BeartypeHintOverrides({float: int | float}))) # The @lyingbeartype decorator now expands this signature... @lyingbeartype def lies(all_lies: list[int]) -> int: return all_lies[0] # ...as if it had been annotated like this instead. @beartype def lies(all_lies: list[int | float]) -> int | float: return all_lies[0] is_color : BoolTristateUnpassable Tri-state boolean governing how and whether beartype colours **type-checking violations** (i.e., :class:`beartype.roar.BeartypeCallHintViolation` exceptions) with POSIX-compliant ANSI escape sequences for readability. Specifically, if this boolean is: * :data:`False`, beartype *never* colours type-checking violations raised by callables configured with this configuration. * :data:`True`, beartype *always* colours type-checking violations raised by callables configured with this configuration. * :data:`None`, beartype conditionally colours type-checking violations raised by callables configured with this configuration only when standard output is attached to an interactive terminal. The ``${BEARTYPE_IS_COLOR}`` environment variable globally overrides *all* attempts by *all* callers to explicitly pass this parameter, enabling end users to enforce a global colour policy across their full app stack. If ``${BEARTYPE_IS_COLOR}`` is set to a different value than that of this parameter, this constructor emits a non-fatal :class:`beartype.roar.BeartypeConfShellVarWarning` warning informing the caller of this configuration conflict. To avoid this conflict, open-source libraries are recommended to *not* pass this parameter; ideally, *only* end user apps should pass this parameter. Effectively defaults to :data:`None`. Technically, this parameter defaults to a private magic constant *not* intended to be passed by callers, enabling :mod:`beartype` to reliably detect whether the caller has explicitly passed this parameter or not. is_debug : bool, optional :data:`True` only if debugging :mod:`beartype`. Enabling this boolean: * Prints the definition (including both the signature and body) of each type-checking wrapper function dynamically generated by :mod:`beartype` to standard output. * Caches the body of each type-checking wrapper function dynamically generated by :mod:`beartype` with the standard :mod:`linecache` module, enabling these function bodies to be introspected at runtime *and* improving the readability of tracebacks whose call stacks contain one or more calls to these :func:`beartype.beartype`-decorated functions. * Appends to the declaration of each **hidden parameter** (i.e., whose name is prefixed by ``"__beartype_"`` and whose value is that of an external attribute internally referenced in the body of that function) the machine-readable representation of the initial value of that parameter, stripped of newlines and truncated to a hopefully sensible length. Since the :func:`beartype._util.text.utiltextrepr.represent_object` function called to do so is shockingly slow, these substrings are conditionally embedded in the returned signature *only* when enabling this boolean. Defaults to :data:`False`. is_pep484_tower : bool, optional :data:`True` only if enabling support for the :pep:`484`-compliant **implicit numeric tower** (i.e., lossy conversion of integers to floating-point numbers *and* both integers and floating-point numbers to complex numbers). Specifically, enabling this instructs :mod:`beartype` to automatically expand: * All :class:`float` type hints to ``float | int``, thus implicitly accepting both integers and floating-point numbers for objects annotated as only accepting floating-point numbers. * All :class:`complex` type hints to ``complex | float | int``, thus implicitly accepting integers, floating-point, and complex numbers for objects annotated as only accepting complex numbers. Defaults to :data:`False` to minimize precision error introduced by lossy conversions from integers to floating-point numbers to complex numbers. Since most integers do *not* have exact representations as floating-point numbers, each conversion of an integer into a floating-point number typically introduces a small precision error that accumulates over multiple conversions and operations into a larger precision error. Enabling this improves the usability of public APIs at a cost of introducing precision errors. strategy : BeartypeStrategy, optional **Type-checking strategy** (i.e., :class:`BeartypeStrategy` enumeration member) with which to implement all type-checks in the wrapper function dynamically generated by the :func:`beartype.beartype` decorator for the decorated callable. Defaults to :attr: `BeartypeStrategy.O1`, the ``O(1)`` constant-time strategy. violation_door_type : Optional[TypeException] **DOOR violation type** (i.e., type of exception raised by the :func:`beartype.door.die_if_unbearable` type-checker when the object passed to that type-checker violates the type hint passed to that type-checker). Defaults to :data:`None`, in which case this type defaults to either: * If ``violation_type`` is passed and *not* :data:`None`, that type. * Else, :exc:`.BeartypeDoorHintViolation`. violation_param_type : Optional[TypeException] **Parameter violation type** (i.e., type of exception raised by callables generated by the :func:`beartype.beartype` decorator when those callables receive parameters violating the type hints annotating those parameters). Defaults to :data:`None`, in which case this type defaults to either: * If ``violation_type`` is passed and *not* :data:`None`, that type. * Else, :exc:`.BeartypeCallHintParamViolation`. violation_return_type : Optional[TypeException] **Return violation type** (i.e., type of exception raised by callables generated by the :func:`beartype.beartype` decorator when those callables return values violating the type hints annotating those returns). Defaults to :data:`None`, in which case this type defaults to either: * If ``violation_type`` is passed and *not* :data:`None`, that type. * Else, :exc:`.BeartypeCallHintReturnViolation`. violation_type : Optional[TypeException] **Default violation type** (i.e., type of exception to default whichever of the ``violation_door_type``, ``violation_param_type``, and ``violation_return_type`` exception types are unpassed and thus :data:`None`). This parameter is merely a convenience enabling callers to trivially control the ``violation_door_type``, ``violation_param_type``, and ``violation_return_type`` parameters *without* having to explicitly pass all three of those parameters. Specifically, if this parameter is: * *Not* :data:`None`, then the ``violation_door_type``, ``violation_param_type``, and ``violation_return_type`` parameters all default to the value of this parameter. * :data:`None`, then: * ``violation_door_type`` defaults to :exc:`.BeartypeDoorHintViolation`. * ``violation_param_type`` defaults to :exc:`.BeartypeCallHintParamViolation`. * ``violation_return_type`` defaults to :exc:`.BeartypeCallHintReturnViolation`. Defaults to :data:`None`. violation_verbosity : BeartypeViolationVerbosity, optional **Violation verbosity** (i.e., positive integer in the inclusive range ``[1, 5]`` governing the verbosity of exception messages raised by type-checking wrappers generated by the :func:`beartype.beartype` decorator when either receiving parameters *or* returning values violating their annotated type hints). Defaults to :attr:`.BeartypeViolationVerbosity.DEFAULT`. warning_cls_on_decorator_exception : Optional[TypeWarning] Configuration parameter governing whether the :func:`beartype.beartype` decorator reduces what would otherwise be fatal exceptions raised at decoration time to equivalent non-fatal warnings of the passed **warning category** (i.e., subclass of the standard :class:`Warning` class). Specifically, this parameter may be either: * :data:`None`, in which case the :func:`beartype.beartype` decorator raises fatal exceptions at decoration time. * A warning category, in which case the :func:`beartype.beartype` decorator reduces fatal exceptions to non-fatal warnings of this category at decoration time. Defaults to a private warning type *not* intended to be passed by callers, enabling :mod:`beartype` to reliably detect when the caller has *not* explicitly passed this parameter and respond accordingly by defaulting this parameter to a context-dependent value. Notably, if this parameter is *not* explicitly passed: * The :func:`beartype.beartype` decorator defaults this parameter to :data:`None`, thus raising decoration-time exceptions. * The :mod:`beartype.claw` API defaults this parameter to the public :class:`beartype.roar.BeartypeClawDecorWarning` warning category, thus reducing decoration-time exceptions to warnings of that category when performing import hooks. This default behaviour significantly increases the likelihood that import hooks installed by :mod:`beartype.claw` will successfully decorate the entirety of their target packages rather than prematurely halt with a single fatal exception at the first decoration issue. Returns ------- BeartypeConf Beartype configuration memoized with these parameters. Raises ------ BeartypeConfParamException If either: * ``is_color`` is *not* a tri-state boolean. * ``is_debug`` is *not* a boolean. * ``is_pep484_tower`` is *not* a boolean. * ``strategy`` is *not* a :class:`BeartypeStrategy` enumeration member. * ``warning_cls_on_decorator_exception`` is neither :data:`None` *nor* a **warning category** (i.e., :class:`Warning` subclass). BeartypeConfShellVarException If either: * The external ``${BEARTYPE_IS_COLOR}`` shell environment variable is set to an unrecognized string (i.e., neither ``"True"``, ``"False"``, nor ``"None"``). ''' #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Synchronize this tuple with the similar "self._conf_kwargs" # dictionary defined below. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # In a non-reentrant thread lock specific to beartype configurations... # # Note that this lock is potentially overkill and thus unnecessary. # Nonetheless, since the number of beartype configurations instantiated # over the lifetime of the average Python interpreter is small, since # non-reentrant thread locks are reasonably fast to enter, and since the # cost of race conditions is high, this lock does no real-world harm and # may actually do a great deal of real-world good. Safety first, all! with _beartype_conf_lock: # ..................{ CACHE }.................. # Validate and possibly override the "is_color" parameter by the # value of the ${BEARTYPE_IS_COLOR} environment variable (if set). is_color = get_is_color(is_color) # Efficiently hashable tuple of these parameters in arbitrary order. conf_args = ( claw_is_pep526, hint_overrides, is_color, is_debug, is_pep484_tower, strategy, violation_door_type, violation_param_type, violation_return_type, violation_type, violation_verbosity, warning_cls_on_decorator_exception, ) # If this method has already instantiated a configuration with these # parameters, return that configuration for consistency and # efficiency. if conf_args in _beartype_conf_args_to_conf: return _beartype_conf_args_to_conf[conf_args] # Else, this method has *NOT* yet instantiated a configuration with # these parameters. In this case, continue to do so and then cache # that configuration. # Dictionary mapping from the names to values of *ALL* possible # keyword parameters configuring this configuration, intentionally # defined *AFTER* this method first attempts to efficiently reduce # to a noop by returning a previously instantiated configuration. conf_kwargs = dict( claw_is_pep526=claw_is_pep526, hint_overrides=hint_overrides, is_color=is_color, is_debug=is_debug, is_pep484_tower=is_pep484_tower, strategy=strategy, violation_door_type=violation_door_type, violation_param_type=violation_param_type, violation_return_type=violation_return_type, violation_type=violation_type, violation_verbosity=violation_verbosity, warning_cls_on_decorator_exception=( warning_cls_on_decorator_exception), ) # Default all parameters not explicitly passed by the user to sane # defaults *BEFORE* validating these parameters. default_conf_kwargs_before(conf_kwargs) # If one or more passed parameters are invalid, raise an exception. die_if_conf_kwargs_invalid(conf_kwargs) # Else, all passed parameters are valid. # Default all parameters not explicitly passed by the user to sane # defaults *AFTER* validating these parameters. default_conf_kwargs_after(conf_kwargs) # ..................{ INSTANTIATE }.................. # Instantiate a new configuration of this type. self = super().__new__(cls) # Nullify critical instance variables for safety. self._repr = None # Precompute the hash to be returned by the __hash__() dunder method # as the hash of a tuple containing these parameters in an arbitrary # (albeit well-defined) order. # # Note this has been profiled to be the optimal means of hashing # object attributes in Python, where "optimal" means: # * Optimally fast. CPython in particular optimizes the creation and # garbage collection of "small" tuples, where "small" is # ill-defined but almost certainly applies here. # * Optimally uniformly distributed, thus minimizing the likelihood # of expensive hash collisions. self._hash = hash(conf_args) # Store data structures encapsulating these passed parameters for # subsequent reuse *BEFORE* possibly modifying the values of these # parameters below. self._conf_args = conf_args self._conf_kwargs = conf_kwargs # Assert that these two data structures encapsulate the same number # of configuration parameters (as a feeble safety check). assert len(self._conf_args) == len(self._conf_kwargs) # Cache this configuration with all relevant dictionary singletons # *BEFORE* possibly modifying the values of passed parameters below. _beartype_conf_args_to_conf[conf_args] = self # ..................{ CLASSIFY }.................. # Classify all passed parameters that have now been possibly # modified above with this configuration. # # Note that this classification intentionally accesses these # parameters from the "conf_kwargs" dictionary possibly modified by # the above call to the default_conf_kwargs() function rather than # the original passed values of these parameters. self._claw_is_pep526 = conf_kwargs['claw_is_pep526'] # pyright: ignore self._hint_overrides = conf_kwargs['hint_overrides'] # pyright: ignore self._is_color = conf_kwargs['is_color'] # pyright: ignore self._is_debug = conf_kwargs['is_debug'] # pyright: ignore self._is_pep484_tower = conf_kwargs['is_pep484_tower'] # pyright: ignore self._strategy = conf_kwargs['strategy'] # pyright: ignore self._violation_door_type = conf_kwargs['violation_door_type'] # pyright: ignore self._violation_param_type = conf_kwargs['violation_param_type'] # pyright: ignore self._violation_return_type = conf_kwargs['violation_return_type'] # pyright: ignore self._violation_type = conf_kwargs['violation_type'] # pyright: ignore self._violation_verbosity = conf_kwargs['violation_verbosity'] # pyright: ignore # Classify all remaining instance variables. self._is_violation_door_warn = issubclass( self._violation_door_type, Warning) # pyright: ignore self._is_violation_param_warn = issubclass( self._violation_param_type, Warning) # pyright: ignore self._is_violation_return_warn = issubclass( self._violation_return_type, Warning) # pyright: ignore # ..................{ CLASSIFY ~ more }.................. # If the value of the "warning_cls_on_decorator_exception" parameter # is still the default private fake warning category established # above, then the caller failed to explicitly pass a valid value. In # this case... if ( warning_cls_on_decorator_exception is _BeartypeConfReduceDecoratorExceptionToWarningDefault ): # Note this fact for subsequent reference elsewhere (e.g., in # the "beartype.claw" subpackage). self._is_warning_cls_on_decorator_exception_set = False # Default this parameter to "None" for safety. Since this # default private fake warning category is *NOT* an actual # warning category intended for real-world use, this category # *MUST* be replaced with a sane default that is safely usable. warning_cls_on_decorator_exception = None # Else, the caller explicitly passed a valid value for this # parameter. In this case, preserve this value and note this fact. else: self._is_warning_cls_on_decorator_exception_set = True self._warning_cls_on_decorator_exception = ( warning_cls_on_decorator_exception) # Return this configuration. return self # ..................{ PROPERTIES }.................. # Read-only public properties effectively prohibiting mutation of their # underlying private attributes. #FIXME: Publicly document this in our reST-formatted docos, please. @property def kwargs(self) -> DictStrToAny: ''' **Beartype configuration keyword dictionary** (i.e., dictionary mapping from the names of all keyword parameters accepted by the :meth:`__new__` method to the corresponding values of those parameters in this configuration). This property can be used to permute new configurations from existing configurations, overriding only a small handful of parameters while preserving all other parameters as is: e.g., .. code-block:: python # Arbitrary input beartype configuration. conf = BeartypeConf(is_color=True) # New keyword dictionary permuted from this input. conf_kwargs = conf.kwargs.copy() conf_kwargs['is_debug'] = True # New beartype configuration initialized by this dictionary. debug_conf = BeartypeConf(**conf_kwargs) See Also -------- :meth:`__new__` Further details. ''' return self._conf_kwargs # ..................{ PROPERTIES ~ options }.................. # Read-only public properties with which this configuration was originally # instantiated (as keyword-only parameters). @property def hint_overrides(self) -> BeartypeHintOverrides: ''' **Type hint overrides** (i.e., frozen dictionary mapping from arbitrary source to target type hints), enabling callers to lie to both their users and all other packages other than :mod:`beartype`. This dictionary enables callers to externally present a public API annotated by simplified type hints while internally instructing :mod:`beartype` to privately type-check that API under a completely different set of (typically more complicated) type hints. See Also -------- :meth:`__new__` Further details. ''' return self._hint_overrides @property def strategy(self) -> BeartypeStrategy: ''' **Type-checking strategy** (i.e., :class:`BeartypeStrategy` enumeration member) with which to implement all type-checks in the wrapper function dynamically generated by the :func:`beartype.beartype` decorator for the decorated callable. See Also -------- :meth:`__new__` Further details. ''' return self._strategy @property def warning_cls_on_decorator_exception(self) -> ( Optional[TypeWarning]): ''' Configuration parameter governing whether the :func:`beartype.beartype` decorator reduces otherwise fatal exceptions raised at decoration time to equivalent non-fatal warnings of this warning category. See Also -------- :meth:`__new__` Further details. ''' return self._warning_cls_on_decorator_exception # ..................{ PROPERTIES ~ options : bool }.................. # Read-only public properties with which this configuration was originally # instantiated (as keyword-only parameters). @property def claw_is_pep526(self) -> bool: ''' :data:`True` only if type-checking **annotated variable assignments** (i.e., :pep:`526`-compliant assignments to local, global, class, and instance variables annotated by type hints) when importing modules under import hooks published by the :mod:`beartype.claw` subpackage. See Also -------- :meth:`__new__` Further details. ''' return self._claw_is_pep526 @property def is_color(self) -> Optional[bool]: ''' Tri-state boolean governing how and whether beartype colours **type-checking violations** (i.e., :class:`beartype.roar.BeartypeCallHintViolation` exceptions) with POSIX-compliant ANSI escape sequences for readability. Specifically, if this boolean is: * :data:`False`, beartype *never* colours type-checking violations raised by callables configured with this configuration. * :data:`True`, beartype *always* colours type-checking violations raised by callables configured with this configuration. * :data:`None`, beartype conditionally colours type-checking violations raised by callables configured with this configuration only when standard output is attached to an interactive terminal. See Also -------- :meth:`__new__` Further details. ''' return self._is_color @property def is_debug(self) -> bool: ''' :data:`True` only if debugging :mod:`beartype`. See Also -------- :meth:`__new__` Further details. ''' return self._is_debug @property def is_pep484_tower(self) -> bool: ''' :data:`True` only if enabling support for the :pep:`484`-compliant implicit numeric tower. See Also -------- :meth:`__new__` Further details. ''' return self._is_pep484_tower # ..................{ PROPERTIES ~ options : violation }.................. @property def violation_door_type(self) -> TypeException: ''' **DOOR violation type** (i.e., type of exception raised by the :func:`beartype.door.die_if_unbearable` type-checker when the object passed to that type-checker violates the type hint passed to that type-checker). See Also -------- :meth:`__new__` Further details. ''' return self._violation_door_type @property def violation_param_type(self) -> TypeException: ''' **Parameter violation type** (i.e., type of exception raised by callables generated by the :func:`beartype.beartype` decorator when those callables receive parameters violating the type hints annotating those parameters). See Also -------- :meth:`__new__` Further details. ''' return self._violation_param_type @property def violation_return_type(self) -> TypeException: ''' **Return violation type** (i.e., type of exception raised by callables generated by the :func:`beartype.beartype` decorator when those callables return values violating the type hints annotating those returns). See Also -------- :meth:`__new__` Further details. ''' return self._violation_return_type @property def violation_type(self) -> Optional[TypeException]: ''' **Default violation type** (i.e., type of exception to default whichever of the ``violation_door_type``, ``violation_param_type``, and ``violation_return_type`` exception types are unpassed and thus :data:`None`). See Also -------- :meth:`__new__` Further details. ''' return self._violation_type @property def violation_verbosity(self) -> BeartypeViolationVerbosity: ''' **Violation verbosity** (i.e., positive integer in the inclusive range ``[1, 5]`` governing the verbosity of exception messages raised by type-checking wrappers generated by the :func:`beartype.beartype` decorator when either receiving parameters *or* returning values violating their annotated type hints). See Also -------- :meth:`__new__` Further details. ''' return self._violation_verbosity # ..................{ DUNDERS }.................. def __eq__(self, other: object) -> bool: ''' **Beartype configuration equality comparator.** Parameters ---------- other : object Arbitrary object to be compared for equality against this configuration. Returns ------- Union[bool, type(NotImplemented)] Either: * If this other object is also a beartype configuration, either: * If these configurations share the same settings, :data:`True`. * Else, :data:`False`. * Else, :data:`NotImplemented`. See Also -------- :func:`_hash_beartype_conf` Further details. ''' # Return either... return ( # If this other object is also a beartype configuration, true only # if these configurations share the same settings; self._conf_args == other._conf_args if isinstance(other, BeartypeConf) else # Else, this other object is *NOT* also a beartype configuration. In # this case, the standard singleton informing Python that this # equality comparator fails to support this comparison. NotImplemented # type: ignore[return-value] ) def __hash__(self) -> int: ''' **Hash** (i.e., non-negative integer quasi-uniquely identifying this beartype configuration with respect to hashable container membership). Returns ------- int Hash of this configuration. ''' # Return the precomputed hash for this configuration. return self._hash def __repr__(self) -> str: ''' **Beartype configuration representation** (i.e., machine-readable string which, when dynamically evaluated as code, restores access to this exact configuration object). Returns ------- str Representation of this configuration. ''' # If machine-readable representation of this configuration has yet to be # computed... if self._repr is None: # Initialize this representation to the unqualified basename of the # class of this configuration. conf_repr = f'{get_object_type_basename(self)}(' # Dictionary mapping from the names to values of *ALL* possible # keyword parameters configuring the default beartype configuration. KWARGS_DEFAULT = BEARTYPE_CONF_DEFAULT._conf_kwargs # For the name and value of each keyword parameter with which this # configuration was instantiated... for kwarg_name, kwarg_value in self._conf_kwargs.items(): # If this value differs from that of the default value for this # keyword parameter... if kwarg_value != KWARGS_DEFAULT[kwarg_name]: # Append a comma-delimited representation of this keyword # argument to this representation. conf_repr += f'{kwarg_name}={kwarg_value}, ' # Else, this value is the default value for this keyword # parameter. In this case, silently ignore this value. Appending # this value to this representation would convey *NO* meaningful # semantics and, indeed, only inhibit the readability of this # representation for end users and developers alike. # If this representation is suffixed by a whitespaced comma, remove # that suffix. if conf_repr[-2:] == ', ': conf_repr = conf_repr[:-2] # Else, this representation is *NOT* suffixed by a comma. In this # case, preserve this representation as is. # Preserve this representation for subsequent use. self._repr = f'{conf_repr})' # Else, the machine-readable representation of this configuration has # already been computed. # Return the machine-readable representation of this configuration. return self._repr # ....................{ PRIVATE ~ globals }.................... _beartype_conf_lock = Lock() ''' **Non-reentrant beartype configuration thread lock** (i.e., low-level thread locking mechanism implemented as a highly efficient C extension, defined as an global for non-reentrant reuse elsewhere as a context manager). ''' _beartype_conf_args_to_conf: Dict[tuple, BeartypeConf] = {} ''' Non-thread-safe **beartype configuration parameter cache** (i.e., dictionary mapping from the hash of each set of parameters accepted by a prior call of the :meth:`BeartypeConf.__new__` instantiator to the unique :class:`BeartypeConf` instance instantiated by that call). Caveats ------- **This cache is non-thread-safe.** However, since this cache is only used as a memoization optimization, the only harmful consequences of a race condition between threads contending over this cache is a mildly inefficient (but otherwise harmless) repeated re-memoization of duplicate configurations. ''' # ....................{ GLOBALS }.................... # This global is intentionally defined *AFTER* all other attributes above, which # this global implicitly assumes to be defined. BEARTYPE_CONF_DEFAULT = BeartypeConf() ''' **Default beartype configuration** (i.e., :class:`BeartypeConf` class instantiated with *no* parameters and thus default parameters), globalized to trivially optimize external access to this configuration throughout this codebase. Note that this global is *not* publicized to end users, who can simply instantiate ``BeartypeConf()`` to obtain the same singleton. ''' beartype-0.18.5/beartype/_conf/confenum.py000066400000000000000000000130131461113517100205010ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **configuration enumerations** (i.e., public enumerations whose members may be passed as initialization-time parameters to the :meth:`beartype._conf.confcls.BeartypeConf.__init__` constructor to configure :mod:`beartype` with optional runtime type-checking behaviours). Most of the public attributes defined by this private submodule are explicitly exported to external users in our top-level :mod:`beartype.__init__` submodule. This private submodule is *not* intended for direct importation by downstream callers. ''' # ....................{ IMPORTS }.................... from enum import ( Enum, IntEnum, auto as next_enum_member_value, unique as die_unless_enum_member_values_unique, ) # ....................{ ENUMERATIONS }.................... @die_unless_enum_member_values_unique class BeartypeStrategy(Enum): ''' Enumeration of all kinds of **type-checking strategies** (i.e., competing procedures for type-checking objects passed to or returned from :func:`beartype.beartype`-decorated callables, each with concomitant tradeoffs with respect to runtime complexity and quality assurance). Strategies are intentionally named according to `conventional Big O notation `__ (e.g., :attr:`BeartypeStrategy.On` enables the ``O(n)`` strategy). Strategies are established per-decoration at the fine-grained level of callables decorated by the :func:`beartype.beartype` decorator by setting the :attr:`beartype.BeartypeConf.strategy` parameter of the :class:`beartype.BeartypeConf` object passed as the optional ``conf`` parameter to that decorator. Strategies enforce their corresponding runtime complexities (e.g., ``O(n)``) across *all* type-checks performed for callables enabling those strategies. For example, a callable configured by the :attr:`BeartypeStrategy.On` strategy will exhibit linear ``O(n)`` complexity as its overhead for type-checking each nesting level of each container passed to and returned from that callable. .. _Big O: https://en.wikipedia.org/wiki/Big_O_notation Attributes ---------- O0 : EnumMemberType **No-time strategy** (i.e, disabling type-checking for a decorated callable by reducing :func:`beartype.beartype` to the identity decorator for that callable). Although seemingly useless, this strategy enables users to selectively blacklist (prevent) callables from being type-checked by our as-yet-unimplemented import hook. When implemented, that hook will type-check all callables within a package or module *except* those callables explicitly decorated by this strategy. O1 : EnumMemberType **Constant-time strategy** (i.e., the default ``O(1)`` strategy, type-checking a single randomly selected item of each container). As the default, this strategy need *not* be explicitly enabled. Ologn : EnumMemberType **Logarithmic-time strategy** (i.e., the ``O(log n)`` strategy, type-checking a randomly selected number of items ``log(len(obj))`` of each container ``obj``). This strategy is **currently unimplemented.** (*To be implemented by a future beartype release.*) On : EnumMemberType **Linear-time strategy** (i.e., the ``O(n)`` strategy, type-checking *all* items of a container). This strategy is **currently unimplemented.** (*To be implemented by a future beartype release.*) ''' O0 = next_enum_member_value() O1 = next_enum_member_value() Ologn = next_enum_member_value() On = next_enum_member_value() @die_unless_enum_member_values_unique class BeartypeViolationVerbosity(IntEnum): ''' Enumeration of all kinds of **violation verbosities** (i.e., positive integers in the inclusive range ``[1, 5]`` governing the verbosity of exception messages raised by type-checking wrappers generated by the :func:`beartype.beartype` decorator when either receiving parameters *or* returning values violating their annotated type hints). Verbosities transparently reduce to integers and can thus be used wherever integers are used (e.g., ``BeartypeViolationVerbosity.DEFAULT + 1`` is the next level of verbosity beyond that of the default). Verbosities are established per-decoration at the fine-grained level of callables decorated by the :func:`beartype.beartype` decorator by setting the :attr:`beartype.BeartypeConf.violation_verbosity` parameter of the :class:`beartype.BeartypeConf` object passed as the optional ``conf`` parameter to that decorator. Attributes ---------- MINIMAL : EnumMemberType **Minimal verbosity,** intended for end users potentially lacking core expertise in Python. DEFAULT : EnumMemberType **Default verbosity,** intended for a general developer audience assumed to be fluent in Python. MAXIMAL : EnumMemberType **Maximum verbosity,** extending the default verbosity with additional contextual metadata intended for debugging violations. This includes: * A machine-readable representation of the beartype configuration under which the current violation occurred. ''' MINIMAL = next_enum_member_value() DEFAULT = next_enum_member_value() MAXIMAL = next_enum_member_value() beartype-0.18.5/beartype/_conf/confoverrides.py000066400000000000000000000137741461113517100215550ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **hint overrides class hierarchy** (i.e., public classes implementing immutable mappings intended to be passed as the value of the ``hint_overrides`` parameter accepted by the :class:`beartype.BeartypeConf.__init__` method). ''' # ....................{ IMPORTS }.................... from beartype.meta import URL_ISSUES from beartype.roar import BeartypeHintOverridesException from beartype._data.hint.datahinttyping import ( Pep484TowerComplex, Pep484TowerFloat, ) from beartype._util.kind.map.utilmapfrozen import FrozenDict from re import ( escape as re_escape, search as re_search, ) # ....................{ CLASSES }.................... #FIXME: Unit test us up, please. class BeartypeHintOverrides(FrozenDict): ''' Beartype **hint overrides** (i.e., immutable mapping intended to be passed as the value of the ``hint_overrides`` parameter accepted by the :class:`beartype.BeartypeConf.__init__` method). ''' # ..................{ INITIALIZERS }.................. def __init__(self, *args, **kwargs) -> None: # Instantiate this immutable dictionary with all passed parameters. super().__init__(*args, **kwargs) # For each source and target hint override in this dictionary... for hint_override_src, hint_override_trg in self.items(): # The machine-readable representation of this source override, # escaped to protect all regex-specific syntax in this # representation from being erroneously parsed as that syntax. HINT_OVERRIDE_SRC_REPR = re_escape(repr(hint_override_src)) # Regular expression matching subscription-style recursion in this # hint override (e.g., 'str: list[str]'). # # Note that: # * Beartype currently only supports union-style recursion (e.g., # "float: int | float"). # * Regular expressions inevitably fail in edge cases (e.g., # "str: Literal['str']"). Thankfully, hint overrides *AND* these # edge cases are sufficiently rare that we can conveniently ignore # them until some GitHub pyromaniac inevitably complains and # starts lighting dumpster fires on our issue tracker. HINT_OVERRIDE_RECURSION_REGEX = ( # An opening "[" delimeter followed by zero or more characters # that are *NOT* the corresponding closing "]" delimiter. r'\[[^]]*' # The machine-readable representation of this source override, # bounded before but *NOT* after by a word boundary. Why? # Consider an invalid recursive non-union type hint resembling: # BeartypeHintOverrides({List[str]: Tuple[List[str], ...]}) # # In the above case, "HINT_OVERRIDE_SRC_REPR == 'List[str]'. # Bounding that source string: # * Before by a word boundary guards against false positives # that would otherwise match valid larger target strings # merely suffixed by that string but otherwise unrelated and # thus non-recursive (e.g., "Tuple[MuhList[str]]"). # * After by a word boundary would effectively prevent # *ANYTHING* from matching, because only alphanumeric # characters match the word boundary following a punctuation # character (e.g., "List[str]]!]?..."). fr'\b{HINT_OVERRIDE_SRC_REPR}' # Zero or more characters that are *NOT* the corresponding # closing "]" delimiter followed by that delimiter. r'[^]]*\]' ) # print(f'HINT_OVERRIDE_RECURSION_REGEX: {HINT_OVERRIDE_RECURSION_REGEX}') # Match object if this hint override contains one or more instances # of subscription-style recursion *OR* "None" otherwise. hint_override_recursion = re_search( HINT_OVERRIDE_RECURSION_REGEX, repr(hint_override_trg)) # If this hint override contains one or more instances of # subscription-style recursion, raise an exception. if hint_override_recursion is not None: raise BeartypeHintOverridesException( f'Recursive type hint override ' f'{repr(hint_override_src)}: {repr(hint_override_trg)} ' f'currently unsupported. Please complain on our friendly ' f'issue tracker if you feel that this is dumb:\n' f'\t{URL_ISSUES}' ) # Else, this hint override contains *NO* such recursion. # ....................{ GLOBALS }.................... BEARTYPE_HINT_OVERRIDES_EMPTY = BeartypeHintOverrides() ''' **Empty type hint overrides** (i.e., default :class:`.BeartypeHintOverrides` instance overriding *no* type hints). ''' BEARTYPE_HINT_OVERRIDES_PEP484_TOWER = BeartypeHintOverrides({ float: Pep484TowerFloat, complex: Pep484TowerComplex, }) ''' :pep:`484`-compliant **implicit tower type hint overrides** (i.e., :class:`.BeartypeHintOverrides` instance lossily convering integers to floating-point numbers *and* both integers and floating-point numbers to complex numbers). Specifically, these overrides instruct :mod:`beartype` to automatically expand: * All :class:`float` type hints to ``float | int``, thus implicitly accepting both integers and floating-point numbers for objects annotated as only accepting floating-point numbers. * All :class:`complex` type hints to ``complex | float | int``, thus implicitly accepting integers, floating-point, and complex numbers for objects annotated as only accepting complex numbers. ''' beartype-0.18.5/beartype/_conf/conftest.py000066400000000000000000000311521461113517100205200ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **configuration class testers** (i.e., low-level callables testing and validating various metadata of interest to the high-level :class:`beartype.BeartypeConf` dataclass). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... import beartype from beartype.roar import ( BeartypeConfException, BeartypeConfParamException, BeartypeCallHintParamViolation, BeartypeCallHintReturnViolation, BeartypeDoorHintViolation, ) from beartype.typing import Optional from beartype._cave._cavemap import NoneTypeOr from beartype._conf.confenum import ( BeartypeStrategy, BeartypeViolationVerbosity, ) from beartype._conf.confoverrides import ( BEARTYPE_HINT_OVERRIDES_PEP484_TOWER, BeartypeHintOverrides, ) from beartype._data.hint.datahinttyping import DictStrToAny from beartype._util.cls.utilclstest import is_type_subclass # ....................{ RAISERS }.................... def die_unless_conf(conf: 'beartype.BeartypeConf') -> None: ''' Raise an exception unless the passed object is a beartype configuration. Parameters ---------- conf : beartype.BeartypeConf Object to be validated. Raises ------ BeartypeConfException If this object is *not* a beartype configuration. ''' # Avoid circular import dependencies. from beartype._conf.confcls import BeartypeConf # If this object is *NOT* a configuration, raise an exception. if not isinstance(conf, BeartypeConf): raise BeartypeConfException( f'{repr(conf)} not beartype configuration.') # Else, this object is a configuration. def die_if_conf_kwargs_invalid(conf_kwargs: DictStrToAny) -> None: ''' Raise an exception if one or more configuration parameters in the passed dictionary of such parameters are invalid. Parameters ---------- conf_kwargs : Dict[str, object] Dictionary mapping from the names to values of *all* possible keyword parameters configuring this configuration. Raises ------ BeartypeConfParamException If one or more configurations parameter in this dictionary are invalid. ''' # ..................{ VALIDATE }.................. # If "claw_is_pep526" is *NOT* a boolean, raise an exception. if not isinstance(conf_kwargs['claw_is_pep526'], bool): raise BeartypeConfParamException( f'Beartype configuration parameter "claw_is_pep526" ' f'value {repr(conf_kwargs["claw_is_pep526"])} not boolean.' ) # Else, "claw_is_pep526" is a boolean. # # If "hint_overrides" is *NOT* a frozen dict, raise an exception. elif not isinstance(conf_kwargs['hint_overrides'], BeartypeHintOverrides): raise BeartypeConfParamException( f'Beartype configuration parameter "hint_overrides" ' f'value {repr(conf_kwargs["hint_overrides"])} not ' f'frozen dictionary ' f'(i.e., "beartype.BeartypeHintOverrides" instance).' ) # Else, "hint_overrides" is a frozen dict. # # If "is_color" is *NOT* a tri-state boolean, raise an exception. elif not isinstance(conf_kwargs['is_color'], NoneTypeOr[bool]): raise BeartypeConfParamException( f'Beartype configuration parameter "is_color" ' f'value {repr(conf_kwargs["is_color"])} not tri-state boolean ' f'(i.e., "True", "False", or "None").' ) # Else, "is_color" is a tri-state boolean. # # If "is_debug" is *NOT* a boolean, raise an exception. elif not isinstance(conf_kwargs['is_debug'], bool): raise BeartypeConfParamException( f'Beartype configuration parameter "is_debug" ' f'value {repr(conf_kwargs["is_debug"])} not boolean.' ) # Else, "is_debug" is a boolean. # # If "is_pep484_tower" is *NOT* a boolean, raise an exception. elif not isinstance(conf_kwargs['is_pep484_tower'], bool): raise BeartypeConfParamException( f'Beartype configuration parameter "is_pep484_tower" ' f'value {repr(conf_kwargs["is_debug"])} not boolean.' ) # Else, "is_pep484_tower" is a boolean. # # If "strategy" is *NOT* an enumeration member, raise an exception. elif not isinstance(conf_kwargs['strategy'], BeartypeStrategy): raise BeartypeConfParamException( f'Beartype configuration parameter "strategy" ' f'value {repr(conf_kwargs["strategy"])} not ' f'"beartype.BeartypeStrategy" enumeration member.' ) # Else, "strategy" is an enumeration member. # # If "violation_verbosity" is *NOT* an enumeration member, raise an # exception. elif not isinstance( conf_kwargs['violation_verbosity'], BeartypeViolationVerbosity): raise BeartypeConfParamException( f'Beartype configuration parameter "violation_verbosity" ' f'value {repr(conf_kwargs["violation_verbosity"])} not ' f'"beartype.BeartypeViolationVerbosity" enumeration member.' ) # Else, "violation_verbosity" is an enumeration member. # # If "warning_cls_on_decorator_exception" is neither "None" *NOR* a # warning category, raise an exception. elif not ( conf_kwargs['warning_cls_on_decorator_exception'] is None or is_type_subclass( conf_kwargs['warning_cls_on_decorator_exception'], Warning) ): raise BeartypeConfParamException( f'Beartype configuration parameter ' f'"warning_cls_on_decorator_exception" value ' f'{repr(conf_kwargs["warning_cls_on_decorator_exception"])} ' f'neither "None" nor warning category ' f'(i.e., "Warning" subclass).' ) # Else, "warning_cls_on_decorator_exception" is either "None" *OR* a # warning category. # For the name of each keyword parameter whose value is expected to be an # exception subclass... for arg_name_exception_subclass in _ARG_NAMES_EXCEPTION_SUBCLASS: # If the value of this keyword parameter is *NOT* an exception subclass, # raise an exception. if not is_type_subclass( conf_kwargs[arg_name_exception_subclass], Exception): raise BeartypeConfParamException( f'Beartype configuration parameter ' f'"{arg_name_exception_subclass}" value ' f'{repr(conf_kwargs[arg_name_exception_subclass])} not ' f'exception type.' ) # ....................{ DEFAULTERS }.................... def default_conf_kwargs_before(conf_kwargs: DictStrToAny) -> None: ''' Sanitize the passed dictionary of configuration parameters by defaulting all parameters not explicitly passed by the user to sane internal defaults *before* the :func:`.die_if_conf_kwargs_invalid` raiser validates these parameters. Parameters ---------- conf_kwargs : Dict[str, object] Dictionary mapping from the names to values of *all* possible keyword parameters configuring this configuration. ''' # ..................{ DEFAULT ~ violation_*type }.................. # Default violation type if passed *OR* "None" if unpassed. violation_type = conf_kwargs['violation_type'] # If... if ( # The caller explicitly passed a default violation type... violation_type is not None and # That is *NOT* an exception subclass... not is_type_subclass(violation_type, Exception) # Raise an exception. ): raise BeartypeConfParamException( f'Beartype configuration parameter "violation_type" value ' f'{repr(violation_type)} not exception type.' ) # Else, the caller either passed *NO* default violation type or passed a # valid default violation type. # If the caller did *NOT* explicitly pass a DOOR violation type, default # this type to either the default violation type if passed *OR* the default # DOOR violation type if unpassed. if conf_kwargs['violation_door_type'] is None: conf_kwargs['violation_door_type'] = ( violation_type or BeartypeDoorHintViolation) # Else, the caller explicitly passed a DOOR violation type. # If the caller did *NOT* explicitly pass a parameter violation type, # default this type to either the default violation type if passed *OR* the # default DOOR violation type if unpassed. if conf_kwargs['violation_param_type'] is None: conf_kwargs['violation_param_type'] = ( violation_type or BeartypeCallHintParamViolation) # If the caller did *NOT* explicitly pass a return violation type, default # this type to either the default violation type if passed *OR* the default # DOOR violation type if unpassed. if conf_kwargs['violation_return_type'] is None: conf_kwargs['violation_return_type'] = ( violation_type or BeartypeCallHintReturnViolation) # Else, the caller explicitly passed a DOOR violation type. def default_conf_kwargs_after(conf_kwargs: DictStrToAny) -> None: ''' Sanitize the passed dictionary of configuration parameters by defaulting all parameters not explicitly passed by the user to sane internal defaults *after* the :func:`.die_if_conf_kwargs_invalid` raiser validates these parameters. Parameters ---------- conf_kwargs : Dict[str, object] Dictionary mapping from the names to values of *all* possible keyword parameters configuring this configuration. ''' # ..................{ DEFAULT ~ hint_overrides }.................. # If enabling the PEP 484-compliant implicit numeric tower... if conf_kwargs['is_pep484_tower']: # Hint overrides if passed by the caller *OR* "None" otherwise. hint_overrides = conf_kwargs['hint_overrides'] # Target hint overrides for the source "float" and "complex" types if # any *OR* "None" otherwise. hint_overrides_float = hint_overrides.get(float) hint_overrides_complex = hint_overrides.get(complex) # Whichever of the "float" or "complex" types are already existing # overrides in the passed type hint overrides. hint_override_cls_conflict: Optional[type] = None # If these overrides already define conflicting overrides for either the # "float" or "complex" types, record that fact. if ( hint_overrides_float and hint_overrides_float != BEARTYPE_HINT_OVERRIDES_PEP484_TOWER[float] ): hint_override_cls_conflict = float elif ( hint_overrides_complex and hint_overrides_complex != BEARTYPE_HINT_OVERRIDES_PEP484_TOWER[ complex] ): hint_override_cls_conflict = complex # Else, these overrides do *NOT* already define conflicting overrides # for either the "float" or "complex" types. # If these overrides already define conflicting overrides for either the # "float" or "complex" types, raise an exception. if hint_override_cls_conflict: raise BeartypeConfParamException( f'Beartype configuration ' f'parameter "is_pep484_tower" conflicts with ' f'parameter "hint_overrides" key ' f'"{hint_override_cls_conflict.__name__}" ' f'value ' f'{repr(hint_overrides[hint_override_cls_conflict])}.' ) # Else, these overrides do *NOT* already define conflicting overrides # for either the "float" or "complex" types. # Add hint overrides expanding the passed type hint overrides with # additional overrides mapping: # * The "float" type to the "float | int" type hint. # * The "complex" type to the "complex | float | int" type hint. conf_kwargs['hint_overrides'] = ( hint_overrides | BEARTYPE_HINT_OVERRIDES_PEP484_TOWER) # type: ignore[assignment] # Else, the PEP 484-compliant implicit numeric tower is disabled. # ....................{ PRIVATE ~ globals }.................... _ARG_NAMES_EXCEPTION_SUBCLASS = ( 'violation_door_type', 'violation_param_type', 'violation_return_type', ) ''' Tuple of the names of all keyword parameters to the :meth:`beartype.BeartypeConf.__new__` dunder method whose values are expected to be exception subclasses. ''' beartype-0.18.5/beartype/_data/000077500000000000000000000000001461113517100163035ustar00rootroot00000000000000beartype-0.18.5/beartype/_data/__init__.py000066400000000000000000000000001461113517100204020ustar00rootroot00000000000000beartype-0.18.5/beartype/_data/ast/000077500000000000000000000000001461113517100170725ustar00rootroot00000000000000beartype-0.18.5/beartype/_data/ast/__init__.py000066400000000000000000000000001461113517100211710ustar00rootroot00000000000000beartype-0.18.5/beartype/_data/ast/dataast.py000066400000000000000000000027431461113517100210730ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **abstract syntax tree (AST) singletons** (i.e., objects pertaining to ASTs commonly required throughout this codebase, reducing space and time consumption by preallocating widely used AST-centric objects). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from ast import ( ClassDef, FunctionDef, Load, Store, ) # ....................{ NODES }.................... NODE_CONTEXT_LOAD = Load() ''' **Node context load singleton** (i.e., object suitable for passing as the ``ctx`` keyword parameter accepted by the ``__init__()`` method of various abstract syntax tree (AST) node classes). ''' NODE_CONTEXT_STORE = Store() ''' **Node context store singleton** (i.e., object suitable for passing as the ``ctx`` keyword parameter accepted by the ``__init__()`` method of various abstract syntax tree (AST) node classes). ''' # ....................{ TYPES }.................... TYPES_NODE_LEXICAL_SCOPE = frozenset(( ClassDef, FunctionDef, )) ''' Frozen set of all **lexically scoping abstract syntax tree (AST) node types** (i.e., types of all AST nodes whose declaration defines a new lexical scope). ''' beartype-0.18.5/beartype/_data/cls/000077500000000000000000000000001461113517100170645ustar00rootroot00000000000000beartype-0.18.5/beartype/_data/cls/__init__.py000066400000000000000000000000001461113517100211630ustar00rootroot00000000000000beartype-0.18.5/beartype/_data/cls/datacls.py000066400000000000000000000134311461113517100210530ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **class globals** (i.e., global constants describing various well-known types). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import ( Dict, ForwardRef, FrozenSet, ) from beartype._cave._cavefast import ( ClassType, EnumMemberType, FunctionType, MethodDecoratorBuiltinTypes, NoneType, ) from collections.abc import ( Set as SetABC, ) from pathlib import Path # ....................{ TYPES ~ abc }.................... TYPES_CONTEXTMANAGER_FAKE = (Path,) ''' Tuple of all **fake context manager types** (i.e., types that erroneously masquerade as being context managers by defining fake ``__enter__()`` dunder methods, which typically emit non-fatal warnings and reduce to noops). This set includes: * The :class:`pathlib.Path` superclass, whose subclasses under Python < 3.13 defined fake ``__enter__()`` dunder methods that are now deprecated. ''' TYPES_SET_OR_TUPLE = (SetABC, tuple) ''' 2-tuple containing the superclasses of all frozen sets and tuples. Note that the :class:`Set` abstract base class (ABC) rather than the concrete :class:`set` subclass is intentionally listed here, as the concrete :class:`frozenset` subclass subclasses the former but *not* latter: e.g., .. code-block:: python >>> from collections.abc import Set >>> issubclass(frozenset, Set) True >>> issubclass(frozenset, set) False ''' # ....................{ TYPES ~ beartype }.................... # Types of *ALL* objects that may be decorated by @beartype, intentionally # listed in descending order of real-world prevalence for negligible efficiency # gains when performing isinstance()-based tests against this tuple. These # include the types of *ALL*... TYPES_BEARTYPEABLE = ( # Pure-Python unbound functions and methods. FunctionType, # Pure-Python classes. ClassType, # C-based builtin method descriptors wrapping pure-Python unbound methods, # including class methods, static methods, and property methods. MethodDecoratorBuiltinTypes, ) ''' Tuple of all **beartypeable types** (i.e., types of all objects that may be decorated by the :func:`beartype.beartype` decorator). ''' # ....................{ TYPES ~ module }.................... # Defined below by the _init() function. TYPE_BUILTIN_NAME_TO_TYPE: Dict[str, type] = None # type: ignore[assignment] ''' Dictionary mapping from the name of each **builtin type** (i.e., globally accessible C-based type implicitly accessible from all scopes and thus requiring *no* explicit importation) to that type. ''' # Defined below by the _init() function. TYPES_BUILTIN: FrozenSet[type] = None # type: ignore[assignment] ''' Frozen set of all **builtin types** (i.e., globally accessible C-based types implicitly accessible from all scopes and thus requiring *no* explicit importation). ''' # ....................{ PEP ~ (484|585) }.................... TYPES_PEP484585_REF = (str, ForwardRef) ''' Tuple union of all :pep:`484`- or :pep:`585`-compliant **forward reference types** (i.e., classes of all forward reference objects). Specifically, this union contains: * :class:`str`, the class of all :pep:`585`-compliant forward reference objects implicitly preserved by all :pep:`585`-compliant type hint factories when subscripted by a string. * :class:`HINT_PEP484_FORWARDREF_TYPE`, the class of all :pep:`484`-compliant forward reference objects implicitly created by all :mod:`typing` type hint factories when subscripted by a string. While :pep:`585`-compliant type hint factories preserve string-based forward references as is, :mod:`typing` type hint factories coerce string-based forward references into higher-level objects encapsulating those strings. The latter approach is the demonstrably wrong approach, because encapsulating strings only harms space and time complexity at runtime with *no* concomitant benefits. ''' # ....................{ PEP ~ 586 }.................... TYPES_PEP586_ARG = (bool, bytes, int, str, EnumMemberType, NoneType) ''' Tuple of all types of objects permissible as arguments subscripting the :pep:`586`-compliant :attr:`typing.Literal` singleton. These types are explicitly listed by :pep:`586` as follows: Literal may be parameterized with literal ints, byte and unicode strings, bools, Enum values and None. ''' # ....................{ PRIVATE ~ init }.................... def _init() -> None: ''' Initialize this submodule. ''' # Function-specific imports. from builtins import __dict__ as BUILTIN_NAME_TO_TYPE # type: ignore[attr-defined] # Global variables redefined below. global TYPE_BUILTIN_NAME_TO_TYPE, TYPES_BUILTIN # Dictionary mapping from,... TYPE_BUILTIN_NAME_TO_TYPE = { # The name of each builtin type to that type builtin_name: builtin_value # For each attribute defined by the standard "builtins" module... for builtin_name, builtin_value in BUILTIN_NAME_TO_TYPE.items() # If... if ( # This attribute is a type *AND*... isinstance(builtin_value, type) and # This is *NOT* a dunder attribute. not ( builtin_name.startswith('__') and builtin_name.endswith ('__') ) ) } # Frozenset of all builtin types, derived from this dictionary. TYPES_BUILTIN = frozenset(TYPE_BUILTIN_NAME_TO_TYPE.values()) # Initialize this submodule. _init() beartype-0.18.5/beartype/_data/code/000077500000000000000000000000001461113517100172155ustar00rootroot00000000000000beartype-0.18.5/beartype/_data/code/__init__.py000066400000000000000000000000001461113517100213140ustar00rootroot00000000000000beartype-0.18.5/beartype/_data/code/datacodeindent.py000066400000000000000000000071511461113517100225410ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **Python expression indentation substrings** (i.e., string constants intended to be embedded as syntactically valid indentation in dynamically generated Python expressions). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ SUBCLASSES }.................... class IndentLevelToCode(dict): ''' **Indentation cache** (i.e., dictionary mapping from 1-based indentation levels to the corresponding indentation string constant). See Also -------- :data:`.INDENT_LEVEL_TO_CODE` Singleton instance of this dictionary subclass. ''' # ....................{ DUNDERS }.................... def __missing__(self, indent_level: int) -> str: ''' Dunder method explicitly called by the superclass :meth:`dict.__getitem__` method implicitly called on the first ``[``- and ``]``-delimited attempt to access an indentation string constant with the passed indentation level. Parameters ---------- indent_level : int 1-based level of indentation to be created, cached, and returned. Returns ------- str String constant indented to this level of indentation. Raises ------ AssertionError If either: * ``indent_level`` is *not* an integer. * ``indent_level`` is a **non-positive integer** (i.e., is less than or equal to 0). ''' assert isinstance(indent_level, int), ( f'{repr(indent_level)} not integer.') assert indent_level > 0, f'{indent_level} <= 0.' # print(f'Generating indentation level {indent_level}...') # String constant indented to this level of indentation. # # Note that this could also be done recursively (e.g., as # "self[indent_level - 1]"), but that doing so would be needlessly cute, # overly slow, and dangerously fragile for *NO* good reason. indent_code = ' ' * indent_level # Cache this string constant. self[indent_level] = indent_code # Return this string constant. return indent_code # ....................{ MAPPINGS }.................... INDENT_LEVEL_TO_CODE = IndentLevelToCode() ''' **Indentation cache singleton** (i.e., global dictionary efficiently mapping from 1-based indentation levels to the corresponding indentation string constant). Caveats ------- **Indentation string constants should always be accessed via this cache rather than manually generated.** This cache dynamically creates and efficiently caches indentation string constants on the first access of those constants, obviating the performance cost of string formatting required to create these constants. Examples -------- .. code-block:: pycon >>> from beartype._data.code.datacodeindent import INDENT_LEVEL_TO_CODE >>> INDENT_LEVEL_TO_CODE[1] ' ' >>> INDENT_LEVEL_TO_CODE[2] ' ' ''' # ....................{ STRINGS }.................... CODE_INDENT_1 = INDENT_LEVEL_TO_CODE[1] ''' Code snippet expanding to a single level of indentation. ''' CODE_INDENT_2 = INDENT_LEVEL_TO_CODE[2] ''' Code snippet expanding to two levels of indentation. ''' CODE_INDENT_3 = INDENT_LEVEL_TO_CODE[3] ''' Code snippet expanding to three levels of indentation. ''' beartype-0.18.5/beartype/_data/code/datacodemagic.py000066400000000000000000000017001461113517100223320ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **magic Python expression substrings** (i.e., string constants intended to be embedded in dynamically generated Python expressions). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ CODE ~ operator }.................... LINE_RSTRIP_INDEX_AND = -len(' and') ''' Negative index relative to the end of any arbitrary newline-delimited Python code string suffixed by the boolean operator ``" and"`` required to strip that suffix from that substring. ''' LINE_RSTRIP_INDEX_OR = -len(' or') ''' Negative index relative to the end of any arbitrary newline-delimited Python code string suffixed by the boolean operator ``" or"`` required to strip that suffix from that substring. ''' beartype-0.18.5/beartype/_data/error/000077500000000000000000000000001461113517100174345ustar00rootroot00000000000000beartype-0.18.5/beartype/_data/error/__init__.py000066400000000000000000000000001461113517100215330ustar00rootroot00000000000000beartype-0.18.5/beartype/_data/error/dataerrmagic.py000066400000000000000000000107231461113517100224340ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **magic error substrings** (i.e., string constants intended to be embedded in exception and warning messages or otherwise pertaining to exceptions and warnings). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ STRINGS }.................... EXCEPTION_PLACEHOLDER = '$%ROOT_PITH_LABEL/~' ''' Non-human-readable source substring to be globally replaced by a human-readable target substring in the messages of memoized exceptions passed to the :func:`reraise_exception` function. This substring prefixes most exception messages raised by memoized callables, including code generation factories memoized on passed PEP-compliant type hints (e.g., the :mod:`beartype._check` and :mod:`beartype._decor` submodules). The :func:`beartype._util.error.utilerrraise.reraise_exception_placeholder` function then dynamically replaces this prefix of the message of the passed exception with a human-readable synopsis of the current unmemoized exception context, including the name of both the currently decorated callable *and* the currently iterated parameter or return of that callable for aforementioned code generation factories. Usage ----- This substring is typically hard-coded into non-human-readable exception messages raised by low-level callables memoized with the :func:`beartype._util.cache.utilcachecall.callable_cached` decorator. Why? Memoization prohibits those callables from raising human-readable exception messages. Why? Doing so would require those callables to accept fine-grained parameters unique to each call to those callables, which those callables would then dynamically format into human-readable exception messages raised by those callables. The standard example would be a ``exception_prefix`` parameter labelling the human-readable category of type hint being inspected by the current call (e.g., ``@beartyped muh_func() parameter "muh_param" PEP type hint "List[int]"`` for a ``List[int]`` type hint on the `muh_param` parameter of a ``muh_func()`` function decorated by the :func:`beartype.beartype` decorator). Since the whole point of memoization is to cache callable results between calls, any callable accepting any fine-grained parameter unique to each call to that callable is effectively *not* memoizable in any meaningful sense of the adjective "memoizable." Ergo, memoized callables *cannot* raise human-readable exception messages unique to each call to those callables. This substring indirectly solves this issue by inverting the onus of human readability. Rather than requiring memoized callables to raise human-readable exception messages unique to each call to those callables (which we've shown above to be pragmatically infeasible), memoized callables instead raise non-human-readable exception messages containing this substring where they instead would have contained the human-readable portions of their messages unique to each call to those callables. This indirection renders exceptions raised by memoized callables generic between calls and thus safely memoizable. This indirection has the direct consequence, however, of shifting the onus of human readability from those lower-level memoized callables onto higher-level non-memoized callables -- which are then required to explicitly (in order): #. Catch exceptions raised by those lower-level memoized callables. #. Call the :func:`reraise_exception_placeholder` function with those exceptions and desired human-readable substrings. That function then: #. Replaces this magic substring hard-coded into those exception messages with those human-readable substring fragments. #. Reraises the original exceptions in a manner preserving their original tracebacks. Unsurprisingly, as with most inversion of control schemes, this approach is non-intuitive. Surprisingly, however, the resulting code is actually *more* elegant than the standard approach of raising human-readable exceptions from low-level callables. Why? Because the standard approach percolates human-readable substring fragments from the higher-level callables defining those fragments to the lower-level callables raising exception messages containing those fragments. The indirect approach avoids percolation, thus streamlining the implementations of all callables involved. Phew! ''' beartype-0.18.5/beartype/_data/func/000077500000000000000000000000001461113517100172365ustar00rootroot00000000000000beartype-0.18.5/beartype/_data/func/__init__.py000066400000000000000000000000001461113517100213350ustar00rootroot00000000000000beartype-0.18.5/beartype/_data/func/datafunc.py000066400000000000000000000031701461113517100213760ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **callable globals** (i.e., global constants describing various well-known functions and methods). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ SETS }.................... METHOD_NAMES_DUNDER_BINARY = frozenset(( '__add__', '__and__', '__cmp__', '__divmod__', '__div__', '__eq__', '__floordiv__', '__ge__', '__gt__', '__iadd__', '__iand__', '__idiv__', '__ifloordiv__', '__ilshift__', '__imatmul__', '__imod__', '__imul__', '__ior__', '__ipow__', '__irshift__', '__isub__', '__itruediv__', '__ixor__', '__le__', '__lshift__', '__lt__', '__matmul__', '__mod__', '__mul__', '__ne__', '__or__', '__pow__', '__radd__', '__rand__', '__rdiv__', '__rfloordiv__', '__rlshift__', '__rmatmul__', '__rmod__', '__rmul__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__sub__', '__truediv__', '__xor__', )) ''' Frozen set of the unqualified names of all **binary dunder methods** (i.e., methods whose names are both prefixed and suffixed by ``__``, which the active Python interpreter implicitly calls to perform binary operations on instances whose first operands are instances of the classes declaring those methods). ''' beartype-0.18.5/beartype/_data/func/datafuncarg.py000066400000000000000000000047461461113517100221020ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **callable argument metadata** (i.e., global magic constants describing arguments accepted by various functions and methods). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... # ....................{ NAMES ~ return }.................... ARG_NAME_RETURN = 'return' ''' Unique name arbitrarily assigned by Python to the key of the ``__annotations__`` dunder attribute providing the type hint annotating the return of callables. Note that Python itself prohibits callable parameters from being named ``"return"`` and thus guarantees this name to be safe and unambiguous. ''' ARG_NAME_RETURN_REPR = repr(ARG_NAME_RETURN) ''' Object representation of the magic string implying a return value in various Python objects (e.g., the ``__annotations__`` dunder dictionary of annotated callables). ''' # ....................{ VALUES }.................... ARG_VALUE_UNPASSED = 0xBABECAFE ''' **Unpassed argument value** (i.e., arbitrary magic constant serving as the default value of an optional parameter accepted by a callable). This constant is intentionally defined as an arbitrary integer literal compatible with the :pep:`586`-compatible :obj:`typing.Literal` type hint factory, simplifying annotations for optional parameters defaulting to this unpassed argument value: e.g., .. code-block:: python from typing import Literal def muh_func(muh_arg: Literal[True, False, None, ARG_VALUE_UNPASSED] = ( ARG_VALUE_UNPASSED)) -> None: ... Usage of this default value enables a callable to deterministically differentiate between two otherwise indistinguishable cases in call-time semantics: * When a caller explicitly passes that callable that optional parameter as a value that is possibly :data:`None`. In this case, that value is effectively guaranteed to *not* be this arbitrary magic constant. Moreover, since that value is possibly :data:`None`, testing for :data:`None` does *not* suffice to decide whether the caller explicitly passed that value or not. * When a caller does *not* explicitly pass that callable that optional parameter. In this case, the value of that parameter is guaranteed to be this arbitrary magic constant. ''' beartype-0.18.5/beartype/_data/func/datafunccodeobj.py000066400000000000000000000017041461113517100227250ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **code object globals** (i.e., global constants describing code objects of callables, classes, and modules). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ STRINGS }.................... FUNC_CODEOBJ_NAME_MODULE = '' ''' Arbitrary string constant unconditionally assigned to the ``co_name`` instance variables of the code objects of all pure-Python modules (i.e., the top-most lexical scope of each module in the current call stack). This constant enables callers to reliably differentiate between code objects encapsulating: * Module scopes, whose ``co_name`` variable is this constant. * Callable scopes, whose ``co_name`` variable is *not* this constant. ''' beartype-0.18.5/beartype/_data/hint/000077500000000000000000000000001461113517100172455ustar00rootroot00000000000000beartype-0.18.5/beartype/_data/hint/__init__.py000066400000000000000000000000001461113517100213440ustar00rootroot00000000000000beartype-0.18.5/beartype/_data/hint/datahintfactory.py000066400000000000000000000061651461113517100230130ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **type hints** (i.e., PEP-compliant type hints annotating callables and classes declared throughout this codebase, either for compliance with :pep:`561`-compliant static type checkers like :mod:`mypy` or simply for documentation purposes). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... #FIXME: This approach is *PHENOMENAL.* No. Seriously, We could implement a #full-blown "beartype.typing" subpackage (or perhaps even separate "beartyping" #package) extending this core concept to *ALL* type hint factories, enabling #users to trivially annotate with any type hint factory regardless of the #current version of Python or whether "typing_extensions" is installed or not. # ....................{ IMPORTS }.................... from beartype.typing import ( TYPE_CHECKING, ) from beartype._util.hint.utilhintfactory import TypeHintTypeFactory from beartype._util.api.utilapityping import import_typing_attr_or_fallback # ....................{ FACTORIES }.................... # Portably import the PEP 647-compliant "typing.TypeGuard" type hint factory # first introduced by Python >= 3.10, regardless of the current version of # Python and regardless of whether this submodule is currently being subject to # static type-checking or not. Praise be to MIT ML guru and stunning Hypothesis # maintainer @rsokl (Ryan Soklaski) for this brilliant circumvention. \o/ # # Usage of this factory is a high priority. Hinting the return of the # is_bearable() tester with a type guard created by this factory effectively # coerces that tester in an arbitrarily complete type narrower and thus type # parser at static analysis time, substantially reducing complaints from static # type-checkers in end user code deferring to that tester. # # If this submodule is currently being statically type-checked (e.g., mypy), # intentionally import from the third-party "typing_extensions" module rather # than the standard "typing" module. Why? Because doing so eliminates Python # version complaints from static type-checkers (e.g., mypy, pyright). Static # type-checkers could care less whether "typing_extensions" is actually # installed or not; they only care that "typing_extensions" unconditionally # defines this type factory across all Python versions, whereas "typing" only # conditionally defines this type factory under Python >= 3.10. *facepalm* if TYPE_CHECKING: from typing_extensions import TypeGuard as TypeGuard # Else, this submodule is currently being imported at runtime by Python. In this # case, dynamically import this factory from whichever of the standard "typing" # module *OR* the third-party "typing_extensions" module declares this factory, # falling back to the builtin "bool" type if none do. else: TypeGuard = import_typing_attr_or_fallback( 'TypeGuard', TypeHintTypeFactory(bool)) beartype-0.18.5/beartype/_data/hint/datahinttyping.py000066400000000000000000000502321461113517100226500ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **type hints** (i.e., PEP-compliant type hints annotating callables and classes declared throughout this codebase, either for compliance with :pep:`561`-compliant static type checkers like :mod:`mypy` or simply for documentation purposes). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... import beartype # <-- satisfy mypy [note to self: i can't stand you, mypy] from ast import ( AST, AsyncFunctionDef, ClassDef, FunctionDef, ) from beartype.typing import ( AbstractSet, Any, Callable, Dict, ForwardRef, Iterable, List, Literal, Mapping, Optional, Tuple, Type, TypeVar, Union, ) from beartype._cave._cavefast import ( MethodBoundInstanceOrClassType, MethodDecoratorClassType, MethodDecoratorPropertyType, MethodDecoratorStaticType, ) from beartype._data.hint.pep.sign.datapepsigncls import HintSign from beartype._data.func.datafuncarg import ARG_VALUE_UNPASSED from collections.abc import Callable as CallableABC from importlib.abc import PathEntryFinder from pathlib import Path from types import ( CodeType, FrameType, GeneratorType, ) # ....................{ TYPEVARS }.................... S = TypeVar('S') ''' **Unbound type variable** (i.e., matching *any* arbitrary type) locally bound to different types than the :data:`.T` type variable. ''' T = TypeVar('T') ''' **Unbound type variable** (i.e., matching *any* arbitrary type) locally bound to different types than the :data:`.S` type variable. ''' CallableT = TypeVar('CallableT', bound=CallableABC) ''' **Callable type variable** (i.e., bound to match *only* callables). ''' NodeT = TypeVar('NodeT', bound=AST) ''' **Node type variable** (i.e., type variable constrained to match *only* abstract syntax tree (AST) nodes). ''' # ....................{ AST }.................... NodeCallable = Union[FunctionDef, AsyncFunctionDef] ''' PEP-compliant type hint matching a **callable node** (i.e., abstract syntax tree (AST) node encapsulating the definition of a pure-Python function or method that is either synchronous or asynchronous). ''' NodeDecoratable = Union[NodeCallable, ClassDef] ''' PEP-compliant type hint matching a **decoratable node** (i.e., abstract syntax tree (AST) node encapsulating the definition of a pure-Python object supporting decoration by one or more ``"@"``-prefixed decorations, including both pure-Python classes *and* callables). ''' NodeVisitResult = Optional[Union[AST, List[AST]]] ''' PEP-compliant type hint matching a **node visitation result** (i.e., object returned by any visitor method of an :class:`ast.NodeVisitor` subclass). Specifically, this hint matches either: * A single node, in which case a visitor method has effectively preserved the currently visited node passed to that method in the AST. * A list of zero or more nodes, in which case a visitor method has replaced the currently visited node passed to that method with those nodes in the AST. * :data:`None`, in which case a visitor method has effectively destroyed the currently visited node passed to that method from the AST. ''' NodesList = List[AST] ''' PEP-compliant type hint matching an **abstract syntax tree (AST) node list** (i.e., list of zero or more AST nodes). ''' # ....................{ BOOL }.................... BoolTristate = Literal[True, False, None] ''' PEP-compliant type hint matching a **tri-state boolean** whose value may be either: * :data:`True`. * :data:`False`. * :data:`None`, implying that the actual value of this boolean is contextually dependent on context-sensitive program state. ''' BoolTristateUnpassable = Literal[True, False, None, ARG_VALUE_UNPASSED] # type: ignore[valid-type] ''' PEP-compliant type hint matching an **unpassable tri-state boolean** whose value may be either: * :data:`True`. * :data:`False`. * :data:`None`, implying that the actual value of this boolean is contextually dependent on context-sensitive program state. * :data:`.ARG_VALUE_UNPASSED`, enabling any callable that annotates a tri-state boolean parameter by this type hint to deterministically identify whether the caller explicitly passed that parameter or not. Since the caller may explicitly pass :data:`None` as a valid value, testing that parameter against :data:`None` does *not* suffice to decide this decision problem. ''' # ....................{ CALLABLE ~ early }.................... # Callable-specific type hints required by subsequent type hints below. CallableAny = Callable[..., Any] ''' PEP-compliant type hint matching any callable in a manner explicitly matching all possible callable signatures. ''' # ....................{ TYPEVAR ~ early }.................... # Type variables required by subsequent type hints below. BeartypeableT = TypeVar( 'BeartypeableT', # The @beartype decorator decorates objects that are either... bound=Union[ # An arbitrary class *OR*... type, # An arbitrary callable *OR*... CallableAny, # A C-based unbound class method descriptor (i.e., a pure-Python unbound # function decorated by the builtin @classmethod decorator) *OR*... MethodDecoratorClassType, # A C-based unbound property method descriptor (i.e., a pure-Python # unbound function decorated by the builtin @property decorator) *OR*... MethodDecoratorPropertyType, # A C-based unbound static method descriptor (i.e., a pure-Python # unbound function decorated by the builtin @staticmethod decorator). MethodDecoratorStaticType, #FIXME: Currently unused, but preserved for posterity. # # A C-based bound method descriptor (i.e., a pure-Python unbound # # function bound to an object instance on Python's instantiation of that # # object) *OR*... # MethodBoundInstanceOrClassType, ], ) ''' :pep:`484`-compliant **generic beartypeable type variable** (i.e., type hint matching any arbitrary object decoratable by the :func:`beartype.beartype` decorator, including pure-Python callables and classes, C-based descriptors, and even more exotic objects). This type variable notifies static analysis performed by both static type checkers (e.g., :mod:`mypy`) and type-aware IDEs (e.g., VSCode) that the :func:`beartype.beartype` decorator preserves: * Callable signatures by creating and returning callables with the same signatures as passed callables. * Class hierarchies by preserving passed classes with respect to inheritance, including metaclasses and method-resolution orders (MRO) of those classes. ''' # ....................{ CALLABLE }.................... # Callable-specific type hints *NOT* required by subsequent type hints below. CallableRaiser = Callable[[object], None] ''' PEP-compliant type hint matching a **raiser callable** (i.e., arbitrary callable accepting a single arbitrary object and either raising an exception or emitting a warning rather than returning any value). ''' CallableRaiserOrTester = Callable[[object], Optional[bool]] ''' PEP-compliant type hint matching a **raiser or tester callable** (i.e., arbitrary callable accepting a single arbitrary object and either returning no value or returning either :data:`True` if that object satisfies an arbitrary constraint *or* :data:`False` otherwise). ''' CallableTester = Callable[[object], bool] ''' PEP-compliant type hint matching a **tester callable** (i.e., arbitrary callable accepting a single arbitrary object and returning either :data:`True` if that object satisfies an arbitrary constraint *or* :data:`False` otherwise). ''' Codeobjable = Union[Callable, CodeType, FrameType, GeneratorType] ''' PEP-compliant type hint matching a **codeobjable** (i.e., pure-Python object directly associated with a code object and thus safely passable as the first parameter to the :func:`beartype._util.func.utilfunccodeobj.get_func_codeobj` getter retrieving the code object associated with this codeobjable). Specifically, this hint matches: * Code objects. * Pure-Python callables, including generators (but *not* C-based callables, which lack code objects). * Pure-Python callable stack frames. ''' MethodDescriptorNondata = Union[ # A C-based unbound class method descriptor (i.e., a pure-Python unbound # function decorated by the builtin @classmethod decorator) *OR*... MethodDecoratorClassType, # A C-based unbound static method descriptor (i.e., a pure-Python # unbound function decorated by the builtin @staticmethod decorator). MethodDecoratorStaticType, # A C-based bound method descriptor (i.e., a pure-Python unbound # function bound to an object instance on Python's instantiation of that # object) *OR*... MethodBoundInstanceOrClassType, ] ''' PEP-compliant type hint matching any **builtin method non-data descriptor** (i.e., C-based descriptor builtin to Python defining only the ``__get__()`` dunder method, encapsulating read-only access to some kind of method). ''' # ....................{ CALLABLE ~ args }.................... CallableMethodGetitemArg = Union[int, slice] ''' PEP-compliant type hint matching the standard type of the single positional argument accepted by the ``__getitem__` dunder method. ''' # ....................{ CALLABLE ~ decor }.................... BeartypeConfedDecorator = Callable[[BeartypeableT], BeartypeableT] ''' PEP-compliant type hint matching a **configured beartype decorator** (i.e., closure created and returned from the :func:`beartype.beartype` decorator when passed a beartype configuration via the optional ``conf`` parameter rather than an arbitrary object to be decorated via the optional ``obj`` parameter). ''' BeartypeReturn = Union[BeartypeableT, BeartypeConfedDecorator] ''' PEP-compliant type hint matching any possible value returned by any invocation of the :func:`beartype.beartype` decorator, including calls to that decorator in both configuration and decoration modes. ''' # ....................{ DICT }.................... HintSignTrie = Dict[str, Union[HintSign, 'HintSignTrie']] ''' PEP-compliant type hint matching a **sign trie** (i.e., dictionary-of-dictionaries tree data structure enabling efficient mapping from the machine-readable representations of type hints created by an arbitrary number of type hint factories defined by an external third-party package to their identifying sign). ''' # ....................{ DICT ~ any }.................... DictStrToAny = Dict[str, Any] ''' PEP-compliant type hint matching a mapping whose keys are *all* strings. ''' HintAnnotations = DictStrToAny ''' PEP-compliant type hint matching **annotations** (i.e., dictionary mapping from the name of each annotated parameter or return of a callable or annotated variable of a class to the type hint annotating that parameter, return, or variable). ''' MappingStrToAny = Mapping[str, object] ''' PEP-compliant type hint matching a mapping whose keys are *all* strings. ''' # ....................{ CODE }.................... LexicalScope = DictStrToAny ''' PEP-compliant type hint matching a **lexical scope** (i.e., dictionary mapping from the relative unqualified name to value of each locally or globally scoped attribute accessible to a callable or class). ''' CodeGenerated = Tuple[str, LexicalScope, Tuple[str, ...]] ''' PEP-compliant type hint matching **generated code** (i.e., a tuple containing a Python code snippet dynamically generated on-the-fly by a :mod:`beartype`-specific code generator and metadata describing that code). Specifically, this hint matches a 3-tuple ``(func_wrapper_code, func_wrapper_scope, hint_refs_type_basename)``, where: * ``func_wrapper_code`` is a Python code snippet type-checking an arbitrary object against this hint. For the common case of code generated for a :func:`beartype.beartype`-decorated callable, this snippet type-checks a previously localized parameter or return value against this hint. * ``func_wrapper_scope`` is the **local scope** (i.e., dictionary mapping from the name to value of each attribute referenced one or more times in this code) of the body of the function embedding this code. * ``hint_refs_type_basename`` is a tuple of the unqualified classnames of :pep:`484`-compliant relative forward references visitable from this hint (e.g., ``('MuhClass', 'YoClass')`` given the hint ``Union['MuhClass', List['YoClass']]``). ''' # ....................{ ITERABLE }.................... IterableStrs = Iterable[str] ''' PEP-compliant type hint matching *any* iterable of zero or more strings. ''' # ....................{ PATH }.................... CommandWords = IterableStrs ''' PEP-compliant type hint matching **command words** (i.e., an iterable of one or more shell words comprising a shell command, suitable for passing as the ``command_words`` parameter accepted by most callables declared in the test-specific :mod:`beartype_test._util.command.pytcmdrun` submodule). ''' # ....................{ TUPLE }.................... TupleTypes = Tuple[type, ...] ''' PEP-compliant type hint matching a tuple of zero or more classes. Equivalently, this hint matches all tuples passable as the second parameters to the :func:`isinstance` and :func:`issubclass` builtins. ''' TypeOrTupleTypes = Union[type, TupleTypes] ''' PEP-compliant type hint matching either a single class *or* a tuple of zero or more classes. Equivalently, this hint matches all objects passable as the second parameters to the :func:`isinstance` and :func:`issubclass` builtins. ''' SetOrTupleTypes = Union[AbstractSet[type], TupleTypes] ''' PEP-compliant type hint matching a set *or* tuple of zero or more classes. ''' # ....................{ TUPLE ~ stack }.................... TypeStack = Optional[Tuple[type, ...]] ''' PEP-compliant type hint matching a **type stack** (i.e., either tuple of zero or more arbitrary types *or* :data:`None`). Objects matched by this hint are guaranteed to be either: * If the **beartypeable** (i.e., object currently being decorated by the :func:`beartype.beartype` decorator) is an attribute (e.g., method, nested class) of a class currently being decorated by that decorator, the **type stack** (i.e., tuple of one or more lexically nested classes that are either currently being decorated *or* have already been decorated by this decorator in descending order of top- to bottom-most lexically nested) such that: * The first item of this tuple is expected to be the **root decorated class** (i.e., module-scoped class initially decorated by this decorator whose lexical scope encloses this beartypeable). * The last item of this tuple is expected to be the **current decorated class** (i.e., possibly nested class currently being decorated by this decorator). * Else, this beartypeable was decorated directly by this decorator. In this case, :data:`None`. Parameters annotated by this hint typically default to :data:`None`. Note that :func:`beartype.beartype` requires *both* the root and currently decorated class to correctly resolve edge cases under :pep:`563`: e.g., .. code-block:: python from __future__ import annotations from beartype import beartype @beartype class Outer(object): class Inner(object): # At this time, the "Outer" class has been fully defined but is *NOT* # yet accessible as a module-scoped attribute. Ergo, the *ONLY* means # of exposing the "Outer" class to the recursive decoration of this # get_outer() method is to explicitly pass the "Outer" class as the # "cls_root" parameter to all decoration calls. def get_outer(self) -> Outer: return Outer() Note also that nested classes have *no* implicit access to either their parent classes *or* to class variables declared by those parent classes. Nested classes *only* have explicit access to module-scoped classes -- exactly like any other arbitrary objects: e.g., .. code-block:: python class Outer(object): my_str = str class Inner(object): # This induces a fatal compile-time exception resembling: # NameError: name 'my_str' is not defined def get_str(self) -> my_str: return 'Oh, Gods.' Ergo, the *only* owning class of interest to :mod:`beartype` is the root owning class containing other nested classes; *all* of those other nested classes are semantically and syntactically irrelevant. Nonetheless, this tuple intentionally preserves *all* of those other nested classes. Why? Because :pep:`563` resolution can only find the parent callable lexically containing that nested class hierarchy on the current call stack (if any) by leveraging the total number of classes lexically nesting the currently decorated class as input metadata, as trivially provided by the length of this tuple. ''' # ....................{ MODULE ~ beartype }.................... BeartypeForwardRef = Type[ 'beartype._check.forward.reference.fwdrefabc.BeartypeForwardRefABC'] # pyright: ignore ''' PEP-compliant type hint matching a **forward reference proxy** (i.e., concrete subclass of the abstract :class:`beartype._check.forward.reference.fwdrefabc.BeartypeForwardRefABC` superclass). ''' BeartypeForwardRefArgs = Tuple[Optional[str], str, TupleTypes] ''' PEP-compliant type hint matching a **forward reference proxy argument list** (i.e., tuple of all parameters passed to each call of the low-level private :func:`beartype._check.forward.reference.fwdrefmake._make_forwardref_subtype` factory function, in the same order as positionally accepted by that function). ''' # ....................{ MODULE ~ importlib }.................... # Type hints specific to the standard "importlib" package. ImportPathHook = Callable[[str], PathEntryFinder] ''' PEP-compliant type hint matching an **import path hook** (i.e., factory closure creating and returning a new :class:`importlib.abc.PathEntryFinder` instance creating and leveraging a new :class:`importlib.machinery.FileLoader` instance). ''' # ....................{ MODULE ~ pathlib }.................... # Type hints specific to the standard "pathlib" package. PathnameLike = Union[str, Path] ''' PEP-compliant type hint matching a **pathname-like object** (i.e., either a low-level string possibly signifying a pathname *or* a high-level :class:`Path` instance definitely encapsulating a pathname). ''' PathnameLikeTuple = (str, Path) ''' 2-tuple of the types of all **pathname-like objects** (i.e., either low-level strings possibly signifying pathnames *or* high-level :class:`Path` instances definitely encapsulating pathnames). ''' # ....................{ PEP 484 }.................... # Type hints required to fully comply with PEP 484. Pep484TowerComplex = Union[complex, float, int] ''' :pep:`484`-compliant type hint matching the **implicit complex tower** (i.e., complex numbers, floating-point numbers, and integers). ''' Pep484TowerFloat = Union[float, int] ''' :pep:`484`-compliant type hint matching the **implicit floating-point tower** (i.e., both floating-point numbers and integers). ''' # ....................{ PEP (484|585) }.................... # Type hints required to fully comply with both PEP 484 *AND* 585. Pep484585ForwardRef = Union[str, ForwardRef] ''' Union of all :pep:`484`- or :pep:`585`-compliant **forward reference types** (i.e., classes of all forward reference objects). See Also -------- :data:`HINT_PEP484585_FORWARDREF_TYPES` Further details. ''' # ....................{ TYPE }.................... TypeException = Type[Exception] ''' PEP-compliant type hint matching *any* exception class. ''' TypeWarning = Type[Warning] ''' PEP-compliant type hint matching *any* warning category. ''' beartype-0.18.5/beartype/_data/hint/pep/000077500000000000000000000000001461113517100200315ustar00rootroot00000000000000beartype-0.18.5/beartype/_data/hint/pep/__init__.py000066400000000000000000000000001461113517100221300ustar00rootroot00000000000000beartype-0.18.5/beartype/_data/hint/pep/datapeprepr.py000066400000000000000000001032311461113517100227120ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **bare PEP-compliant type hint representations** (i.e., global constants pertaining to machine-readable strings returned by the :func:`repr` builtin suffixed by *no* "["- and "]"-delimited subscription representations). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import ( Dict, FrozenSet, Set, # Union, ) from beartype._data.hint.datahinttyping import HintSignTrie from beartype._data.hint.pep.sign import datapepsigns from beartype._data.hint.pep.sign.datapepsigncls import HintSign from beartype._data.hint.pep.sign.datapepsigns import ( HintSignAbstractSet, # HintSignAnnotated, # HintSignAny, HintSignAsyncContextManager, HintSignAsyncIterable, HintSignAsyncIterator, HintSignAsyncGenerator, HintSignAwaitable, HintSignBinaryIO, HintSignByteString, HintSignCallable, HintSignChainMap, # HintSignClassVar, HintSignCollection, # HintSignConcatenate, HintSignContainer, HintSignCoroutine, HintSignContextManager, HintSignCounter, HintSignDefaultDict, HintSignDeque, HintSignDict, # HintSignFinal, HintSignForwardRef, HintSignFrozenSet, HintSignGenerator, # HintSignGeneric, # HintSignHashable, HintSignIO, HintSignItemsView, HintSignIterable, HintSignIterator, HintSignKeysView, HintSignList, # HintSignLiteral, HintSignMapping, HintSignMappingView, HintSignMatch, HintSignMutableMapping, HintSignMutableSequence, HintSignMutableSet, # HintSignNamedTuple, HintSignNewType, HintSignNone, HintSignNumpyArray, # HintSignOptional, HintSignOrderedDict, HintSignPanderaAny, HintSignParamSpec, # HintSignParamSpecArgs, HintSignPep557DataclassInitVar, HintSignTypeAlias, HintSignPep695TypeAlias, # HintSignProtocol, HintSignReversible, HintSignSequence, HintSignSet, # HintSignSized, HintSignPattern, HintSignTextIO, HintSignTuple, HintSignType, HintSignTypeVar, # HintSignTypedDict, HintSignUnion, HintSignValuesView, ) from beartype._util.py.utilpyversion import ( IS_PYTHON_AT_LEAST_3_9, IS_PYTHON_AT_MOST_3_8, ) # ....................{ MAPPINGS ~ repr }.................... # The majority of this dictionary is initialized with automated inspection below # in the _init() function. The *ONLY* key-value pairs explicitly defined here # are those *NOT* amenable to such inspection. HINT_REPR_PREFIX_ARGS_0_OR_MORE_TO_SIGN: Dict[str, HintSign] = { # ..................{ PEP 484 }.................. # All other PEP 484-compliant representation prefixes are defined by # automated inspection below. # PEP 484-compliant "None" singleton, which transparently reduces to # "types.NoneType". While not explicitly defined by the "typing" module, # PEP 484 explicitly supports this singleton: # When used in a type hint, the expression None is considered equivalent # to type(None). # # Note that the representation of the type of the "None" singleton (i.e., # "") is intentionally omitted here despite the "None" # singleton reducing to that type. Indeed, the *ONLY* reason we detect this # singleton at all is to enable that reduction. Although this singleton # conveys a PEP-compliant semantic, the type of this singleton explicitly # conveys *NO* PEP-compliant semantics. That type is simply a standard # isinstanceable type (like any other). Indeed, attempting to erroneously # associate the type of the "None" singleton with the same sign here would # cause that type to be detected as conveying sign-specific PEP-compliant # semantics rather than *NO* such semantics, which would then substantially # break and complicate dynamic code generation for no benefit whatsoever. 'None': HintSignNone, #FIXME: Almost certain that these should be detected instead via the #slightly more efficient and elegant "HINT_TYPE_NAME_TO_SIGN" global below. # PEP 484-compliant abstract base classes (ABCs) requiring non-standard and # non-trivial type-checking. Although most types are trivially type-checked # by the isinstance() builtin, these types break the mold in various ways. "": HintSignBinaryIO, "": HintSignIO, "": HintSignTextIO, } ''' Dictionary mapping from the **possibly unsubscripted PEP-compliant type hint representation prefix** (i.e., unsubscripted prefix of the machine-readable strings returned by the :func:`repr` builtin for PEP-compliant type hints permissible in both subscripted and unsubscripted forms) of each hint uniquely identifiable by that representation to its identifying sign. Notably, this dictionary maps from the representation prefixes of: * *All* :pep:`484`-compliant type hints. Whereas *all* :pep:`585`-compliant type hints (e.g., ``list[str]``) are necessarily subscripted and thus omitted from this dictionary, *all* :pep:`484`-compliant type hints support at least unsubscripted form and most :pep:`484`-compliant type hints support subscription as well. Moreover, the unsubscripted forms of most :pep:`484`-compliant type hints convey deep semantics and thus require detection as PEP-compliant (e.g., :obj:`typing.List`, requiring detection and reduction to :class:`list`). ''' # The majority of this dictionary is defined by explicit key-value pairs here. HINT_REPR_PREFIX_ARGS_1_OR_MORE_TO_SIGN: Dict[str, HintSign] = { # ..................{ PEP 585 }.................. # PEP 585-compliant type hints *MUST* by definition be subscripted (e.g., # "list[str]" rather than "list"). While the stdlib types underlying those # hints are isinstanceable classes and thus also permissible as type hints # when unsubscripted (e.g., simply "list"), unsubscripted classes convey no # deep semantics and thus need *NOT* be detected as PEP-compliant. # # For maintainability, these key-value pairs are intentionally listed in the # same order as the official list in PEP 585 itself. 'tuple': HintSignTuple, 'list': HintSignList, 'dict': HintSignDict, 'set': HintSignSet, 'frozenset': HintSignFrozenSet, 'type': HintSignType, 'collections.deque': HintSignDeque, 'collections.defaultdict': HintSignDefaultDict, 'collections.OrderedDict': HintSignOrderedDict, 'collections.Counter': HintSignCounter, 'collections.ChainMap': HintSignChainMap, 'collections.abc.Awaitable': HintSignAwaitable, 'collections.abc.Coroutine': HintSignCoroutine, 'collections.abc.AsyncIterable': HintSignAsyncIterable, 'collections.abc.AsyncIterator': HintSignAsyncIterator, 'collections.abc.AsyncGenerator': HintSignAsyncGenerator, 'collections.abc.Iterable': HintSignIterable, 'collections.abc.Iterator': HintSignIterator, 'collections.abc.Generator': HintSignGenerator, 'collections.abc.Reversible': HintSignReversible, 'collections.abc.Container': HintSignContainer, 'collections.abc.Collection': HintSignCollection, 'collections.abc.Callable': HintSignCallable, 'collections.abc.Set': HintSignAbstractSet, 'collections.abc.MutableSet': HintSignMutableSet, 'collections.abc.Mapping': HintSignMapping, 'collections.abc.MutableMapping': HintSignMutableMapping, 'collections.abc.Sequence': HintSignSequence, 'collections.abc.MutableSequence': HintSignMutableSequence, 'collections.abc.ByteString': HintSignByteString, 'collections.abc.MappingView': HintSignMappingView, 'collections.abc.KeysView': HintSignKeysView, 'collections.abc.ItemsView': HintSignItemsView, 'collections.abc.ValuesView': HintSignValuesView, 'contextlib.AbstractContextManager': HintSignContextManager, 'contextlib.AbstractAsyncContextManager': HintSignAsyncContextManager, 're.Pattern': HintSignPattern, 're.Match': HintSignMatch, # ..................{ NON-PEP ~ lib : numpy }.................. # The PEP-noncompliant "numpy.typing.NDArray" type hint is permissible in # both subscripted and unsubscripted forms. In the latter case, this hint # is implicitly subscripted by generic type variables. In both cases, this # hint presents a uniformly reliable representation -- dramatically # simplifying detection via a common prefix of that representation here: # >>> import numpy as np # >>> import numpy.typing as npt # >>> repr(npt.NDArray) # numpy.ndarray[typing.Any, numpy.dtype[+ScalarType]] # >>> repr(npt.NDArray[np.float64]) # repr: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]] # # Ergo, unsubscripted "numpy.typing.NDArray" type hints present themselves # as implicitly subscripted through their representation. 'numpy.ndarray': HintSignNumpyArray, } ''' Dictionary mapping from the **necessarily subscripted PEP-compliant type hint representation prefixes** (i.e., unsubscripted prefix of the machine-readable strings returned by the :func:`repr` builtin for subscripted PEP-compliant type hints) of all hints uniquely identifiable by those representations to their identifying signs. Notably, this dictionary maps from the representation prefixes of: * All :pep:`585`-compliant type hints. Whereas all :pep:`484`-compliant type hints support both subscripted and unsubscripted forms (e.g., ``typing.List``, ``typing.List[str]``), all :pep:`585`-compliant type hints necessarily require subscription. While the stdlib types underlying :pep:`585`-compliant type hints are isinstanceable classes and thus also permissible as type hints when unsubscripted (e.g., simply :class:`list`), isinstanceable classes convey *no* deep semantics and thus need *not* be detected as PEP-compliant. ''' # ....................{ MAPPINGS ~ repr : trie }.................... # The majority of this trie is defined by explicit key-value pairs here. HINT_REPR_PREFIX_TRIE_ARGS_0_OR_MORE_TO_SIGN: HintSignTrie = { # ..................{ NON-PEP ~ lib : pandera }.................. # All PEP-noncompliant "pandera.typing" type hints are permissible in # both subscripted and unsubscripted forms. 'pandera': { 'typing': HintSignPanderaAny, } } ''' **Sign trie** (i.e., dictionary-of-dictionaries tree data structure enabling efficient mapping from the machine-readable representations of type hints created by an arbitrary number of type hint factories defined by an external third-party package to their identifying sign) from the **possibly unsubscripted PEP-compliant type hint representation prefix** (i.e., unsubscripted prefix of the machine-readable strings returned by the :func:`repr` builtin for PEP-compliant type hints permissible in both subscripted and unsubscripted forms) of each hint uniquely identifiable by that representation to its identifying sign. ''' # ....................{ MAPPINGS ~ type }.................... # The majority of this dictionary is initialized with automated inspection # below in the _init() function. The *ONLY* key-value pairs explicitly defined # here are those *NOT* amenable to such inspection. HINT_TYPE_NAME_TO_SIGN: Dict[str, HintSign] = { # ..................{ PEP 484 }.................. # PEP 484-compliant forward reference type hints may be annotated either: # * Explicitly as "typing.ForwardRef" instances, which automated inspection # performed by the _init() function below already handles. # * Implicitly as strings, which this key-value pair here detects. Note # this unconditionally matches *ALL* strings, including both: # * Invalid Python identifiers (e.g., "0d@yw@r3z"). # * Absolute forward references (i.e., fully-qualified classnames) # technically non-compliant with PEP 484 but seemingly compliant with # PEP 585. # Since the distinction between PEP-compliant and -noncompliant forward # references is murky at best and since unconditionally matching *ALL* # string as PEP-compliant substantially simplifies logic throughout the # codebase, we (currently) opt to do so. 'builtins.str': HintSignForwardRef, # Python >= 3.10 implements PEP 484-compliant "typing.NewType" type hints as # instances of that class. Regardless of the current Python version, # "typing_extensions.NewType" type hints remain implemented in manner of # Python < 3.10 -- which is to say, as closures of that function. Ergo, we # intentionally omit "typing_extensions.NewType" here. See also: # https://github.com/python/typing/blob/master/typing_extensions/src_py3/typing_extensions.py 'typing.NewType': HintSignNewType, # ..................{ PEP 557 }.................. # Python >= 3.8 implements PEP 557-compliant "dataclasses.InitVar" type # hints as instances of that class. 'dataclasses.InitVar': HintSignPep557DataclassInitVar, # ..................{ PEP 604 }.................. # PEP 604-compliant |-style unions (e.g., "int | float") are internally # implemented as instances of the low-level C-based "types.UnionType" type. # Thankfully, these unions are semantically interchangeable with comparable # PEP 484-compliant unions (e.g., "typing.Union[int, float]"); both kinds # expose equivalent dunder attributes (e.g., "__args__", "__parameters__"), # enabling subsequent code generation to conflate the two without issue. 'types.UnionType': HintSignUnion, # ..................{ PEP 612 }.................. # Python >= 3.10 implements PEP 612-compliant "typing.ParamSpec" type hints # as instances of that class. 'typing.ParamSpec': HintSignParamSpec, # ..................{ PEP 695 }.................. # Python >= 3.12 implements PEP 695-compliant "type" aliases as instances of # the low-level C-based "typing.TypeAliasType" type. 'typing.TypeAliasType': HintSignPep695TypeAlias, } ''' Dictionary mapping from the fully-qualified classnames of all PEP-compliant type hints uniquely identifiable by those classnames to their identifying signs. ''' # ....................{ SETS ~ deprecated }.................... # Initialized with automated inspection below in the _init() function. HINTS_PEP484_REPR_PREFIX_DEPRECATED: FrozenSet[str] = set() # type: ignore[assignment] ''' Frozen set of all **bare deprecated** :pep:`484`-compliant **type hint representations** (i.e., machine-readable strings returned by the :func:`repr` builtin suffixed by *no* "["- and "]"-delimited subscription representations for all :pep:`484`-compliant type hints obsoleted by :pep:`585`-compliant subscriptable classes). ''' # ....................{ SETS ~ ignorable }.................... # The majority of this dictionary is initialized with automated inspection # below in the _init() function. The *ONLY* key-value pairs explicitly defined # here are those *NOT* amenable to such inspection. HINTS_REPR_IGNORABLE_SHALLOW: FrozenSet[str] = { # type: ignore[assignment] #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Synchronize changes to this set with the corresponding # testing-specific set # "beartype_test.a00_unit.data.hint.pep.data_pep.HINTS_PEP_IGNORABLE_SHALLOW". #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ..................{ NON-PEP }.................. # The PEP-noncompliant builtin "object" type is the transitive superclass # of all classes. Ergo, parameters and return values annotated as "object" # unconditionally match *ALL* objects under isinstance()-based type # covariance and thus semantically reduce to unannotated parameters and # return values. This is literally the "beartype.cave.AnyType" type. "", # ..................{ PEP 604 }.................. # The low-level C-based "types.UnionType" class underlying PEP 604-compliant # |-style unions (e.g., "int | float") imposes no constraints and is thus # also semantically synonymous with the ignorable PEP-noncompliant # "beartype.cave.AnyType" and hence "object" types. Nonetheless, this class # *CANNOT* be instantiated from Python code: # >>> import types # >>> types.UnionType(int, bool) # TypeError: cannot create 'types.UnionType' instances # # Likewise, this class *CANNOT* be subscripted. It follows that there exists # no meaningful equivalent of shallow type-checking for these unions. While # trivially feasible, listing "" here would only # prevent callers from meaningfully type-checking these unions passed as # valid parameters or returned as valid returns: e.g., # @beartype # def muh_union_printer(muh_union: UnionType) -> None: print(muh_union) # # Ergo, we intentionally omit this type from consideration here. # ....................{ NON-PEP }.................... # Machine-readable representations of shallowly ignorable type hints # published by PEP-noncompliant third-party type hints, including... } ''' Frozen set of all **shallowly ignorable PEP-compliant type hint representations** (i.e., machine-readable strings returned by the :func:`repr` builtin for all PEP-compliant type hints that are unconditionally ignorable by the :func:`beartype.beartype` decorator in *all* possible contexts) Caveats ---------- **The high-level** :func:`beartype._util.hint.pep.utilhinttest.is_hint_ignorable` **tester function should always be called in lieu of testing type hints against this low-level set.** This set is merely shallow and thus excludes **deeply ignorable type hints** (e.g., :data:`Union[Any, bool, str]`). Since there exist a countably infinite number of deeply ignorable type hints, this set is necessarily constrained to the substantially smaller finite subset of only shallowly ignorable type hints. ''' # ....................{ INITIALIZERS }.................... def _init() -> None: ''' Initialize this submodule. ''' # ..................{ EXTERNALS }.................. # Defer initialization-specific imports. from beartype._data.module.datamodtyping import TYPING_MODULE_NAMES # Permit redefinition of these globals below. global \ HINTS_PEP484_REPR_PREFIX_DEPRECATED, \ HINTS_REPR_IGNORABLE_SHALLOW # ..................{ HINTS }.................. # Length of the ignorable substring prefixing the name of each sign. _HINT_SIGN_PREFIX_LEN = len('HintSign') # ..................{ HINTS ~ repr }.................. # Dictionary mapping from the unqualified names of typing attributes whose # names are erroneously desynchronized from their bare machine-readable # representations to the actual representations of those attributes. # # The unqualified names and representations of *MOST* typing attributes are # rigorously synchronized. However, those two strings are desynchronized # for a proper subset of Python versions and typing attributes: # $ ipython3.8 # >>> import typing # >>> repr(typing.List[str]) # typing.List[str] # <-- this is good # >>> repr(typing.ContextManager[str]) # typing.AbstractContextManager[str] # <-- this is pants # # This dictionary enables subsequent logic to transparently resynchronize # the unqualified names and representations of pants typing attributes. _HINT_TYPING_ATTR_NAME_TO_REPR_PREFIX: Dict[str, str] = {} # If the active Python interpreter targets Python >= 3.7.x <= 3.8.x (i.e., # either Python 3.7 or 3.8), resynchronize the unqualified names and # representations of desynchronized typing attributes. Bizarrely: # * Python 3.7.0 first desynchronized these attributes, despite the # otherwise insane Python 3.6.x series having actually gotten this right. # * Python 3.8.x preserved this bad behaviour. # * Python 3.9.0 rectified this issue finally. *sigh* if IS_PYTHON_AT_MOST_3_8: _HINT_TYPING_ATTR_NAME_TO_REPR_PREFIX.update({ 'AsyncContextManager': 'AbstractAsyncContextManager', 'ContextManager': 'AbstractContextManager', }) # ..................{ HINTS ~ types }.................. # Dictionary mapping from the unqualified names of all classes defined by # typing modules used to instantiate PEP-compliant type hints to their # corresponding signs. _HINT_TYPE_BASENAMES_TO_SIGN = { # ................{ PEP 484 }................ # All PEP 484-compliant forward references are necessarily instances of # the same class. 'ForwardRef' : HintSignForwardRef, # All PEP 484-compliant type variables are necessarily instances of the # same class. 'TypeVar': HintSignTypeVar, #FIXME: "Generic" is ignorable when unsubscripted. Excise this up! # The unsubscripted PEP 484-compliant "Generic" superclass is # explicitly equivalent under PEP 484 to the "Generic[Any]" # subscription and thus slightly conveys meaningful semantics. # 'Generic': HintSignGeneric, } # ..................{ HINTS ~ deprecated }.................. # Set of the unqualified names of all deprecated PEP 484-compliant typing # attributes. _HINT_PEP484_TYPING_ATTR_NAMES_DEPRECATED: Set[str] = set() # If the active Python interpreter targets Python >= 3.9 and thus # supports PEP 585, add the names of all deprecated PEP 484-compliant # typing attributes (e.g., "typing.List") that have since been obsoleted by # equivalent bare PEP 585-compliant builtin classes (e.g., "list"). if IS_PYTHON_AT_LEAST_3_9: _HINT_PEP484_TYPING_ATTR_NAMES_DEPRECATED.update(( # ..............{ PEP 484 }.............. 'AbstractSet', 'AsyncContextManager', 'AsyncGenerator', 'AsyncIterable', 'AsyncIterator', 'Awaitable', 'ByteString', 'Callable', 'ChainMap', 'Collection', 'Container', 'ContextManager', 'Coroutine', 'Counter', 'DefaultDict', 'Deque', 'Dict', 'FrozenSet', 'Generator', 'Hashable', 'ItemsView', 'Iterable', 'Iterator', 'KeysView', 'List', 'MappingView', 'Mapping', 'Match', 'MutableMapping', 'MutableSequence', 'MutableSet', 'OrderedDict', 'Pattern', 'Reversible', 'Sequence', 'Set', 'Sized', 'Tuple', 'Type', 'ValuesView', )) # ..................{ HINTS ~ ignorable }.................. # Set of the unqualified names of all shallowly ignorable typing non-class # attributes. Since classes and non-class attributes have incommensurate # machine-readable representations, these two types of attributes *MUST* be # isolated to distinct sets. See "_HINT_TYPING_TYPE_NAMES_IGNORABLE" below. _HINT_TYPING_ATTR_NAMES_IGNORABLE = { # ................{ PEP 484 }................ # The "Any" singleton is semantically synonymous with the ignorable # PEP-noncompliant "beartype.cave.AnyType" and hence "object" types. 'Any', # The unsubscripted "Optional" singleton semantically expands to the # implicit "Optional[Any]" singleton by the same argument. Since PEP # 484 also stipulates that all "Optional[t]" singletons semantically # expand to "Union[t, type(None)]" singletons for arbitrary arguments # "t", "Optional[Any]" semantically expands to merely "Union[Any, # type(None)]". Since all unions subscripted by "Any" semantically # reduce to merely "Any", the "Optional" singleton also reduces to # merely "Any". # # This intentionally excludes "Optional[type(None)]", which the # "typing" module physically reduces to merely "type(None)". *shrug* 'Optional', # The unsubscripted "Union" singleton semantically expands to the # implicit "Union[Any]" singleton by the same argument. Since PEP 484 # stipulates that a union of one type semantically reduces to only that # type, "Union[Any]" semantically reduces to merely "Any". Despite # their semantic equivalency, however, these objects remain # syntactically distinct with respect to object identification: e.g., # >>> Union is not Union[Any] # True # >>> Union is not Any # True # # This intentionally excludes: # # * The "Union[Any]" and "Union[object]" singletons, since the "typing" # module physically reduces: # * "Union[Any]" to merely "Any" (i.e., "Union[Any] is Any"), which # this frozen set already contains. # * "Union[object]" to merely "object" (i.e., "Union[object] is # object"), which this frozen set also already contains. # * "Union" singleton subscripted by one or more ignorable type hints # contained in this set (e.g., "Union[Any, bool, str]"). Since there # exist a countably infinite number of these subscriptions, these # subscriptions *CANNOT* be explicitly listed in this set. Instead, # these subscriptions are dynamically detected by the high-level # beartype._util.hint.pep.utilhinttest.is_hint_ignorable() tester # function and thus referred to as deeply ignorable type hints. 'Union', } # Set of the unqualified names of all shallowly ignorable typing classes. _HINT_TYPING_TYPE_NAMES_IGNORABLE = { # ................{ PEP 484 }................ # The "Generic" superclass imposes no constraints and is thus also # semantically synonymous with the "object" superclass. Since PEP # 484 stipulates that *ANY* unsubscripted subscriptable PEP-compliant # singleton including "typing.Generic" semantically expands to that # singleton subscripted by an implicit "Any" argument, "Generic" # semantically expands to the implicit "Generic[Any]" singleton. 'Generic', # ................{ PEP 544 }................ # Note that ignoring the "typing.Protocol" superclass is vital here. For # unknown and presumably uninteresting reasons, *ALL* possible objects # satisfy this superclass. Ergo, this superclass is synonymous with the # "object" root superclass: e.g., # >>> import typing as t # >>> isinstance(object(), t.Protocol) # True # >>> isinstance('wtfbro', t.Protocol) # True # >>> isinstance(0x696969, t.Protocol) # True 'Protocol', } # ..................{ CONSTRUCTION }.................. # For the fully-qualified name of each quasi-standard typing module... for typing_module_name in TYPING_MODULE_NAMES: # For the name of each sign... # # Note that: # * The inspect.getmembers() getter could also be called here. However, # that getter internally defers to dir() and getattr() with a # considerable increase in runtime complexity for no tangible benefit. # * The "__dict__" dunder attribute should *NEVER* be accessed directly # on a module, as that attribute commonly contains artificial entries # *NOT* explicitly declared by that module (e.g., "__", "e__", # "ns__"). for hint_sign_name in dir(datapepsigns): # If this name is *NOT* prefixed by the substring prefixing the # names of all signs, this name is *NOT* the name of a sign. In this # case, silently continue to the next sign. if not hint_sign_name.startswith('HintSign'): continue # Else, this name is that of a sign. # Sign with this name. hint_sign = getattr(datapepsigns, hint_sign_name) # Unqualified name of the typing attribute identified by this sign. typing_attr_name = hint_sign_name[_HINT_SIGN_PREFIX_LEN:] assert typing_attr_name, f'{hint_sign_name} not sign name.' # Substring prefixing the machine-readable representation of this # attribute, conditionally defined as either: # * If this name is erroneously desynchronized from this # representation under the active Python interpreter, the actual # representation of this attribute under this interpreter (e.g., # "AbstractContextManager" for the "typing.ContextManager" hint). # * Else, this name is correctly synchronized with this # representation under the active Python interpreter. In this # case, fallback to this name as is (e.g., "List" for the # "typing.List" hint). hint_repr_prefix = _HINT_TYPING_ATTR_NAME_TO_REPR_PREFIX.get( typing_attr_name, typing_attr_name) #FIXME: It'd be great to eventually generalize this to support #aliases from one unwanted sign to another wanted sign. Perhaps #something resembling: ## In global scope above: #_HINT_SIGN_REPLACE_SOURCE_BY_TARGET = { # HintSignProtocol: HintSignGeneric, #} # # # In this iteration here: # ... # hint_sign_replaced = _HINT_SIGN_REPLACE_SOURCE_BY_TARGET.get( # hint_sign, hint_sign) # # # Map from that attribute in this module to this sign. # # print(f'[datapeprepr] Mapping repr("{typing_module_name}.{hint_repr_prefix}[...]") -> {repr(hint_sign)}...') # HINT_REPR_PREFIX_ARGS_0_OR_MORE_TO_SIGN[ # f'{typing_module_name}.{hint_repr_prefix}'] = hint_sign_replaced # Map from that attribute in this module to this sign. # print(f'[datapeprepr] Mapping repr("{typing_module_name}.{hint_repr_prefix}[...]") -> {repr(hint_sign)}...') HINT_REPR_PREFIX_ARGS_0_OR_MORE_TO_SIGN[ f'{typing_module_name}.{hint_repr_prefix}'] = hint_sign # For the unqualified classname identifying each sign to that sign... for typing_attr_name, hint_sign in _HINT_TYPE_BASENAMES_TO_SIGN.items(): # Map from that classname in this module to this sign. # print(f'[datapeprepr] Mapping type "{typing_module_name}.{typing_attr_name}" -> {repr(hint_sign)}...') HINT_TYPE_NAME_TO_SIGN[ f'{typing_module_name}.{typing_attr_name}'] = hint_sign # For each shallowly ignorable typing non-class attribute name... for typing_attr_name in _HINT_TYPING_ATTR_NAMES_IGNORABLE: # Add that attribute relative to this module to this set. # print(f'[datapeprepr] Registering ignorable non-class "{typing_module_name}.{typing_attr_name}"...') HINTS_REPR_IGNORABLE_SHALLOW.add( # type: ignore[attr-defined] f'{typing_module_name}.{typing_attr_name}') # For each shallowly ignorable typing classname... for typing_type_name in _HINT_TYPING_TYPE_NAMES_IGNORABLE: # Add that classname relative to this module to this set. # print(f'[datapeprepr] Registering ignorable class "{typing_module_name}.{typing_attr_name}"...') HINTS_REPR_IGNORABLE_SHALLOW.add( # type: ignore[attr-defined] f"") # For each deprecated PEP 484-compliant typing attribute name... for typing_attr_name in _HINT_PEP484_TYPING_ATTR_NAMES_DEPRECATED: # Add that attribute relative to this module to this set. # print(f'[datapeprepr] Registering deprecated "{typing_module_name}.{typing_attr_name}"...') HINTS_PEP484_REPR_PREFIX_DEPRECATED.add( # type: ignore[attr-defined] f'{typing_module_name}.{typing_attr_name}') # ..................{ SYNTHESIS }.................. # Freeze all relevant global sets for safety. HINTS_PEP484_REPR_PREFIX_DEPRECATED = frozenset( HINTS_PEP484_REPR_PREFIX_DEPRECATED) HINTS_REPR_IGNORABLE_SHALLOW = frozenset(HINTS_REPR_IGNORABLE_SHALLOW) # ..................{ DEBUGGING }.................. # Uncomment as needed to display the contents of these objects. # from pprint import pformat # print(f'HINTS_PEP484_REPR_PREFIX_DEPRECATED: {pformat(HINTS_PEP484_REPR_PREFIX_DEPRECATED)}') # print(f'HINT_REPR_PREFIX_ARGS_0_OR_MORE_TO_SIGN: {pformat(HINT_REPR_PREFIX_ARGS_0_OR_MORE_TO_SIGN)}') # print(f'HINT_REPR_PREFIX_ARGS_1_OR_MORE_TO_SIGN: {pformat(HINT_REPR_PREFIX_ARGS_1_OR_MORE_TO_SIGN)}') # print(f'HINT_TYPE_NAME_TO_SIGN: {pformat(HINT_TYPE_NAME_TO_SIGN)}') # Initialize this submodule. _init() beartype-0.18.5/beartype/_data/hint/pep/sign/000077500000000000000000000000001461113517100207715ustar00rootroot00000000000000beartype-0.18.5/beartype/_data/hint/pep/sign/__init__.py000066400000000000000000000000001461113517100230700ustar00rootroot00000000000000beartype-0.18.5/beartype/_data/hint/pep/sign/datapepsigncls.py000066400000000000000000000054261461113517100243530ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **sign classes** (i.e., classes whose instances uniquely identifying PEP-compliant type hints in a safe, non-deprecated manner regardless of the Python version targeted by the active Python interpreter). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import Union # ....................{ CLASSES }.................... class HintSign(object): ''' **Sign** (i.e., object uniquely identifying PEP-compliant type hints in a safe, non-deprecated manner regardless of the Python version targeted by the active Python interpreter). Attributes ---------- name : str Uniqualified name of the :mod:`typing` attribute uniquely identified by this sign (e.g., ``Literal`` for :pep:`586`-compliant type hints). ''' # ..................{ CLASS VARIABLES }.................. # Slot all instance variables defined on this object to minimize the time # complexity of both reading and writing variables across frequently # called @beartype decorations. Slotting has been shown to reduce read and # write costs by approximately ~10%, which is non-trivial. __slots__ = ('name',) # ..................{ DUNDERS }.................. def __init__(self, name: str) -> None: ''' Initialize this sign. Parameters ---------- name : str Uniqualified name of the :mod:`typing` attribute uniquely identified by this sign (e.g., ``Literal`` for :pep:`586`-compliant type hints). ''' assert isinstance(name, str), f'{repr(name)} not string.' # Classify all passed parameters. self.name = name def __repr__(self) -> str: ''' Machine-readable representation of this sign. ''' return f"HintSign('{self.name}')" def __str__(self) -> str: ''' Human-readable stringification of this sign. ''' return f'"HintSign{self.name}"' # ....................{ HINTS }.................... HintSignOrType = Union[HintSign, type] ''' PEP-compliant type hint matching either a **sign** (i.e., object uniquely identifying PEP-compliant type hints in a safe, non-deprecated manner regardless of the Python version targeted by the active Python interpreter) or **isinstanceable class** (i.e., class safely passable as the second argument to the :func:`isinstance` builtin). ''' beartype-0.18.5/beartype/_data/hint/pep/sign/datapepsigns.py000066400000000000000000000312711461113517100240310ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **Python version-agnostic signs** (i.e., instances of the :class:`beartype._data.hint.pep.sign.datapepsigncls.HintSign` class uniquely identifying PEP-compliant type hints in a safe, non-deprecated manner regardless of the Python version targeted by the active Python interpreter). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Attributes imported here at module scope *MUST* be explicitly # deleted from this module's namespace below. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype._data.hint.pep.sign.datapepsigncls import HintSign as _HintSign # ....................{ SIGNS ~ explicit }.................... # Signs with explicit analogues in the stdlib "typing" module. # #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Signs defined by this module are synchronized with the "__all__" # list global of the "typing" module bundled with the most recent CPython # release. For that reason, these signs are: # * Intentionally declared in the exact same order prefixed by the exact same # inline comments as for that list global. # * Intentionally *NOT* commented with docstrings, both because: # * These docstrings would all trivially reduce to a single-line sentence # fragment resembling "Alias of typing attribute." # * These docstrings would inhibit diffing and synchronization by inspection. # * Intentionally *NOT* conditionally isolated to the specific range of Python # versions whose "typing" module lists these attributes. For example, the # "HintSignAsyncContextManager" sign identifying the # "typing.AsyncContextManager" attribute that only exists under Python >= # 3.7 could be conditionally isolated to that range of Python versions. # Technically, there exists *NO* impediment to doing so; pragmatically, doing # so would be ineffectual. Why? Because attributes *NOT* defined by the # "typing" module of the active Python interpreter cannot (by definition) be # used to annotate callables decorated by the @beartype decorator. # # When bumping beartype to support a new CPython release: # * Declare one new attribute here for each new "typing" attribute added by # that CPython release regardless of whether beartype explicitly supports # that attribute yet. The subsequently called die_unless_hint_pep_supported() # validator will raise exceptions when passed these attributes. # * Preserve attributes here that have since been removed from the "typing" # module in that CPython release to ensure their continued usability when # running beartype against older CPython releases. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Super-special typing primitives. HintSignAnnotated = _HintSign(name='Annotated') HintSignAny = _HintSign(name='Any') HintSignCallable = _HintSign(name='Callable') HintSignClassVar = _HintSign(name='ClassVar') HintSignConcatenate = _HintSign(name='Concatenate') HintSignFinal = _HintSign(name='Final') HintSignForwardRef = _HintSign(name='ForwardRef') HintSignGeneric = _HintSign(name='Generic') HintSignLiteral = _HintSign(name='Literal') HintSignOptional = _HintSign(name='Optional') HintSignParamSpec = _HintSign(name='ParamSpec') HintSignProtocol = _HintSign(name='Protocol') HintSignTuple = _HintSign(name='Tuple') HintSignType = _HintSign(name='Type') HintSignTypeVar = _HintSign(name='TypeVar') HintSignTypeVarTuple = _HintSign(name='TypeVarTuple') HintSignUnion = _HintSign(name='Union') # ABCs (from collections.abc). HintSignAbstractSet = _HintSign(name='AbstractSet') HintSignByteString = _HintSign(name='ByteString') HintSignContainer = _HintSign(name='Container') HintSignContextManager = _HintSign(name='ContextManager') HintSignHashable = _HintSign(name='Hashable') HintSignItemsView = _HintSign(name='ItemsView') HintSignIterable = _HintSign(name='Iterable') HintSignIterator = _HintSign(name='Iterator') HintSignKeysView = _HintSign(name='KeysView') HintSignMapping = _HintSign(name='Mapping') HintSignMappingView = _HintSign(name='MappingView') HintSignMutableMapping = _HintSign(name='MutableMapping') HintSignMutableSequence = _HintSign(name='MutableSequence') HintSignMutableSet = _HintSign(name='MutableSet') HintSignSequence = _HintSign(name='Sequence') HintSignSized = _HintSign(name='Sized') HintSignValuesView = _HintSign(name='ValuesView') HintSignAwaitable = _HintSign(name='Awaitable') HintSignAsyncIterator = _HintSign(name='Iterator') HintSignAsyncIterable = _HintSign(name='Iterable') HintSignCoroutine = _HintSign(name='Coroutine') HintSignCollection = _HintSign(name='Collection') HintSignAsyncGenerator = _HintSign(name='AsyncGenerator') HintSignAsyncContextManager = _HintSign(name='ContextManager') # Structural checks, a.k.a. protocols. HintSignReversible = _HintSign(name='Reversible') # SupportsAbs <-- not a useful type hint (already an isinstanceable ABC) # SupportsBytes <-- not a useful type hint (already an isinstanceable ABC) # SupportsComplex <-- not a useful type hint (already an isinstanceable ABC) # SupportsFloat <-- not a useful type hint (already an isinstanceable ABC) # SupportsIndex <-- not a useful type hint (already an isinstanceable ABC) # SupportsInt <-- not a useful type hint (already an isinstanceable ABC) # SupportsRound <-- not a useful type hint (already an isinstanceable ABC) # Concrete collection types. HintSignChainMap = _HintSign(name='ChainMap') HintSignCounter = _HintSign(name='Counter') HintSignDeque = _HintSign(name='Deque') HintSignDict = _HintSign(name='Dict') HintSignDefaultDict = _HintSign(name='DefaultDict') HintSignList = _HintSign(name='List') HintSignOrderedDict = _HintSign(name='OrderedDict') HintSignSet = _HintSign(name='Set') HintSignFrozenSet = _HintSign(name='FrozenSet') HintSignNamedTuple = _HintSign(name='NamedTuple') HintSignTypedDict = _HintSign(name='TypedDict') HintSignGenerator = _HintSign(name='Generator') # Other concrete types. HintSignMatch = _HintSign(name='Match') HintSignPattern = _HintSign(name='Pattern') # Other concrete type aliases. HintSignIO = HintSignGeneric HintSignBinaryIO = HintSignGeneric HintSignTextIO = HintSignGeneric # One-off things. # AnyStr <-- not a unique type hint (just a constrained "TypeVar") # cast <-- unusable as a type hint # final <-- unusable as a type hint # get_args <-- unusable as a type hint # get_origin <-- unusable as a type hint # get_type_hints <-- unusable as a type hint # is_typeddict <-- unusable as a type hint HintSignLiteralString = _HintSign(name='LiteralString') HintSignNever = _HintSign(name='Never') HintSignNewType = _HintSign(name='NewType') # no_type_check <-- unusable as a type hint # no_type_check_decorator <-- unusable as a type hint # Note that "NoReturn" is contextually valid *ONLY* as a top-level return hint. # Since this use case is extremely limited, we explicitly generate code for this # use case outside of the general-purpose code generation pathway for standard # type hints. Since "NoReturn" is an unsubscriptable singleton, we explicitly # detect this type hint with an identity test and thus require *NO* sign to # uniquely identify this type hint. # # Theoretically, explicitly defining a sign uniquely identifying this type hint # could erroneously encourage us to use that sign elsewhere; we should avoid # that, as "NoReturn" is invalid in almost all possible contexts. Pragmatically, # doing so nonetheless improves orthogonality when detecting and validating # PEP-compliant type hints, which ultimately matters more than our subjective # feelings about the matter. Wisely, we choose pragmatics. # # In short, "NoReturn" is insane. HintSignNoReturn = _HintSign(name='NoReturn') HintSignNotRequired = _HintSign(name='NotRequired') # overload <-- unusable as a type hint HintSignParamSpecArgs = _HintSign(name='ParamSpecArgs') HintSignParamSpecKwargs = _HintSign(name='ParamSpecKwargs') HintSignRequired = _HintSign(name='Required') # runtime_checkable <-- unusable as a type hint HintSignSelf = _HintSign(name='Self') # Text <-- not actually a type hint (literal alias for "str") # TYPE_CHECKING <-- unusable as a type hint HintSignTypeAlias = _HintSign(name='TypeAlias') HintSignTypeGuard = _HintSign(name='TypeGuard') HintSignUnpack = _HintSign(name='Unpack') # Wrapper namespace for re type aliases. # # Note that "typing.__all__" intentionally omits the "Match" and "Pattern" # attributes, which it oddly considers to comprise another namespace. *shrug* # ....................{ SIGNS ~ implicit }.................... # Signs with *NO* explicit analogues in the stdlib "typing" module but # nonetheless standardized by one or more PEPs. HintSignNone = _HintSign(name='None') ''' :pep:`484` explicitly supports the :data:`None` singleton, albeit implicitly: When used in a type hint, the expression None is considered equivalent to type(None). ''' # ....................{ SIGNS ~ implicit : lib }.................... # Signs identifying PEP-noncompliant third-party type hints published by... # # ....................{ SIGNS ~ implicit : lib : numpy }.................... HintSignNumpyArray = _HintSign(name='NumpyArray') # <-- "numpy.typing.NDArray" ''' ...the :mod:`numpy.typing` subpackage. ''' # ....................{ SIGNS ~ implicit : lib : pandera }.................... HintSignPanderaAny = _HintSign(name='PanderaAny') # <-- "pandera.typing.*" ''' ...the :mod:`pandera.typing` subpackage. Specifically, define a single sign unconditionally matching *all* type hints published by the :mod:`pandera.typing` subpackage. Why? Because Pandera insanely publishes its own Pandera-specific PEP-noncompliant runtime type-checking decorator :func:`pandera.check_types` that supports *only* Pandera-specific PEP-noncompliant :mod:`pandera.typing` type hints. Since Pandera users are already accustomed to decorating *all* Pandera-based callables (i.e., callables accepting one or more parameters and/or returning one or more values which are Pandera objects) by :func:`pandera.check_types`, attempting to type-check the same objects already type-checked by that decorator would only inefficiently and needlessly slow :mod:`beartype` down. Ergo, we ignore *all* Pandera type hints by: * Defining this catch-all singleton for Pandera type hints here. * Denoting this singleton to be unconditionally ignorable elsewhere. ''' # ....................{ SIGNS ~ implicit : pep : 557 }.................... # dataclasses.InitVar[...]. HintSignPep557DataclassInitVar = _HintSign(name='Pep557DataclassInitVar') ''' :pep:`557`-compliant :obj:`dataclasses.InitVar` type hint factory, annotating class-scoped variable annotations of :func:`dataclass.dataclass`-decorated data classes. ''' # ....................{ SIGNS ~ implicit : pep : 585 }.................... # os.PathLike[...], weakref.weakref[...], et al. HintSignPep585BuiltinSubscriptedUnknown = _HintSign( name='Pep585BuiltinSubscriptedUnknown') ''' :pep:`585`-compliant C-based :class:`types.GenericAlias` superclass inheritable by PEP-noncompliant pure-Python subclasses in either the standard library or third-party packages, which when subscripted by otherwise PEP-compliant child type hints produce PEP-noncompliant **unrecognized subscripted builtin type hints** (i.e., C-based type hints that are *not* isinstanceable types, instantiated by subscripting pure-Python origin classes unrecognized by :mod:`beartype` and thus PEP-noncompliant). Examples include: * ``os.PathLike[...]`` type hints. * ``weakref.weakref[...]`` type hints. Unsurprisingly, :mod:`beartype` reduces C-based unrecognized subscripted builtin type hints (which are *not* type-checkable as is) to their unsubscripted pure-Python origin classes (which are type-checkable as is). ''' # ....................{ SIGNS ~ implicit : pep : 695 }.................... # "type {alias_name} = {alias_value}" statements. HintSignPep695TypeAlias = _HintSign(name='HintSignPep695TypeAlias') ''' :pep:`695`-compliant C-based :class:`types.TypeAliasType` class of all :pep:`695`-compliant **type aliases** (i.e., objects created as the left-hand sides of statements of the form ``type {alias_name} = {alias_value}``). ''' # ....................{ CLEANUP }.................... # Prevent all attributes imported above from polluting this namespace. Why? # Logic elsewhere subsequently assumes a one-to-one mapping between the # attributes of this namespace and signs. del _HintSign beartype-0.18.5/beartype/_data/hint/pep/sign/datapepsignset.py000066400000000000000000000555221461113517100243670ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **type hint sign sets** (i.e., frozen set globals aggregating instances of the :class:`beartype._data.hint.pep.sign.datapepsigncls.HintSign` class, enabling efficient categorization of signs as belonging to various categories of type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._data.hint.pep.sign.datapepsigns import ( HintSignAbstractSet, HintSignAnnotated, HintSignAny, HintSignAsyncContextManager, HintSignAsyncGenerator, HintSignAsyncIterator, HintSignAsyncIterable, HintSignAwaitable, HintSignBinaryIO, HintSignByteString, HintSignCallable, HintSignChainMap, HintSignCollection, HintSignConcatenate, HintSignContainer, HintSignContextManager, HintSignCoroutine, HintSignCounter, HintSignPep557DataclassInitVar, HintSignDefaultDict, HintSignDeque, HintSignDict, HintSignFinal, HintSignForwardRef, HintSignFrozenSet, HintSignGenerator, HintSignGeneric, HintSignHashable, HintSignIO, HintSignItemsView, HintSignIterable, HintSignIterator, HintSignKeysView, HintSignList, HintSignLiteral, HintSignLiteralString, HintSignMapping, HintSignMappingView, HintSignMatch, HintSignMutableMapping, HintSignMutableSequence, HintSignMutableSet, HintSignNewType, HintSignNumpyArray, HintSignNone, HintSignOptional, HintSignOrderedDict, # HintSignPanderaAny, HintSignParamSpec, HintSignPattern, HintSignPep585BuiltinSubscriptedUnknown, HintSignTypeAlias, HintSignPep695TypeAlias, HintSignProtocol, HintSignReversible, HintSignSelf, HintSignSequence, HintSignSet, HintSignSized, HintSignTextIO, HintSignTuple, HintSignType, HintSignTypedDict, HintSignTypeGuard, HintSignTypeVar, HintSignUnion, HintSignValuesView, ) # ....................{ SETS ~ deprecated }.................... #FIXME: Currently unused but preserved for posterity. *shrug* # HINT_SIGNS_DEPRECATED = frozenset(( # # ..................{ PEP 613 }.................. # # PEP 613-compliant "typing.TypeAlias" type hint singletons have been # # deprecated by PEP 695-compliant type aliases under Python >= 3.12. # HintSignTypeAlias, # )) # ''' # Frozen set of all **deprecated signs** (i.e., arbitrary objects uniquely # identifying PEP-compliant type hints unconditionally obsoleted by equivalent # PEP-compliant type hints standardized by more recently released PEPs). # ''' # ....................{ SIGNS ~ ignorable }.................... HINT_SIGNS_BARE_IGNORABLE = frozenset(( # ..................{ PEP 484 }.................. # The "Any" singleton is semantically synonymous with the ignorable # PEP-noncompliant "beartype.cave.AnyType" and hence "object" types. HintSignAny, # The "Generic" superclass imposes no constraints and is thus also # semantically synonymous with the ignorable PEP-noncompliant # "beartype.cave.AnyType" and hence "object" types. Since PEP # 484 stipulates that *ANY* unsubscripted subscriptable PEP-compliant # singleton including "typing.Generic" semantically expands to that # singelton subscripted by an implicit "Any" argument, "Generic" # semantically expands to the implicit "Generic[Any]" singleton. HintSignGeneric, # The unsubscripted "Optional" singleton semantically expands to the # implicit "Optional[Any]" singleton by the same argument. Since PEP # 484 also stipulates that all "Optional[t]" singletons semantically expand # to "Union[t, type(None)]" singletons for arbitrary arguments "t", # "Optional[Any]" semantically expands to merely "Union[Any, # type(None)]". Since all unions subscripted by "Any" semantically # reduce to merely "Any", the "Optional" singleton also reduces to # merely "Any". # # This intentionally excludes "Optional[type(None)]", which the "typing" # module physically reduces to merely "type(None)". *shrug* HintSignOptional, # The unsubscripted "Union" singleton semantically expands to the implicit # "Union[Any]" singleton by the same argument. Since PEP 484 stipulates that # a union of one type semantically reduces to only that type, "Union[Any]" # semantically reduces to merely "Any". Despite their semantic equivalency, # however, these objects remain syntactically distinct with respect to # object identification: e.g., # >>> Union is not Union[Any] # True # >>> Union is not Any # True # # This intentionally excludes: # # * The "Union[Any]" and "Union[object]" singletons, since the "typing" # module physically reduces: # * "Union[Any]" to merely "Any" (i.e., "Union[Any] is Any"), which # this frozen set already contains. # * "Union[object]" to merely "object" (i.e., "Union[object] is # object"), which this frozen set also already contains. # * "Union" singleton subscripted by one or more ignorable type hints # contained in this set (e.g., "Union[Any, bool, str]"). Since there exist # a countably infinite number of these subscriptions, these subscriptions # *CANNOT* be explicitly listed in this set. Instead, these subscriptions # are dynamically detected by the high-level # beartype._util.hint.pep.utilhinttest.is_hint_ignorable() tester function # and thus referred to as deeply ignorable type hints. HintSignUnion, # ..................{ PEP 544 }.................. # Note that ignoring the "typing.Protocol" superclass is vital here. For # unknown and presumably uninteresting reasons, *ALL* possible objects # satisfy this superclass. Ergo, this superclass is synonymous with the # "object" root superclass: e.g., # >>> import typing as t # >>> isinstance(object(), t.Protocol) # True # >>> isinstance('wtfbro', t.Protocol) # True # >>> isinstance(0x696969, t.Protocol) # True HintSignProtocol, )) ''' Frozen set of all **bare ignorable signs** (i.e., arbitrary objects uniquely identifying unsubscripted type hints that are unconditionally ignorable by the :func:`beartype.beartype` decorator). ''' # ....................{ SETS ~ kind }.................... HINT_SIGNS_CALLABLE_PARAMS = frozenset(( # ..................{ PEP 612 }.................. HintSignConcatenate, HintSignParamSpec, )) ''' Frozen set of all **callable argument signs** (i.e., arbitrary objects uniquely identifying PEP-compliant child type hints typing the argument lists of parent :class:`collections.abc.Callable` type hints). This set necessarily excludes: * **Standard callable argument lists** (e.g., ``Callable[[bool, int], str]``), which are specified as standard lists and thus identified by *no* signs. * **Ellipsis callable argument lists** (e.g., ``Callable[..., str]``), which are specified as the ellipsis singleton and thus identified by *no* signs. ''' HINT_SIGNS_MAPPING = frozenset(( # ..................{ PEP (484|585) }.................. HintSignDefaultDict, HintSignDict, HintSignMapping, HintSignMutableMapping, HintSignOrderedDict, )) ''' Frozen set of all **standard mapping signs** (i.e., arbitrary objects uniquely identifying :pep:`484`- and :pep:`585`-compliant type hints subscripted by exactly two child type hints constraining *all* key-value pairs of compliant mappings, which necessarily satisfy the :class:`collections.abc.Mapping` protocol with guaranteed :math:`O(1)` indexation of at least the first key-value pair). ''' HINT_SIGNS_SEQUENCE_ARGS_1 = frozenset(( # ..................{ PEP (484|585) }.................. HintSignByteString, HintSignList, HintSignMutableSequence, HintSignSequence, )) ''' Frozen set of all **standard sequence signs** (i.e., arbitrary objects uniquely identifying :pep:`484`- and :pep:`585`-compliant type hints subscripted by exactly one child type hint constraining *all* items of compliant sequences, which necessarily satisfy the :class:`collections.abc.Sequence` protocol with guaranteed :math:`O(1)` indexation across all sequence items). This set intentionally excludes the: * :obj:`typing.AnyStr` sign, which accepts only the :class:`str` and :class:`bytes` types as its sole subscripted argument, which does *not* unconditionally constrain *all* items (i.e., unencoded and encoded characters respectively) of compliant sequences but instead parametrizes this attribute. * :obj:`typing.ByteString` sign, which accepts *no* subscripted arguments. :obj:`typing.ByteString` is simply an alias for the :class:`collections.abc.ByteString` abstract base class (ABC) and thus already handled by our fallback logic for supported PEP-compliant type hints. * :obj:`typing.Deque` sign, whose compliant objects (i.e., :class:`collections.deque` instances) only `guarantee O(n) indexation across all sequence items `__: Indexed access is ``O(1)`` at both ends but slows to ``O(n)`` in the middle. For fast random access, use lists instead. * :obj:`typing.NamedTuple` sign, which embeds a variadic number of PEP-compliant field type hints and thus requires special-cased handling. * :obj:`typing.Text` sign, which accepts *no* subscripted arguments. :obj:`typing.Text` is simply an alias for the builtin :class:`str` type and thus handled elsewhere as a PEP-noncompliant type hint. * :obj:`typing.Tuple` sign, which accepts a variadic number of subscripted arguments and thus requires special-cased handling. .. _collections.deque: https://docs.python.org/3/library/collections.html#collections.deque ''' HINT_SIGNS_UNION = frozenset(( # ..................{ PEP 484 }.................. HintSignOptional, HintSignUnion, )) ''' Frozen set of all **union signs** (i.e., arbitrary objects uniquely identifying :pep:`484`- and :pep:`604`-compliant type hints unifying one or more subscripted type hint arguments into a disjunctive set union of these arguments). If the active Python interpreter targets: * Python >= 3.9, the :obj:`typing.Optional` and :obj:`typing.Union` attributes are distinct. * Python < 3.9, the :obj:`typing.Optional` attribute reduces to the :obj:`typing.Union` attribute, in which case this set is technically semantically redundant. Since tests of both object identity and set membership are :math:`O(1)`, this set incurs no significant performance penalty versus direct usage of the :obj:`typing.Union` attribute and is thus unconditionally used as is irrespective of Python version. ''' # ....................{ SIGNS ~ origin }.................... HINT_SIGNS_ORIGIN_ISINSTANCEABLE = frozenset(( # ..................{ PEP (484|585) }.................. HintSignAbstractSet, HintSignAsyncContextManager, HintSignAsyncGenerator, HintSignAsyncIterable, HintSignAsyncIterator, HintSignAwaitable, HintSignByteString, HintSignCallable, HintSignChainMap, HintSignCollection, HintSignContainer, HintSignContextManager, HintSignCoroutine, HintSignCounter, HintSignDefaultDict, HintSignDeque, HintSignDict, HintSignFrozenSet, HintSignGenerator, HintSignHashable, HintSignItemsView, HintSignIterable, HintSignIterator, HintSignKeysView, HintSignList, HintSignMapping, HintSignMappingView, HintSignMatch, HintSignMutableMapping, HintSignMutableSequence, HintSignMutableSet, HintSignOrderedDict, HintSignPattern, HintSignReversible, HintSignSequence, HintSignSet, HintSignSized, HintSignTuple, HintSignType, HintSignValuesView, # ..................{ NON-PEP }.................. HintSignPep585BuiltinSubscriptedUnknown, )) ''' Frozen set of all signs uniquely identifying PEP-compliant type hints originating from an **isinstanceable origin type** (i.e., isinstanceable class such that *all* objects satisfying this hint are instances of this class). All hints identified by signs in this set are guaranteed to define ``__origin__`` dunder instance variables whose values are the standard origin types they originate from. Since any object is trivially type-checkable against such a type by passing that object and type to the :func:`isinstance` builtin, *all* objects annotated by hints identified by signs in this set are at least shallowly type-checkable from wrapper functions generated by the :func:`beartype.beartype` decorator. ''' # ....................{ SIGNS ~ origin : args }.................... HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_1 = frozenset(( HintSignAbstractSet, HintSignAsyncContextManager, HintSignAsyncIterable, HintSignAsyncIterator, HintSignAwaitable, HintSignCollection, HintSignContainer, HintSignContextManager, HintSignCounter, HintSignDeque, HintSignFrozenSet, HintSignIterable, HintSignIterator, HintSignKeysView, HintSignList, HintSignMatch, HintSignMappingView, HintSignMutableSequence, HintSignMutableSet, HintSignPattern, HintSignReversible, HintSignSequence, HintSignSet, HintSignType, HintSignValuesView, )) ''' Frozen set of all signs uniquely identifying **single-argument PEP-compliant type hints** (i.e., type hints subscriptable by only one child type hint) originating from an **isinstanceable origin type** (i.e., isinstanceable class such that *all* objects satisfying this hint are instances of this class). Note that the corresponding types in the typing module will have an ``_nparams`` instance variable with a value equal to 1. ''' HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_2 = frozenset(( HintSignAsyncGenerator, # HintSignCallable, # defined explicitly below HintSignChainMap, HintSignDefaultDict, HintSignDict, HintSignItemsView, HintSignMapping, HintSignMutableMapping, HintSignOrderedDict, )) ''' Frozen set of all signs uniquely identifying **two-argument PEP-compliant type hints** (i.e., type hints subscriptable by exactly two child type hints) Note that the corresponding types in the typing module will have an ``_nparams`` instance variable with a value equal to 2. ''' HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_3 = frozenset(( HintSignCoroutine, HintSignGenerator, )) ''' Frozen set of all signs uniquely identifying **three-argument PEP-compliant type hints** (i.e., type hints subscriptable by exactly three child type hints) Note that the corresponding types in the typing module will have an ``_nparams`` instance variable with a value equal to 3. ''' # ....................{ SIGNS ~ return }.................... HINT_SIGNS_RETURN_GENERATOR_ASYNC = frozenset(( # ..................{ PEP (484|585) }.................. HintSignAsyncGenerator, HintSignAsyncIterable, HintSignAsyncIterator, )) ''' Frozen set of all signs uniquely identifying **PEP-compliant asynchronous generator return type hints** (i.e., hints permissible as the return annotations of asynchronous generators). See Also -------- :data:`.HINT_SIGNS_RETURN_GENERATOR_SYNC` Further discussion. ''' HINT_SIGNS_RETURN_GENERATOR_SYNC = frozenset(( # ..................{ PEP (484|585) }.................. HintSignGenerator, HintSignIterable, HintSignIterator, )) ''' Frozen set of all signs uniquely identifying **PEP-compliant synchronous generator return type hints** (i.e., hints permissible as the return annotations of synchronous generators). Generator callables are simply syntactic sugar for non-generator callables returning generator objects. For this reason, generator callables *must* be annotated as returning a type compatible with generator objects -- including: * :data:`HintSignGenerator`, the narrowest abstract base class (ABC) to which all generator objects necessarily conform. * :data:`HintSignIterator`, the immediate superclass of :data:`HintSignGenerator`. * :data:`HintSignIterable`, the immediate superclass of :data:`HintSignIterator`. Technically, :pep:`484` states that generator callables may only be annotated as only returning a subscription of the :obj:`typing.Generator` factory: The return type of generator functions can be annotated by the generic type ``Generator[yield_type, send_type, return_type]`` provided by ``typing.py`` module: Pragmatically, official documentation for the :mod:`typing` module seemingly *never* standardized by an existing PEP additionally states that generator callables may be annotated as also returning a subscription of either the :obj:`typing.Iterable` or :obj:`typing.Iterator` factories: Alternatively, annotate your generator as having a return type of either ``Iterable[YieldType]`` or ``Iterator[YieldType]``: See Also -------- https://github.com/beartype/beartype/issues/65#issuecomment-954468111 Further discussion. ''' # ....................{ SIGNS ~ type }.................... HINT_SIGNS_TYPE_MIMIC = frozenset(( # ..................{ PEP 484 }.................. HintSignNewType, # ..................{ PEP 593 }.................. HintSignAnnotated, )) ''' Frozen set of all signs uniquely identifying **PEP-compliant type hint mimics** (i.e., hints maliciously masquerading as another type by explicitly overriding their ``__module__`` dunder instance variable to that of that type). Notably, this set contains the signs of: * :pep:`484`-compliant :obj:`typing.NewType` type hints under Python >= 3.10, which badly masquerade as their first passed argument to such an extreme degree that they even intentionally prefix their machine-readable representation by the fully-qualified name of the caller's module: e.g., .. code-block:: python # Under Python >= 3.10: >>> import typing >>> new_type = typing.NewType('List', bool) >>> repr(new_type) __main__.List # <---- this is genuine bollocks * :pep:`593`-compliant :obj:`typing.Annotated` type hints, which badly masquerade as their first subscripted argument (e.g., the :class:`int` in ``typing.Annotated[int, 63]``) such that the value of the ``__module__`` attributes of these hints is that of that argument rather than their own. Oddly, their machine-readable representation remains prefixed by ``"typing."``, enabling an efficient test that also generalizes to all other outlier edge cases that are probably lurking about. I have no code and I must scream. ''' # ....................{ SETS ~ supported }.................... _HINT_SIGNS_SUPPORTED_SHALLOW = frozenset(( # ..................{ PEP 484 }.................. HintSignTypeVar, # ..................{ PEP 589 }.................. #FIXME: Shift into "HINT_SIGNS_SUPPORTED_DEEP" *AFTER* deeply type-checking #typed dictionaries. HintSignTypedDict, # ..................{ PEP 591 }.................. HintSignFinal, # ..................{ PEP 613 }.................. HintSignTypeAlias, # ..................{ PEP 647 }.................. HintSignTypeGuard, # ..................{ PEP 673 }.................. HintSignSelf, # ..................{ PEP 675 }.................. HintSignLiteralString, # ..................{ PEP 695 }.................. HintSignPep695TypeAlias, )) ''' Frozen set of all **shallowly supported non-originative signs** (i.e., arbitrary objects uniquely identifying PEP-compliant type hints *not* originating from an isinstanceable type for which the :func:`beartype.beartype` decorator generates shallow type-checking code). ''' HINT_SIGNS_SUPPORTED_DEEP = ( HINT_SIGNS_MAPPING | HINT_SIGNS_SEQUENCE_ARGS_1 | frozenset(( # ..................{ PEP 484 }.................. # Note that the "NoReturn" type hint is invalid in almost all possible # syntactic contexts and thus intentionally omitted here. See the # "datapepsigns" submodule for further commentary. HintSignAny, HintSignBinaryIO, HintSignForwardRef, HintSignIO, HintSignNewType, HintSignNone, HintSignTextIO, # Note that "typing.Union" implicitly subsumes "typing.Optional" *ONLY* # under Python <= 3.9. The implementations of the "typing" module under # those older Python versions transparently reduced "typing.Optional" to # "typing.Union" at runtime. Since this reduction is no longer the case, # both *MUST* now be explicitly listed here. HintSignOptional, HintSignUnion, # ..................{ PEP (484|585) }.................. HintSignGeneric, HintSignTuple, HintSignType, # ..................{ PEP 544 }.................. HintSignProtocol, # ..................{ PEP 557 }.................. HintSignPep557DataclassInitVar, # ..................{ PEP 586 }.................. HintSignLiteral, # ..................{ PEP 593 }.................. HintSignAnnotated, # ..................{ NON-PEP ~ package : numpy }.................. #FIXME: This should probably be in "HINT_SIGNS_SUPPORTED_SHALLOW", instead. HintSignNumpyArray, ))) ''' Frozen set of all **deeply supported signs** (i.e., arbitrary objects uniquely identifying PEP-compliant type hints for which the :func:`beartype.beartype` decorator generates deeply type-checking code). This set contains *every* sign explicitly supported by one or more conditional branches in the body of the :func:`beartype._check.code.codemake.make_func_pith_code` function generating code deeply type-checking the current pith against the PEP-compliant type hint annotated by a subscription of that attribute. ''' HINT_SIGNS_SUPPORTED = frozenset(( # Set of all deeply supported signs. HINT_SIGNS_SUPPORTED_DEEP | # Set of all shallowly supported signs *NOT* originating from a class. _HINT_SIGNS_SUPPORTED_SHALLOW | # Set of all shallowly supported signs originating from a class. HINT_SIGNS_ORIGIN_ISINSTANCEABLE )) ''' Frozen set of all **supported signs** (i.e., arbitrary objects uniquely identifying PEP-compliant type hints). ''' beartype-0.18.5/beartype/_data/kind/000077500000000000000000000000001461113517100172305ustar00rootroot00000000000000beartype-0.18.5/beartype/_data/kind/__init__.py000066400000000000000000000000001461113517100213270ustar00rootroot00000000000000beartype-0.18.5/beartype/_data/kind/datakinddict.py000066400000000000000000000025141461113517100222270ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **mapping singletons** (i.e., dictionaries commonly required throughout this codebase, reducing space and time consumption by preallocating widely used dictionary-centric objects). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import ( Any, Dict, ) # ....................{ DICTS }.................... # Note that this exact type annotation is required to avoid mypy complaints. :O DICT_EMPTY: Dict[Any, Any] = {} ''' **Empty dictionary singleton.** Whereas Python guarantees the **empty tuple** (i.e., ``()``) to be a singleton, Python does *not* extend that guarantee to dictionaries. This empty dictionary singleton amends that oversight, providing efficient reuse of empty dictionaries: e.g., .. code-block:: >>> () is () True # <-- good. this is good. >>> {} is {} False # <-- bad. this is bad. >>> from beartype._data.kind.datakinddict import DICT_EMPTY >>> DICT_EMPTY is DICT_EMPTY True # <-- good. this is good, because we made it so. ''' beartype-0.18.5/beartype/_data/kind/datakindsequence.py000066400000000000000000000030441461113517100231130ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **sequence singletons** (i.e., lists and tuples commonly required throughout this codebase, reducing space and time consumption by preallocating widely used set-centric objects). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import ( Any, List, Tuple, ) # ....................{ LISTS }.................... # Note that this exact type annotation is required to avoid mypy complaints. :O LIST_EMPTY: List[Any] = [] ''' **Empty list singleton.** ''' # ....................{ TUPLES }.................... # Note that this exact type annotation is required to avoid mypy complaints. :O TUPLE_EMPTY: Tuple[Any, ...] = () ''' **Empty tuple singleton.** Yes, we know exactly what you're thinking: "Why would anyone do this, @leycec? Why not just directly access the empty tuple singleton as ()?" Because Python insanely requires us to do this under Python >= 3.8 to detect empty tuples: .. code-block:: bash $ python3.7 >>> () is () True # <-- yes, this is good $ python3.8 >>> () is () SyntaxWarning: "is" with a literal. Did you mean "=="? # <-- WUT >>> TUPLE_EMPTY = () >>> TUPLE_EMPTY is TUPLE_EMPTY True # <-- *FACEPALM* ''' beartype-0.18.5/beartype/_data/kind/datakindset.py000066400000000000000000000026001461113517100220730ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **set singletons** (i.e., sets and frozen sets commonly required throughout this codebase, reducing space and time consumption by preallocating widely used set-centric objects). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import ( Any, FrozenSet, ) # ....................{ SETS }.................... # Note that this exact type annotation is required to avoid mypy complaints. :O FROZENSET_EMPTY: FrozenSet[Any] = frozenset() ''' **Empty frozen set singleton.** Whereas Python guarantees the **empty tuple** (i.e., ``()``) to be a singleton, Python does *not* extend that guarantee to frozen sets. This empty frozen set singleton amends that oversight, providing efficient reuse of empty frozen sets: e.g., .. code-block:: pycon >>> () is () True # <-- good. this is good. >>> frozenset() is frozenset() False # <-- bad. this is bad. >>> from beartype._data.kind.datakindset import FROZENSET_EMPTY >>> FROZENSET_EMPTY is FROZENSET_EMPTY True # <-- good. this is good, because we made it so. ''' beartype-0.18.5/beartype/_data/kind/datakindtext.py000066400000000000000000000021001461113517100222570ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **string singletons** (i.e., strings and data structures of strings commonly required throughout this codebase, reducing space and time consumption by preallocating widely used string-centric objects). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from string import punctuation # ....................{ SETS ~ punctuation }.................... CHARS_PUNCTUATION = frozenset(punctuation) ''' Frozen set of all **ASCII punctuation characters** (i.e., non-Unicode characters satisfying the conventional definition of English punctuation). Note that the :attr:`string.punctuation` object is actually an inefficient string of these characters rather than an efficient collection. Ergo, this set should *ALWAYS* be accessed in lieu of that string. ''' beartype-0.18.5/beartype/_data/module/000077500000000000000000000000001461113517100175705ustar00rootroot00000000000000beartype-0.18.5/beartype/_data/module/__init__.py000066400000000000000000000000001461113517100216670ustar00rootroot00000000000000beartype-0.18.5/beartype/_data/module/datamodcontextlib.py000066400000000000000000000060471461113517100236560ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :mod:`contextlib` **globals** (i.e., global constants describing the standard :mod:`contextlib` module bundled with CPython's standard library). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import Iterator from beartype._util.func.utilfunccodeobj import get_func_codeobj_basename from contextlib import contextmanager # ....................{ STRINGS }.................... @contextmanager def _noop_context_manager() -> Iterator[None]: ''' Arbitrary :func:`contextlib.contextmanager`-based context manager defined solely to inspect various dunder attributes common to all such managers. ''' yield CONTEXTLIB_CONTEXTMANAGER_CODEOBJ_NAME = get_func_codeobj_basename( _noop_context_manager) ''' Fully-qualified name of the code object underlying the isomorphic decorator closure created and returned by the :func:`contextlib.contextmanager` decorator. This name enables functionality elsewhere to reliably detect when a function has been decorated by that decorator. This is critical, as the type of *all* objects created and returned by :func:`contextlib.contextmanager`-based context managers is a private class of the :mod:`contextlib` module rather than the types implied by the type hints originally annotating the returns of those context managers. If :mod:`beartype` did *not* actively detect and intervene in this edge case, then runtime type-checkers dynamically generated by :mod:`beartype` for those managers would erroneously raise type-checking violations after calling those managers and detecting a seeming type violation: e.g., .. code-block:: python >>> from beartype.typing import Iterator >>> from contextlib import contextmanager >>> @contextmanager ... def _noop_context_manager() -> Iterator[None]: yield >>> type(_noop_context_manager()) # <-- not an "Iterator", bro >>> _noop_context_manager.__qualname__ _noop_context_manager # <-- that looks sane... but *IS* it? >>> _noop_context_manager.__code__.co_qualname contextmanager..helper # <-- So. The truth is revealed at last. As the above example demonstrates, the ``__qualname__`` dunder attribute of the isomorphic decorator closure created and returned by the :func:`contextlib.contextmanager` decorator publicly lies about its identity by masquerading as the decorated generator factory function. Only the secretive ``__code__.co_qualname`` dunder attribute of that closure tells the truth. ''' # print(f'CONTEXTLIB_CONTEXTMANAGER_CODEOBJ_NAME: {CONTEXTLIB_CONTEXTMANAGER_CODEOBJ_NAME}') # Delete this context manager now that we no longer require it as a negligible # safety (and possible space complexity) measure. del _noop_context_manager beartype-0.18.5/beartype/_data/module/datamodpy.py000066400000000000000000000016151461113517100221270ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **standard Python module globals** (i.e., global constants describing modules and packages bundled with CPython's standard library). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ NAMES }.................... BUILTINS_MODULE_NAME = 'builtins' ''' Fully-qualified name of the **builtins module** (i.e., objects defined by the standard :mod:`builtins` module and thus globally available by default *without* requiring explicit importation). ''' SCRIPT_MODULE_NAME = '__main__' ''' Fully-qualified name of the **script module** (i.e., arbitrary module name assigned to scripts run outside of a package context). ''' beartype-0.18.5/beartype/_data/module/datamodtyping.py000066400000000000000000000042231461113517100230070ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **typing module globals** (i.e., global constants describing quasi-standard typing modules). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... # ....................{ SETS }.................... TYPING_MODULE_NAMES_STANDARD = frozenset(( # Official typing module bundled with the Python stdlib. 'typing', # Third-party typing compatibility layer bundled with @beartype itself. 'beartype.typing', )) ''' Frozen set of the fully-qualified names of all **standard typing modules** (i.e., modules whose public APIs *exactly* conform to that of the standard :mod:`typing` module). This set includes both the standard :mod:`typing` module and comparatively more standard :mod:`beartype.typing` submodule while excluding the third-party :mod:`typing_extensions` module, whose runtime behaviour often significantly diverges in non-standard fashion from that of the aforementioned modules. ''' TYPING_MODULE_NAMES = TYPING_MODULE_NAMES_STANDARD | frozenset(( # Third-party module backporting "typing" attributes introduced in newer # Python versions to older Python versions. 'typing_extensions', )) ''' Frozen set of the fully-qualified names of all **quasi-standard typing modules** (i.e., modules defining attributes usable for creating PEP-compliant type hints accepted by both static and runtime type checkers). ''' TYPING_MODULE_NAMES_DOTTED = frozenset( f'{typing_module_name}.' for typing_module_name in TYPING_MODULE_NAMES) ''' Frozen set of the fully-qualified ``.``-suffixed names of all typing modules. This set is a negligible optimization enabling callers to perform slightly more efficient testing of string prefixes against items of this specialized set than those of the more general-purpose :data:`TYPING_MODULE_NAMES` set. See Also ---------- :data:`TYPING_MODULE_NAMES` Further details. ''' beartype-0.18.5/beartype/_data/os/000077500000000000000000000000001461113517100167245ustar00rootroot00000000000000beartype-0.18.5/beartype/_data/os/__init__.py000066400000000000000000000000001461113517100210230ustar00rootroot00000000000000beartype-0.18.5/beartype/_data/os/dataosshell.py000066400000000000000000000030311461113517100215760ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **shell singletons** (i.e., magic constants pertaining to the parent shell encapsulating the active Python interpreter, including the names of :mod:`beartype`-specific environment variables officially recognized by :mod:`beartype`). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import ( Dict, Optional, ) # ....................{ VARS ~ conf }.................... # @beartype-specific environment variables configuring beartype configurations # (i.e., "beartype.BeartypeConf" instances). SHELL_VAR_CONF_IS_COLOR_NAME = 'BEARTYPE_IS_COLOR' ''' Name of the **color configuration environment variable** (i.e., :mod:`beartype`-specific environment variable officially recognized by :mod:`beartype` as globally configuring the value of the :attr:`beartype.BeartypeConf.is_color` tri-state boolean). ''' SHELL_VAR_CONF_IS_COLOR_VALUE_TO_OBJ: Dict[str, Optional[bool]] = { 'True': True, 'False': False, 'None': None, } ''' Dictionary mapping from each permissible string value for the **color configuration environment variable** (i.e., whose name is :data:`.CONF_IS_COLOR_NAME`) to the corresponding value of the :attr:`beartype.BeartypeConf.is_color` tri-state boolean. ''' beartype-0.18.5/beartype/_decor/000077500000000000000000000000001461113517100164665ustar00rootroot00000000000000beartype-0.18.5/beartype/_decor/__init__.py000066400000000000000000000302021461113517100205740ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. # ....................{ TODO }.................... #FIXME: "typing.LiteralString". We just had a mildly brilliant revelation in #the "beartype.claw._clawast" submodule as to how we might go about performing #static analysis at runtime via the third-party "executing" submodule. \o/ #FIXME: [PEP 484]: Support subscripted bounded type variables. We didn't even #know this was a thing -- but it makes sense. An example is probably the best #way to explain this madness. Witness! # from beartype import beartype # from typing import Iterable # # T = TypeVar('T', bound=Iterable) # # @beartype # def stringify_iterable_items(arg: T[int]) -> T[str]: # return type(arg)(str(item) for item in arg) # #Clearly, @beartype should just quietly reduce both the "T[int]" and "T[str]" #type hints that we can't really do anything with to "Iterable[int]" and #"Iterable[str]" type hints, which we can. Does @beartype currently do that? #Probably... not. At the least, we should begin testing this exhaustively. #FIXME: [PEP 585] It looks like CPython's stdlib quietly extended PEP 585 #support to a variety of undocumented classes, including: #* "asyncio.Future[T]". #* "asyncio.Task[T]". #* "asyncio.Queue[T]". #* "pathlib.PathLike[T]". # #Yes, we can verify that *ALL* of those actually are subscriptable at runtime. #@beartype will need to add corresponding support for such hints, beginning with #defining new sign singletons suffixed by the same basenames (e.g., #"HintSignFuture", "HintSignTask"). Or... maybe not? Maybe everything just #behaves as expected as is? # #At the least, we'll want to rigorously test *ALL* of the above in our test #suite to ensure that @beartype does indeed type-check these as expected. #FIXME: Sadly, we do need to explicitly do *SOMETHING*. @beartype currently #raises exceptions on callables annotated by any of the above, as the metaclass #of these hints prohibits isinstance() checks: e.g., # asyncio.Task[~T] uncheckable at runtime (i.e., not passable as second # parameter to isinstance(), due to raising "isinstance() argument 2 cannot # be a parameterized generic" from metaclass __instancecheck__() method). # #Rather than explicitly matching all of the above, we instead want @beartype to #perform an automated solution implicitly matching all of the above. Notably, #improve @beartype to: # #* Detect parametrized generic hints that are otherwise unrecognized (e.g., # "asyncio.Task[~T]"). #* Introspect the origin (i.e., "__origin__" dunder attribute) from these hints. #* Internally replace each such parametrized generic hint with its origin when # generating type-checking code. Voila! #FIXME: [PEP] Add PEP 613 support (i.e., "typing.TypeAlias"). Thankfully, this #is trivial. "typing.TypeAlias" is prohibited in callable definitions and #inside the bodies of callables. Ergo, @beartype should just raise a #decoration-time exception if any parameter or return is annotated as an #explicit "TypeAlias". That constitutes full support for PEP 613 from our #side. Good enough! :p #FIXME: [SPEED] As a useful MACROoptimization, render the entire @beartype #toolchain thread-safe upfront rather than doing so piecemeal throughout the #toolchain. While the latter certainly works as well, the former is #*SUBSTANTIALLY* more efficient due to the non-trivial expense of each #threadsafe context manager. To do so: #* Simply wrap the body of the implementation of the @beartype decorator in a # context manager locking on a globally declared lock: e.g., # with lock: # ... # Note that an "RLock" is neither needed nor desired here, as @beartype # *NEVER* invokes itself recursively. A non-reentrant "Lock" suffices. #* Rip out all now-redundant "with lock:" expressions throughout the codebase. #FIXME: [SPEED] As a useful microoptimization, consider memoizing "repr(hint)" #calls. We strongly suspect these calls to be a performance bottleneck, because #we repeat them so frequently for the same hint throughout the codebase. The #best approach to doing so is to: #* Define a new memoized "beartype._util.hint.utilhintget" getter: e.g., # @callable_cached # def get_hint_repr(hint: object) -> str: # return repr(hint) #* Globally replace all calls to the repr() builtin throughout the codebase # passed a hint with calls to get_hint_repr() instead. #FIXME: [SPEED] As a useful microoptimization, unroll *ALL* calls to the any() #and all() builtins into equivalent "for" loops in our critical path. Since we #typically pass these builtins generator comprehensions created and destroyed #on-the-fly, we've profiled these builtins to incur substantially higher #runtime costs than equivalent "for" loops. Thanks alot, CPython. *sigh* #FIXME: [FEATURE] Plugin architecture. The NumPy type hints use case will come #up again and again. So, let's get out ahead of that use case rather than #continuing to reinvent the wheel. Let's begin by defining a trivial plugin API #enabling users to define their own arbitrary type hint *REDUCTIONS.* Because #it's capitalized, we know the term "REDUCTIONS" is critical here. We are *NOT* #(at least, *NOT* initially) defining a full-blown plugin API. We're only #enabling users to reduce arbitrary type hints: #* From domain-specific objects they implement and annotate their code with... #* Into PEP-compliant type hints @beartype already supports. #Due to their versatility, the standard use case is reducing PEP-noncompliant #type hints to PEP 593-compliant beartype validators. To do so, consider: #* Defining a new public "beartype.plug" subpackage, defining: # * A private "_PLUGIN_NAME_TO_SIGN" dictionary mapping from each "name" # parameter passed to each prior call of the plug_beartype() function to the # "HintSign" object that function dynamically creates to represent # PEP-noncompliant type hints handled by that plugin. This dictionary # effectively maps from the thing our users care about but we don't (i.e., # plugin names) to the thing our users don't care about but we do (i.e., # hint signs). # * A public plug_beartype() function with signature resembling: # def plug_beartype( # # Mandatory parameters. # name: str, # hint_reduce: Callable[[object,], object], # # # Optional parameters. # hint_detect_from_repr_prefix_args_1_or_more: Optional[str] = None, # hint_detect_from_type_name: Optional[str] = None, # ) -> None: # ...where: # * The "name" parameter is an arbitrary non-empty string (e.g., "Numpy"). # This function will then synthesize a new hint sign suffixed by this # substring (e.g., f'HintSign{name}') and map this name to that sign in # the "_PLUGIN_NAME_TO_SIGN" dictionary. # * The "hint_detect_from_repr_prefix_args_1_or_more" parameter is an # arbitrary non-empty string typically corresponding to the # fully-qualified name of a subclass of "types.GenericAlias" serving as a # PEP 585-compliant type hint factory(e.g., # "muh_package.MuhTypeHintFactory"), corresponding exactly to the items # of the "HINT_REPR_PREFIX_ARGS_1_OR_MORE_TO_SIGN" set. # * The "hint_detect_from_type_name" parameter is the fully-qualified name # of a caller-defined class (e.g., "muh_package.MuhTypeHintFactoryType"), # corresponding exactly to the items of the "HINT_TYPE_NAME_TO_SIGN" set. # * The "hint_reduce" parameter is an arbitrary caller-defined callable # reducing all type hints identified by one or more of the detection # schemes below to another arbitrary (but hopefully PEP-compliant and # beartype-supported) type hint. Again, that will typically be a # PEP 593-compliant beartype validator. # * A public unplug_beartype() function with signature resembling: # def unplug_beartype(name: str) -> None: # This function simply looks up the passed name in various internal data # structures (e.g.,"_PLUGIN_NAME_TO_SIGN") to undo the effects of the prior # plug_beartype() call passed that name. # #Given that, we should then entirely reimplement our current strategy for #handling NumPy type hints into a single call to plug_beartype(): e.g., # # Pretty boss, ain't it? Note we intentionally pass # # "hint_detect_from_repr_prefix_args_1_or_more" here, despite the fact # # that the unsubscripted "numpy.typing.NDArray" factory is a valid type # # hint. Yes, this actually works. Why? Because that factory implicitly # # subscripts itself when unsubscripted. In other words, there is *NO* such # # thing as an unsubscripted typed NumPy array. O_o # def plug_beartype( # name='NumpyArray', # hint_reduce=reduce_hint_numpy_ndarray, # hint_detect_from_repr_prefix_args_1_or_more='numpy.ndarray', # ) # #Yes, this would then permit us to break backward compatibility by bundling #that logic into a new external "beartype_numpy" plugin for @beartype -- but we #absolutely should *NOT* do that, both because it would severely break backward #compatibility *AND* because everyone (including us) wants NumPy support #out-of-the-box. We're all data scientists here. Do the right thing. #FIXME: [FEATURE] Define the following supplementary decorators: #* @beartype.beartype_O1(), identical to the current @beartype.beartype() # decorator but provided for disambiguity. This decorator only type-checks # exactly one item from each container for each call rather than all items. #* @beartype.beartype_Ologn(), type-checking log(n) random items from each # container of "n" items for each call. #* @beartype.beartype_On(), type-checking all items from each container for # each call. We have various ideas littered about GitHub on how to optimize # this for various conditions, but this is never going to be ideal and should # thus never be the default. # #To differentiate between these three strategies, consider: #* Declare an enumeration in "beartype._check.checkcall" resembling: # from enum import Enum # BeartypeStrategyKind = Enum('BeartypeStrategyKind ('O1', 'Ologn', 'On',)) #* Define a new "BeartypeCall.strategy_kind" instance variable. #* Set this variable to the corresponding "BeartypeStrategyKind" enumeration # member based on which of the three decorators listed above was called. #* Explicitly pass the value of the "BeartypeCall.strategy_kind" instance # variable to the beartype._check.code.codemake.make_func_pith_code() # function as a new memoized "strategy_kind" parameter. #* Conditionally generate type-checking code throughout that function depending # on the value of that parameter. #FIXME: Emit one non-fatal warning for each annotated type that is either: # #* "beartype.cave.UnavailableType". #* "beartype.cave.UnavailableTypes". # #Both cases imply user-side misconfiguration, but not sufficiently awful enough #to warrant fatal exceptions. Moreover, emitting warnings rather than #exceptions enables end users to unconditionally disable all unwanted warnings, #whereas no such facilities exist for unwanted exceptions. #FIXME: Validate all tuple annotations to be non-empty *EXCLUDING* #"beartype.cave.UnavailableTypes", which is intentionally empty. #FIXME: Unit test the above edge case. #FIXME: Add support for all possible kinds of parameters. @beartype currently #supports most but *NOT* all types. Specifically: # #* Type-check variadic keyword arguments. Currently, only variadic positional # arguments are type-checked. When doing so, remove the # "Parameter.VAR_KEYWORD" type from the "_PARAM_KIND_IGNORABLE" set. #* Type-check positional-only arguments under Python >= 3.8. Note that, since # C-based callables have *ALWAYS* supported positional-only arguments, the # "Parameter.POSITIONAL_ONLY" type is defined for *ALL* Python versions # despite only being usable in actual Python from Python >= 3.8. In other # words, support for type-checking positional-only arguments should be added # unconditionally without reference to Python version -- we suspect, anyway. # When doing so, remove the "Parameter.POSITIONAL_ONLY" type from the # "_PARAM_KIND_IGNORABLE" set. #* Remove the "_PARAM_KIND_IGNORABLE" set entirely. beartype-0.18.5/beartype/_decor/_decornontype.py000066400000000000000000001145611461113517100217200ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Unmemoized beartype non-type decorators** (i.e., low-level decorators decorating *all* types of decoratable objects except classes, which the sibling :mod:`beartype._decor._decortype` submodule handles, on behalf of the parent :mod:`beartype._decor.decorcore` submodule). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import ( BeartypeDecorWrappeeException, BeartypeDecorWrapperException, ) from beartype.typing import no_type_check from beartype._cave._cavefast import ( MethodBoundInstanceOrClassType, MethodDecoratorClassType, MethodDecoratorBuiltinTypes, MethodDecoratorPropertyType, MethodDecoratorStaticType, ) from beartype._check.checkcall import make_beartype_call from beartype._conf.confcls import BeartypeConf from beartype._conf.confenum import BeartypeStrategy from beartype._data.hint.datahinttyping import ( BeartypeableT, ) from beartype._decor.wrap.wrapmain import generate_code from beartype._util.api.utilapibeartype import ( is_func_unbeartypeable, set_func_beartyped, ) from beartype._util.api.utilapicontextlib import ( is_func_contextlib_contextmanager) from beartype._util.api.utilapifunctools import is_func_functools_lru_cache from beartype._util.cache.pool.utilcachepoolobjecttyped import ( release_object_typed) from beartype._util.func.utilfuncget import get_func_boundmethod_self from beartype._util.func.utilfuncmake import make_func from beartype._util.func.utilfunctest import ( is_func_boundmethod, is_func_python, ) from beartype._util.func.utilfuncwrap import ( unwrap_func_once, unwrap_func_boundmethod_once, unwrap_func_classmethod_once, unwrap_func_staticmethod_once, ) from beartype._util.py.utilpyversion import IS_PYTHON_3_8 from contextlib import contextmanager from functools import lru_cache # ....................{ DECORATORS ~ non-func }.................... def beartype_nontype(obj: BeartypeableT, **kwargs) -> BeartypeableT: ''' Decorate the passed **non-class beartypeable** (i.e., caller-defined object that may be decorated by the :func:`beartype.beartype` decorator but is *not* a class) with dynamically generated type-checking. Parameters ---------- obj : BeartypeableT Non-class beartypeable to be decorated. All remaining keyword parameters are passed as is to a lower-level decorator defined by this submodule (e.g., :func:`.beartype_func`). Returns ------- BeartypeableT New pure-Python callable wrapping this beartypeable with type-checking. ''' # Validate that the passed object is *NOT* a class. assert not isinstance(obj, type), f'{repr(obj)} is class.' # print(f'Decorating non-type {repr(obj)}...') # Type of this object. obj_type = type(obj) # If this object is an uncallable builtin method descriptor (i.e., either a # property, class method, instance method, or static method object), # @beartype was listed above rather than below the builtin decorator # generating this descriptor in the chain of decorators decorating this # decorated callable. Although @beartype typically *MUST* decorate a # callable directly, this edge case is sufficiently common *AND* trivial to # resolve to warrant doing so. To do so, this conditional branch effectively # reorders @beartype to be the first decorator decorating the pure-Python # function underlying this method descriptor: e.g., # # This branch detects and reorders this edge case... # class MuhClass(object): # @beartype # @classmethod # def muh_classmethod(cls) -> None: pass # # # ...to resemble this direct decoration instead. # class MuhClass(object): # @classmethod # @beartype # def muh_classmethod(cls) -> None: pass # # Note that most but *NOT* all of these objects are uncallable. Regardless, # *ALL* of these objects are unsuitable for direct decoration. Specifically: # * Under Python < 3.10, *ALL* of these objects are uncallable. # * Under Python >= 3.10: # * Descriptors created by @classmethod and @property are uncallable. # * Descriptors created by @staticmethod are technically callable but # C-based and thus unsuitable for decoration. if obj_type in MethodDecoratorBuiltinTypes: return beartype_descriptor_decorator_builtin(obj, **kwargs) # type: ignore[return-value] # Else, this object is *NOT* an uncallable builtin method descriptor. # # If this object is uncallable, raise an exception. elif not callable(obj): raise BeartypeDecorWrappeeException( f'Uncallable {repr(obj)} not decoratable by @beartype.') # Else, this object is callable. # # If this object is *NOT* a pure-Python function, this object is a # pseudo-callable (i.e., arbitrary pure-Python *OR* C-based object whose # class defines the __call__() dunder method enabling this object to be # called like a standard callable). In this case, attempt to monkey-patch # runtime type-checking into this pure-Python callable by replacing the # bound method descriptor of the type of this object implementing the # __call__() dunder method with a comparable descriptor calling a # @beartype-generated runtime type-checking wrapper function. Go with it. elif not is_func_python(obj): return beartype_pseudofunc(obj, **kwargs) # type: ignore[return-value] # Else, this object is a pure-Python function. # # If this function is a @contextlib.contextmanager-based isomorphic # decorator closure (i.e., closure both created and returned by the standard # @contextlib.contextmanager decorator where that closure isomorphically # preserves both the number and types of all passed parameters and returns # by accepting only a variadic positional argument and variadic keyword # argument), @beartype was listed above rather than below the # @contextlib.contextmanager decorator creating and returning this closure # in the chain of decorators decorating this decorated callable. This is # non-ideal, as the type of *ALL* objects created and returned by # @contextlib.contextmanager-decorated context managers is a private class # of the "contextlib" module rather than the types implied by the type hints # originally annotating the returns of those context managers. If @beartype # did *not* actively detect and intervene in this edge case, then runtime # type-checkers dynamically generated by @beartype for those managers would # erroneously raise type-checking violations after calling those managers # and detecting the apparent type violation: e.g., # >>> from beartype.typing import Iterator # >>> from contextlib import contextmanager # >>> @contextmanager # ... def muh_context_manager() -> Iterator[None]: yield # >>> type(muh_context_manager()) # # <-- not an "Iterator" # # This conditional branch effectively reorders @beartype to be the first # decorator decorating the callable underlying this context manager, # preserving consistency between return types *AND* return type hints: e.g., # from beartype.typing import Iterator # from contextlib import contextmanager # # # This branch detects and reorders this edge case... # @beartype # @contextmanager # def muh_contextmanager(cls) -> Iterator[None]: yield # # # ...to resemble this direct decoration instead. # @contextmanager # @beartype # def muh_contextmanager(cls) -> Iterator[None]: yield elif is_func_contextlib_contextmanager(obj): return beartype_func_contextlib_contextmanager(obj, **kwargs) # type: ignore[return-value] # Else, this function is *NOT* a @contextlib.contextmanager-based isomorphic # decorator closure. # Return a new callable decorating that callable with type-checking. return beartype_func(obj, **kwargs) # type: ignore[return-value] # ....................{ DECORATORS ~ func }.................... def beartype_func( # Mandatory parameters. func: BeartypeableT, conf: BeartypeConf, # Variadic keyword parameters. **kwargs ) -> BeartypeableT: ''' Decorate the passed callable with dynamically generated type-checking. Parameters ---------- func : BeartypeableT Callable to be decorated by :func:`beartype.beartype`. conf : BeartypeConf Beartype configuration configuring :func:`beartype.beartype` uniquely specific to this callable. All remaining keyword parameters are passed as is to the :meth:`beartype._check.checkcall.BeartypeCall.reinit` method. Returns ------- BeartypeableT New pure-Python callable wrapping this callable with type-checking. ''' assert callable(func), f'{repr(func)} uncallable.' # assert isinstance(conf, BeartypeConf), f'{repr(conf)} not configuration.' # assert isinstance(cls_root, NoneTypeOr[type]), ( # f'{repr(cls_root)} neither type nor "None".') # assert isinstance(cls_curr, NoneTypeOr[type]), ( # f'{repr(cls_curr)} neither type nor "None".') #FIXME: Uncomment to display all annotations in "pytest" tracebacks. # func_hints = func.__annotations__ # If this configuration enables the no-time strategy performing *NO* # type-checking, monkey-patch that callable with the standard # @typing.no_type_check decorator detected above by the call to the # is_func_unbeartypeable() tester on all subsequent decorations passed the # same callable... Doing so prevents all subsequent decorations from # erroneously ignoring this previously applied no-time strategy. if conf.strategy is BeartypeStrategy.O0: no_type_check(func) # pyright: ignore[reportGeneralTypeIssues] # Else, this configuration enables a positive-time strategy performing at # least the minimal amount of type-checking. # If that callable is unbeartypeable (i.e., if this decorator should # preserve that callable as is rather than wrap that callable with # constant-time type-checking), silently reduce to the identity decorator. # # Note that this conditional implicitly handles the prior conditional! :O if is_func_unbeartypeable(func): # type: ignore[arg-type] # print(f'Ignoring unbeartypeable callable {repr(func)}...') return func # type: ignore[return-value] # Else, that callable is beartypeable. Let's do this, folks. # Beartype call metadata describing that callable. bear_call = make_beartype_call(func, conf, **kwargs) # pyright: ignore[reportGeneralTypeIssues] # Generate the raw string of Python statements implementing this wrapper. func_wrapper_code = generate_code(bear_call) # If that callable requires *NO* type-checking, silently reduce to a noop # and thus the identity decorator by returning that callable as is. if not func_wrapper_code: return func # type: ignore[return-value] # Else, that callable requires type-checking. Let's *REALLY* do this, fam. # Function wrapping that callable with type-checking to be returned. # # For efficiency, this wrapper accesses *ONLY* local rather than global # attributes. The latter incur a minor performance penalty, since local # attributes take precedence over global attributes, implying all global # attributes are *ALWAYS* first looked up as local attributes before falling # back to being looked up as global attributes. func_wrapper = make_func( func_name=bear_call.func_wrapper_name, func_code=func_wrapper_code, func_locals=bear_call.func_wrapper_scope, #FIXME: String formatting is infamously slow. As an optimization, it'd #be strongly preferable to instead pass a lambda function accepting *NO* #parameters and returning the desired string, which make_func() should #then internally call on an as-needed basis to make this string: e.g., # func_label_factory=lambda: f'@beartyped {bear_call.func_wrapper_name}() wrapper', # #This is trivial. The only question then is: "Which is actually faster?" #Before finalizing this refactoring, let's profile both, adopt whichever #outperforms the other, and then document this choice in make_func(). #FIXME: *WAIT.* We don't need a lambda at all. All we need is to: #* Define a new BeartypeCall.label_func_wrapper() method resembling: # def label_func_wrapper(self) -> str: # return f'@beartyped {self.func_wrapper_name}() wrapper' #* Refactor make_func() to accept a new optional keyword-only # "func_label_factory" parameter, passed here as: # func_label_factory=bear_call.label_func_wrapper, # #That's absolutely guaranteed to be the fastest approach. func_label=f'@beartyped {bear_call.func_wrapper_name}() wrapper', func_wrapped=func, is_debug=conf.is_debug, exception_cls=BeartypeDecorWrapperException, ) # Declare this wrapper to be generated by @beartype, which tests for the # existence of this attribute above to avoid re-decorating callables # already decorated by @beartype by efficiently reducing to a noop. set_func_beartyped(func_wrapper) # Release this beartype call metadata back to its object pool. release_object_typed(bear_call) # Return this wrapper. return func_wrapper # type: ignore[return-value] def beartype_func_contextlib_contextmanager( func: BeartypeableT, **kwargs) -> BeartypeableT: ''' Decorate the passed :func:`contextlib.contextmanager`-based **isomorphic decorator closure** (i.e., closure both defined and returned by the standard :func:`contextlib.contextmanager` decorator where that closure isomorphically preserves both the number and types of all passed parameters and returns by accepting only a variadic positional argument and variadic keyword argument) with dynamically generated type-checking. Parameters ---------- descriptor : BeartypeableT Context manager to be decorated by :func:`beartype.beartype`. All remaining keyword parameters are passed as is to the lower-level :func:`.beartype_func` decorator internally called by this higher-level decorator on the pure-Python function encapsulated in this descriptor. Returns ------- BeartypeableT New pure-Python callable wrapping this context manager with type-checking. ''' # Original pure-Python generator factory function decorated by # @contextlib.contextmanager. generator = unwrap_func_once(func) # Decorate this generator factory function with type-checking. generator_checked = beartype_func(func=generator, **kwargs) # Re-decorate this generator factory function by @contextlib.contextmanager. generator_checked_contextmanager = contextmanager(generator_checked) # Return this context manager. return generator_checked_contextmanager # type: ignore[return-value] # ....................{ DECORATORS ~ descriptor }.................... def beartype_descriptor_decorator_builtin( descriptor: BeartypeableT, **kwargs) -> BeartypeableT: ''' Decorate the passed **builtin decorator object** (i.e., C-based unbound method descriptor produced by the builtin :class:`classmethod`, :class:`property`, or :class:`staticmethod` decorators) with dynamically generated type-checking. Parameters ---------- descriptor : BeartypeableT Descriptor to be decorated by :func:`beartype.beartype`. All remaining keyword parameters are passed as is to the lower-level :func:`.beartype_func` decorator internally called by this higher-level decorator on the pure-Python function encapsulated in this descriptor. Returns ------- BeartypeableT New pure-Python callable wrapping this descriptor with type-checking. Raises ------ BeartypeDecorWrappeeException If this descriptor is neither a class, property, or static method descriptor. ''' # assert isinstance(descriptor, MethodDecoratorBuiltinTypes), ( # f'{repr(descriptor)} not builtin method descriptor.') # assert isinstance(conf, BeartypeConf), f'{repr(conf)} not configuration.' # Type of this descriptor. descriptor_type = type(descriptor) # If this descriptor is a property method... # # Note that property method descriptors are intentionally tested next, due # to their ubiquity "in the wild." Class and static method descriptors are # comparatively rarefied by comparison. if descriptor_type is MethodDecoratorPropertyType: # Pure-Python unbound getter, setter, and deleter functions wrapped by # this descriptor if any *OR* "None" otherwise (i.e., for each such # function currently unwrapped by this descriptor). descriptor_getter = descriptor.fget # type: ignore[assignment,union-attr] descriptor_setter = descriptor.fset # type: ignore[assignment,union-attr] descriptor_deleter = descriptor.fdel # type: ignore[assignment,union-attr] # Decorate this getter function with type-checking. # # Note that *ALL* property method descriptors wrap at least a getter # function (but *NOT* necessarily a setter or deleter function). This # function is thus guaranteed to be non-"None". descriptor_getter = beartype_func( # type: ignore[type-var] func=descriptor_getter, # pyright: ignore **kwargs ) # If this property method descriptor additionally wraps a setter and/or # deleter function, type-check those functions as well. if descriptor_setter is not None: descriptor_setter = beartype_func(descriptor_setter, **kwargs) if descriptor_deleter is not None: descriptor_deleter = beartype_func(descriptor_deleter, **kwargs) # Return a new property method descriptor decorating all of these # functions, implicitly destroying the prior descriptor. # # Note that the "property" class interestingly has this signature: # class property(fget=None, fset=None, fdel=None, doc=None): ... return property( # type: ignore[return-value] fget=descriptor_getter, fset=descriptor_setter, fdel=descriptor_deleter, doc=descriptor.__doc__, ) # Else, this descriptor is *NOT* a property method. # # If this descriptor is a class method... elif descriptor_type is MethodDecoratorClassType: # Possibly C-based callable wrappee object decorated by this descriptor. # # Note that this wrappee is typically but *NOT* necessarily a # pure-Python unbound function. This descriptor explicitly permits the # decorated object to be a callable C-based type (i.e., defining the # __call__() dunder method), which numerous standard and third-party # pure-Python classes then leverage to augment those classes into # subscriptable type hint factories via a simple one-liner: e.g., # from abc import ABCMeta # from beartype import beartype # from types import GenericAlias # # @beartype # class MuhTypeHintFactory(metaclass=ABCMeta): # # This exact one liner appears verbatim throughout the # # standard library (as well as third-party packages). # __class_getitem__ = classmethod(GenericAlias) # # Ergo, the name "__func__" of this dunder attribute is disingenuous. # This descriptor does *NOT* merely decorate functions; this descriptor # permissively decorates all callable objects. descriptor_wrappee = unwrap_func_classmethod_once(descriptor) # type: ignore[arg-type] # If this wrappee is *NOT* a pure-Python unbound function, this wrappee # is C-based and/or a type. In either case, avoid type-checking this # wrappee by silently preserving this descriptor as is. Why? If this # wrappee is: # * C-based, this wrappee *CANNOT* be decorated with type-checking. # * A type, this wrappee *COULD* be effectively decorated with # type-checking by decorating its __call__() dunder method. However, # this type may *NOT* have been intended to be decorated by @beartype. # Indeed, this type may *NOT* even reside within the same package. # That the current class references this type is an insufficient # reason to transitively decorate external types without user consent. if not is_func_python(descriptor_wrappee): return descriptor # Else, this wrappee is a pure-Python unbound function. # Pure-Python unbound function type-checking this class method. # Note that: # * Python 3.8, 3.9, and 3.10 explicitly permit the @classmethod # decorator to be chained into the @property decorator: e.g., # class MuhClass(object): # @classmethod # <-- this is fine under Python < 3.11 # @property # def muh_property(self) -> ...: ... # * Python ≥ 3.11 explicitly prohibits that by emitting a non-fatal # "DeprecationWarning" on each attempt to do so. Under Python ≥ 3.11, # users *MUST* instead refactor the above simplistic decorator # chaining use case as follows: # * Define a metaclass for each class requiring a class property. # * Define each class property on that metaclass rather than on that # class instead. # # In other words: # class MuhClassMeta(type): # <-- Python ≥ 3.11 demands sacrifice # ''' # Metaclass of the :class`.MuhClass` class, defining class # properties for that class. # ''' # # @property # def muh_property(cls) -> ...: ... # # class MuhClass(object, metaclass=MuhClassMeta): # pass # * Technically, all Python versions currently supported by @beartype # permit this. Ergo, @beartype currently defers to: # * The high-level beartype_nontype() decorator (which permits the # passed object to be the descriptor created and returned by the # @property decorator and thus implicitly allows @classmethod to be # chained into @property) rather than... # * The low-level beartype_func() decorator (which requires the passed # object to be callable, which the descriptor created and returned # by the @property decorator is *NOT*). func_checked = beartype_nontype(descriptor_wrappee, **kwargs) # Return a new class method descriptor decorating the pure-Python # unbound function wrapped by this descriptor with type-checking, # implicitly destroying the prior descriptor. return classmethod(func_checked) # type: ignore[return-value] # Else, this descriptor is *NOT* a class method. # # If this descriptor is a static method... elif descriptor_type is MethodDecoratorStaticType: # Possibly C-based callable wrappee object decorated by this descriptor. descriptor_wrappee = unwrap_func_staticmethod_once(descriptor) # type: ignore[arg-type] # Pure-Python unbound function type-checking this static method. func_checked = beartype_func(descriptor_wrappee, **kwargs) # type: ignore[union-attr] # Return a new static method descriptor decorating the pure-Python # unbound function wrapped by this descriptor with type-checking, # implicitly destroying the prior descriptor. return staticmethod(func_checked) # type: ignore[return-value] # Else, this descriptor is *NOT* a static method. # Raise a fallback exception. This should *NEVER happen. This *WILL* happen. raise BeartypeDecorWrappeeException( f'Builtin method descriptor {repr(descriptor)} ' f'not decoratable by @beartype ' f'(i.e., neither property, class method, nor static method descriptor).' ) # ....................{ PRIVATE ~ decorators }.................... def _beartype_descriptor_boundmethod( descriptor: BeartypeableT, **kwargs) -> BeartypeableT: ''' Decorate the passed **builtin bound method object** (i.e., C-based bound method descriptor produced by Python on instantiation for each instance and class method defined by the class being instantiated) with dynamically generated type-checking. Parameters ---------- descriptor : BeartypeableT Descriptor to be decorated by :func:`beartype.beartype`. All remaining keyword parameters are passed as is to the lower-level :func:`.beartype_func` decorator internally called by this higher-level decorator on the pure-Python function encapsulated in this descriptor. Returns ------- BeartypeableT New pure-Python callable wrapping this descriptor with type-checking. ''' assert is_func_boundmethod(descriptor), ( f'{repr(descriptor)} not builtin bound method descriptor.') # Possibly C-based callable wrappee object encapsulated by this descriptor. descriptor_wrappee = unwrap_func_boundmethod_once(descriptor) # Instance object to which this descriptor was bound at instantiation time. descriptor_self = get_func_boundmethod_self(descriptor) # Pure-Python unbound function decorating the similarly pure-Python unbound # function encapsulated by this descriptor with type-checking. # # Note that doing so: # * Implicitly propagates dunder attributes (e.g., "__annotations__", # "__doc__") from the original function onto this new function. Good. # * Does *NOT* implicitly propagate the same dunder attributes from the # original descriptor encapsulating the original function to the new # descriptor (created below) encapsulating this wrapper function. Bad! # Thankfully, only one such attribute exists as of this time: "__doc__". # We propagate this attribute manually below. func_checked = beartype_func(func=descriptor_wrappee, **kwargs) # pyright: ignore # New instance method descriptor rebinding this function to the instance of # the class bound to the prior descriptor. # # Note that: # * This is required, as the "__func__" attribute of method descriptors is # read-only. Attempting to do so raises this non-human-readable exception: # AttributeError: readonly attribute # This implies that the passed descriptor *CANNOT* be meaningfully # modified. Our only recourse is to define an entirely new descriptor, # effectively discarding the passed descriptor, which will then be # subsequently garbage-collected. This is wasteful. This is Python. # * This can also be implemented by abusing the descriptor protocol: # descriptor_new = descriptor_func_new.__get__(descriptor.__self__) # That said, there exist *NO* benefits to doing so. Indeed, doing so only # reduces the legibility and maintainability of this operation. descriptor_new = MethodBoundInstanceOrClassType( func_checked, descriptor_self) # type: ignore[return-value] #FIXME: Actually, Python doesn't appear to support this at the moment. #Attempting to do so raises this exception: # AttributeError: attribute '__doc__' of 'method' objects is not writable # #See also this open issue on the Python bug tracker requesting this be #resolved. Sadly, Python has yet to resolve this: # https://bugs.python.org/issue47153 # # Propagate the docstring from the prior to the new descriptor. # # # # Note that Python guarantees this attribute to exist. If the original # # function had a docstring, this attribute is non-"None"; else, this # # attribute is "None". In either case, this attribute exists. Ergo, # # additional validation is neither required nor desired. # descriptor_new.__doc__ = descriptor.__doc__ # Return this new descriptor, implicitly destroying the prior descriptor. return descriptor_new # type: ignore[return-value] # ....................{ DECORATORS ~ pseudo-callable }.................... def beartype_pseudofunc(pseudofunc: BeartypeableT, **kwargs) -> BeartypeableT: ''' Monkey-patch the passed **pseudo-callable** (i.e., arbitrary pure-Python *or* C-based object whose class defines the ``__call__``) dunder method enabling this object to be called like a standard callable) with dynamically generated type-checking. For each bound method descriptor encapsulating a method bound to this object, this function monkey-patches (i.e., replaces) that descriptor with a comparable descriptor calling a new :func:`beartype.beartype`-generated runtime type-checking wrapper function wrapping the original method. Parameters ---------- pseudofunc : BeartypeableT Pseudo-callable to be monkey-patched by :func:`beartype.beartype`. All remaining keyword parameters are passed as is to the lower-level :func:`.beartype_func` decorator internally called by this higher-level decorator on the pure-Python function encapsulated in this descriptor. Returns ------- BeartypeableT The object monkey-patched by :func:`beartype.beartype`. ''' # print(f'@beartyping pseudo-callable {repr(obj)}...') # __call__() dunder method defined by this object if this object defines # this method *OR* "None" otherwise. pseudofunc_call_method = getattr(pseudofunc, '__call__') # If this object does *NOT* define this method, this object is *NOT* a # pseudo-callable. In this case, raise an exception. # # Note this edge case should *NEVER* occur. By definition, this object has # already been validated to be callable. But this object is *NOT* a # pure-Python function. Since the only other category of callable in Python # is a pseudo-callable, this object *MUST* be a pseudo-callable. That said, # languages change; it's not inconceivable that Python could introduce yet # another kind of callable object under future versions. if pseudofunc_call_method is None: raise BeartypeDecorWrappeeException( # pragma: no cover f'Callable {repr(pseudofunc)} not pseudo-callable ' f'(i.e., callable object defining __call__() dunder method).' ) # Else, this object is a pseudo-callable. # # If this method is *NOT* pure-Python, this method is C-based. In this # case... # # Note that this is non-ideal. Whereas logic below safely monkey-patches # pure-Python pseudo-callables in a general-purpose manner, that same logic # does *NOT* apply to C-based pseudo-callables; indeed, there exists *NO* # general-purpose means of safely monkey-patching the latter. Instead, # specific instances of the latter *MUST* be manually detected and handled. elif not is_func_python(pseudofunc_call_method): # If this is a C-based @functools.lru_cache-memoized callable (i.e., # low-level C-based callable object both created and returned by the # standard @functools.lru_cache decorator), @beartype was listed above # rather than below the @functools.lru_cache decorator creating and # returning this callable in the chain of decorators decorating this # decorated callable. # # This conditional branch effectively reorders @beartype to be the first # decorator decorating the pure-Python callable underlying this C-based # pseudo-callable: e.g., # # from functools import lru_cache # # # This branch detects and reorders this edge case... # @beartype # @lru_cache # def muh_lru_cache() -> None: pass # # # ...to resemble this direct decoration instead. # @lru_cache # @beartype # def muh_lru_cache() -> None: pass if is_func_functools_lru_cache(pseudofunc): # Return a new callable decorating that callable with type-checking. return beartype_pseudofunc_functools_lru_cache( # type: ignore pseudofunc=pseudofunc, **kwargs) # pyright: ignore # Else, this is *NOT* a C-based @functools.lru_cache-memoized callable. # Replace the existing bound method descriptor to this __call__() dunder # method with a new bound method descriptor to a new __call__() dunder # method wrapping the old method with runtime type-checking. # # Note that: # * This is a monkey-patch. Since the caller is intentionally decorating # this pseudo-callable with @beartype, this is exactly what the caller # wanted. Probably. Hopefully. Okay! We're crossing our fingers here. # * This monkey-patches the *CLASS* of this object rather than this object # itself. Why? Because Python. For unknown reasons (so, speed is what # we're saying), Python accesses the __call__() dunder method on the # *CLASS* of an object rather than on the object itself. Of course, this # implies that *ALL* instances of this pseudo-callable (rather than merely # the passed instance) will be monkey-patched. This may *NOT* necessarily # be what the caller wanted. Unfortunately, the only alternative would be # for @beartype to raise an exception when passed a pseudo-callable. Since # doing something beneficial is generally preferable to doing something # harmful, @beartype prefers the former. See also official documentation # on the subject: # https://docs.python.org/3/reference/datamodel.html#special-method-names pseudofunc.__class__.__call__ = _beartype_descriptor_boundmethod( # type: ignore[assignment,method-assign] descriptor=pseudofunc_call_method, **kwargs) # Return this monkey-patched object. return pseudofunc # type: ignore[return-value] def beartype_pseudofunc_functools_lru_cache( pseudofunc: BeartypeableT, **kwargs) -> BeartypeableT: ''' Monkey-patch the passed :func:`functools.lru_cache`-memoized **pseudo-callable** (i.e., low-level C-based callable object both created and returned by the standard :func:`functools.lru_cache` decorator) with dynamically generated type-checking. Parameters ---------- pseudofunc : BeartypeableT Pseudo-callable to be monkey-patched by :func:`beartype.beartype`. All remaining keyword parameters are passed as is to the lower-level :func:`.beartype_func` decorator internally called by this higher-level decorator on the pure-Python function encapsulated in this descriptor. Returns ------- BeartypeableT New pseudo-callable monkey-patched by :func:`beartype.beartype`. ''' # If this pseudo-callable is *NOT* actually a @functools.lru_cache-memoized # callable, raise an exception. if not is_func_functools_lru_cache(pseudofunc): raise BeartypeDecorWrappeeException( # pragma: no cover f'@functools.lru_cache-memoized callable {repr(pseudofunc)} not ' f'decorated by @functools.lru_cache.' ) # Else, this pseudo-callable is a @functools.lru_cache-memoized callable. # Original pure-Python callable decorated by @functools.lru_cache. func = unwrap_func_once(pseudofunc) # If the active Python interpreter targets Python 3.8, then this # pseudo-callable fails to declare the cache_parameters() lambda function # called below to recover the keyword parameters originally passed by the # caller to that decorator. In this case, we have *NO* recourse but to # explicitly inform the caller of this edge case by raising a human-readable # exception providing a pragmatic workaround. if IS_PYTHON_3_8: raise BeartypeDecorWrappeeException( # pragma: no cover f'@functools.lru_cache-memoized callable {repr(func)} not ' f'decoratable by @beartype under Python 3.8. ' f'Consider manually decorating this callable by ' f'@beartype first and then by @functools.lru_cache to preserve ' f'Python 3.8 compatibility: e.g.,\n' f' # Do this...\n' f' @lru_cache(maxsize=42)\n' f' @beartype\n' f' def muh_func(...) -> ...: ...\n' f'\n' f' # Rather than either this...\n' f' @beartype\n' f' @lru_cache(maxsize=42)\n' f' def muh_func(...) -> ...: ...\n' f'\n' f' # Or this (if you use "beartype.claw", which you really should).\n' f' @lru_cache(maxsize=42)\n' f' def muh_func(...) -> ...: ...\n' ) # Else, the active Python interpreter targets Python >= 3.9. # Decorate that callable with type-checking. func_checked = beartype_func(func=func, **kwargs) # Dictionary mapping from the names of all keyword parameters originally # passed by the caller to that decorator, enabling the re-decoration of that # callable. Thankfully, that decorator preserves these parameters via the # decorator-specific "cache_parameters" instance variable whose value is a # bizarre argumentless lambda function (...for unknown reasons that are # probably indefensible) creating and returning this dictionary: e.g., # >>> from functools import lru_cache # >>> @lru_cache(maxsize=3) # ... def plus_one(n: int) -> int: return n +1 # >>> plus_one.cache_parameters() # {'maxsize': 3, 'typed': False} lru_cache_kwargs = pseudofunc.cache_parameters() # type: ignore[attr-defined] # Closure defined and returned by the @functools.lru_cache decorator when # passed these keyword parameters. lru_cache_configured = lru_cache(**lru_cache_kwargs) # Re-decorate that callable by @functools.lru_cache by the same parameters # originally passed by the caller to that decorator. pseudofunc_checked = lru_cache_configured(func_checked) # Return that new pseudo-callable. return pseudofunc_checked beartype-0.18.5/beartype/_decor/_decortype.py000066400000000000000000000557561461113517100212170ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Unmemoized beartype type decorators** (i.e., low-level decorators decorating classes on behalf of the parent :mod:`beartype._decor.decorcore` submodule). This private submodule is effectively the :func:`beartype.beartype` decorator despite *not* actually being that decorator (due to being unmemoized). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import ( Dict, Set, ) from beartype._cave._cavemap import NoneTypeOr from beartype._check.checkcache import clear_checker_caches from beartype._conf.confcls import BeartypeConf from beartype._data.cls.datacls import TYPES_BEARTYPEABLE from beartype._data.hint.datahinttyping import ( BeartypeableT, TypeStack, ) from beartype._util.cls.utilclsset import set_type_attr from beartype._util.module.utilmodget import get_object_module_name_or_none from collections import defaultdict from functools import wraps # ....................{ DECORATORS ~ type }.................... def beartype_type( # Mandatory parameters. cls: BeartypeableT, conf: BeartypeConf, # Optional parameters. cls_stack: TypeStack = None, ) -> BeartypeableT: ''' Decorate the passed class with dynamically generated type-checking. Parameters ---------- cls : BeartypeableT Class to be decorated by :func:`beartype.beartype`. conf : BeartypeConf Beartype configuration configuring :func:`beartype.beartype` uniquely specific to this class. cls_stack : TypeStack, optional **Type stack** (i.e., either a tuple of the one or more :func:`beartype.beartype`-decorated classes lexically containing the class variable or method annotated by this hint *or* :data:`None`). Defaults to :data:`None`. Returns ---------- BeartypeableT This class decorated by :func:`beartype.beartype`. ''' assert isinstance(cls, type), f'{repr(cls)} not type.' assert isinstance(cls_stack, NoneTypeOr[tuple]), ( f'{repr(cls_stack)} neither tuple nor "None".') # assert isinstance(conf, BeartypeConf), f'{repr(conf)} not configuration.' # print(f'Decorating type {repr(obj)}...') # ....................{ IMPORTS }.................... # Avoid circular import dependencies. from beartype._decor.decorcore import beartype_object # ....................{ NOOP }.................... # Original C-based __sizeof__() dunder method defined by this class, which # this decorator subsequently wraps with a pure-Python __sizeof__() dunder # method. Why? Tangential reasons that are obscure, profane, and have # absolutely *NOTHING* to do with the __sizeof__() dunder method itself. # Succinctly, @beartype needs a reasonably safe place to persist # @beartype-specific attributes pertaining to this class. # # Clearly, the obvious place would be this class itself. However, doing so # would fundamentally modify this class and thus *ALL* instances of this # class in an unexpected and thus possibly unsafe manner. Consider common # use cases like slots, introspection, pickling, and sizing. Clearly, # monkey-patching attributes into class dictionaries without the explicit # consent of class designers is an ill-advised approach. # # A less obvious but safer place is required. A method of this class would # be the ideal candidate; whereas everybody cares about object attributes # and thus class dictionaries, nobody cares about method attributes. This is # why @beartype safely monkey-patches attributes into @beartype-decorated # methods. However, which method? Most methods are *NOT* guaranteed to exist # across all possible classes. Adding a new method to this class would be no # better than adding a new attribute to this class; both modify class # dictionaries. Fortunately, Python currently guarantees *ALL* classes to # define at least 24 dunder methods as of Python 3.11. How? Via the root # "object" superclass. Unfortunately, *ALL* of these methods are C-based and # thus do *NOT* directly support monkey-patching: e.g., # >>> class AhMahGoddess(object): pass # >>> AhMahGoddess.__init__.__beartyped_cls = AhMahGoddess # AttributeError: 'wrapper_descriptor' object has no attribute # '__beartyped_cls' # # Fortunately, *ALL* of these methods may be wrapped by pure-Python # equivalents whose implementations defer to their original C-based methods. # Unfortunately, doing so slightly reduces the efficiency of calling these # methods. Fortunately, a subset of these methods are rarely called under # production workloads; slightly reducing the efficiency of calling these # methods is irrelevant to almost all use cases. Of these, the most obscure, # largely useless, poorly documented, and single-use is the __sizeof__() # dunder method -- which is only ever called by the sys.getsizeof() utility # function, which itself is only ever called manually in a REPL or by # third-party object sizing packages. In short, __sizeof__() is perfect. cls_sizeof_old = cls.__sizeof__ # True only if this decorator has already decorated this class, as indicated # by the @beartype-specific class variable "__beartyped_cls" monkey-patched # into a pure-Python __sizeof__() dunder method wrapper by a prior call to # this decorator passed this class. is_cls_beartyped = getattr(cls_sizeof_old, '__beartyped_cls', None) # If the value of this variable is that of this class, a prior call to this # decorator has already decorated this class. In this case, silently reduce # to a noop by returning this class as is. # # See where this variable is set below for further details. if is_cls_beartyped is cls: # print(f'Ignoring repeat decoration of {repr(cls)}...') return cls # type: ignore[return-value] # Else, this decorator has yet to decorate this class. # ....................{ LOCALS }.................... # Replace the passed class stack with a new class stack appending this # decorated class to the top of this stack, reflecting the fact that this # decorated class is now the most deeply lexically nested class for the # currently recursive chain of @beartype-decorated classes. cls_stack = ( # If the caller passed *NO* class stack, then this class is necessarily # the first decorated class being decorated directly by @beartype and # thus the root decorated class. # # Note this is the common case and thus tested first. Since nested # classes effectively do *NOT* exist in the wild, this comprises # 99.999% of all real-world cases. (cls,) if cls_stack is None else # Else, the caller passed a clack stack comprising at least a root # decorated class. Preserve that class as is to properly expose that # class elsewhere. cls_stack + (cls,) ) # ....................{ DECORATION }.................... # Clear *ALL* beartype-specific internal caches that have been shown to fail # when a class is redefined if the passed class is detected as having been # redefined in its module. _uncache_beartype_if_type_redefined(cls) # For the unqualified name and value of each direct (i.e., *NOT* indirectly # inherited) attribute of this class... for attr_name, attr_value in cls.__dict__.items(): # pyright: ignore[reportGeneralTypeIssues] # True only if this attribute is directly beartypeable (e.g., is either # a function, class, or builtin method descriptor). is_attr_beartypeable = isinstance(attr_value, TYPES_BEARTYPEABLE) # If this attribute is *NOT* directly beartypeable (e.g., is neither a # function, class, nor builtin method descriptor), this attribute # *COULD* still be indirectly beartypeable. How? By being a non-standard # object implemented by some third-party package wrapping a standard # object that *is* directly beartypeable. Although the original use case # was non-standard function wrappers implemented by the third-party # Equinox package, this logic transparently generalizes to *ALL* # third-party packages. Consequently, *ALL* third-party packages # defining non-standard objects wrapping standard objects should # endeavour to support @beartype by reproducing the general-purpose # solution that Equinox adopted. # # Notably, third-party packages should ideally add "support for such # monkey-patching, by adding a __setattr__() that checks for functions # and wraps them into one of Equinox's function-wrappers." See also: # https://github.com/patrick-kidger/equinox/issues/584#issuecomment-1806260288 # # Specifically, if this attribute is *NOT* directly beartypeable... if not is_attr_beartypeable: # Uncomment to debug this insanity. *sigh* # attr_value_old = attr_value # Override the previously retrieved static value of this attribute # (i.e., the direct value of this attribute *WITHOUT* regard to # dynamic descriptor lookup, which in the case of a standard # descriptor builtin like @classmethod is that C-based @classmethod # descriptor itself) with the dynamic value of this attribute # (i.e., the indirect value of this attribute *WITH* regard to # dynamic descriptor lookup, which in the case of a standard # descriptor builtin like @classmethod is the pure-Python function # wrapped by that C-based @classmethod descriptor) if this attribute # supports the descriptor protocol *OR* reduce to a noop otherwise. attr_value = getattr(cls, attr_name) # True only if this attribute is directly beartypeable. is_attr_beartypeable = isinstance(attr_value, TYPES_BEARTYPEABLE) # Else, this attribute is directly beartypeable. # If this attribute is... if ( # Now directly beartypeable *AND*... is_attr_beartypeable and # It is *NOT* the case that... # # Note that this condition intentionally excludes class variables # whose values are types from consideration, thus preventing # @beartype from erroneously decorating those types. Why? Because # the caller did *NOT* explicitly instruct us to decorate those # types. Moreover, attempting to do so can ignite infinite recursion # in common edge cases and is thus fundamentally dangerous. # # Consider this sample user-defined class: # class ParentClass(object): # class_var: type = type # class NestedClass(object): # pass # # Syntactically, the class variable "ParentClass.class_var" and # nested class "ParentClass.NestedClass" share *NO* commonality. # Semantically, however, @beartype treats those two attributes of # the parent class "ParentClass" as effectively identical. The # values of those two attributes are both classes, which @beartype # typically tries to recursively decorate. But only the latter are # safely decoratable by @beartype. # # Class variables whose values are types are *NOT* safely # decoratable by @beartype. In the best case, doing so would # decorate external classes *NOT* intended to be decorated; in the # worst case, doing so would provoke infinite recursion. Indeed, the # worst case is exactly what once happened. Previously, decorating # concrete "enum.Enum" subclasses with @beartype once provoked # infinite recursion. Why? Because: # # * *ALL* "enum.Enum" subclasses define a private "_member_type_" # attribute whose value is the "object" superclass, which # @beartype then decorated. # * However, the "object" superclass defines the "__class__" dunder # attribute whose value is the "type" superclass, which @beartype # then decorated. # * However, the "type" superclass defines the "__base__" dunder # attribute whose value is the "object" superclass, which # @beartype then decorated. # * *INFINITE FRIGGIN' RECURSION*. Anarchy today. # # In both the best and worst cases above, class variables whose # values are types *CANNOT* be safely decorated by @beartype. not ( # This attribute is a class *AND*... isinstance(attr_value, type) and # This class was declared elsewhere and merely defined here as a # class attribute of the currently decorated class whose value # is this class (rather than as a nested class of the currently # decorated class)... not attr_value.__qualname__.startswith(cls.__qualname__) ) ): # This attribute decorated with type-checking configured by this # configuration if *NOT* already decorated. attr_value_beartyped = beartype_object( obj=attr_value, conf=conf, cls_stack=cls_stack, ) # Replace this undecorated attribute with this decorated attribute. set_type_attr(cls, attr_name, attr_value_beartyped) # print(f'Decorating {repr(cls)} attribute "{attr_name}"...') # print(f'type: {type(attr_value)}; dir: {dir(attr_value)}') # Else, this attribute is *NOT* beartypeable. In this case, silently # ignore this attribute. # ....................{ MONKEY-PATCH }.................... # Pure-Python __sizeof__() dunder method wrapping the original C-based # __sizeof__() dunder method declared by this class. @wraps(cls_sizeof_old) def cls_sizeof_new(self) -> int: return cls_sizeof_old(self) # type: ignore[call-arg] # Monkey-patch a @beartype-specific instance variable into this wrapper, # recording that this decorator has now decorated this class. # # Note that we intentionally set this variable to this class rather than an # arbitrary value (e.g., "False", "None"). Why? Because subclasses of this # class will inherit this wrapper. If we simply set this variable to an # arbitrary value, we would be unable to decide above between the following # two cases: # * Whether this wrapper was inherited from its superclass, in which case # this class has yet to be decorated by @beartype. # * Whether this wrapper was *NOT* inherited from its superclass, in which # case this class has already been decorated by @beartype. cls_sizeof_new.__beartyped_cls = cls # type: ignore[attr-defined] # Replace the original C-based __sizeof__() dunder method with this wrapper. # We intentionally call our set_type_attr() setter rather than attempting to # set this attribute directly. The latter approach efficiently succeeds for # standard pure-Python mutable classes but catastrophically fails for # non-standard C-based immutable classes (e.g., "enum.Enum" subclasses). set_type_attr(cls, '__sizeof__', cls_sizeof_new) # Return this class as is. return cls # type: ignore[return-value] # ....................{ PRIVATE ~ globals }.................... _BEARTYPED_MODULE_TO_TYPE_NAME: Dict[str, Set[str]] = defaultdict(set) ''' **Decorated classname registry (i.e., dictionary mapping from the fully-qualified name of each module defining one or more classes decorated by the :func:`beartype.beartype` decorator to the set of the unqualified basenames of all classes in that module decorated by that decorator). ''' # ....................{ PRIVATE ~ globals }.................... def _uncache_beartype_if_type_redefined(cls: type) -> None: ''' Clear *all* :mod:`beartype`-specific internal caches that have been shown to fail when a class is redefined if the passed class is detected as having been redefined in its module. If a class with the same unqualified basename defined in a module with the same fully-qualified name has already been marked as decorated by this decorator, then either: * That module has been externally reloaded. In this case, this class (along with the remainder of that module) has now been redefined. Common examples include: * Rerunning a Jupyter cell defining this class. * Refreshing a web app enabling hot reloading (i.e., automatic reloading of on-disk modules whose contents have been externally modified *after* that app was initially run). Since most Python web app frameworks (e.g., Flask, Streamlit) support hot reloading, this is the common case. * That module has internally redefined this class two or more times. This behaviour, while typically a bug, is also technically valid: e.g., .. code-block:: python @beartype def MuhClass(object): ... @beartype def MuhClass(object): ... # <-- this makes me squint In either case, this class has been redefined. Since :mod:`beartype` has no efficient means of deciding which internal caches to clear in response, :mod:`beartype` instead now unconditionally clears *all* internal caches. Doing so incurs a minor performance penalty whenever a module reload occurs while preserving user-facing usability across module reloads. In short, the minor performance penalty is worth this major usability gain. ''' # Fully-qualified name of the module defining this class if this class is # defined by a module *OR* "None" otherwise (e.g., if this class is only # dynamically defined in-memory outside of any module structure). module_name = get_object_module_name_or_none(cls) # If this class is defined by a module... if module_name: # Unqualified basename of this class. type_name = cls.__name__ # Set of the unqualified basenames of *ALL* classes in that module # previously decorated by this decorator. type_names_beartyped = _BEARTYPED_MODULE_TO_TYPE_NAME[module_name] # If a class with the same unqualified basename defined in a module with # the same fully-qualified name has already been marked as decorated by # this decorator, then this class is currently being redefined. In this # case, clear *ALL* beartype-specific internal caches that have been # shown to fail when classes are redefined. if type_name in type_names_beartyped: #FIXME: Consider emitting a logging message instead if this branch #ever becomes computationally intensive, please. # print(f'@beartyped class "{module_name}.{type_name}" redefined!') # Clear the previously accessed set of the unqualified basenames of # *ALL* classes in that module previously decorated by this # decorator. Technically, this is optional. Pragmatically, this # *SHOULD* significantly improve the space and time constraints # associated with this class redefinition. Why? Because this class # being redefined implies that the module defining this class is # being redefined, which implies that all classes in that module are # being redefined as well. If we did *NOT* clear this set here, then # this set would continue to contain the unqualified basenames of # those other classes in that module; each @beartype-decorated # redefinition of those other classes would then unnecessarily clear # the same caches already cleared by the first @beartype-decorated # redefinition of a class in that module. Since doing so would be # overly aggressive and thus inefficient, avoiding doing so improves # efficiency in the common case of module redefinition. _BEARTYPED_MODULE_TO_TYPE_NAME.clear() # Set of the unqualified basenames of *ALL* classes in that module # previously decorated by this decorator, redefined *AFTER* clearing # that set above to enable the addition of this type back to this # new set below. Nobody ever said type-checking was gonna be easy. type_names_beartyped = _BEARTYPED_MODULE_TO_TYPE_NAME[module_name] # Clear *ALL* type-checking caches. Notably: # * The forward reference referee cache (i.e., private # "beartype._check.forward.reference.fwdrefmeta._forwardref_to_referee" # dictionary) is problematic, due to mapping from forward # reference proxies (which are themselves classes) to arbitrary # (and thus usually user-defined) classes -- one or more of which # might be this class or other similarly redefined classes. # * The type hint coercion cache (i.e., private # "beartype._check.convert.convcoerce._hint_repr_to_hint" # dictionary) is problematic, due to mapping from the # machine-readable representations of previously seen # non-self-cached type hints (e.g., "list[MuhClass]") to the first # seen instance of those hints (e.g., list[MuhClass]). Since this # class has been redefined, the first seen instance of those hints # could contain a reference to the first definition of this class. # # If any of these caches contain such desynchronized key-value # pairs, there now exists a discrepancy between the current # definition of this class and existing references in these caches # to the prior definition of this class. For safety, all caches # possibly containing those references must now be assumed to be # invalid. Failing to clear these caches causes @beartype-decorated # wrapper functions to raise erroneous type-checking violations. clear_checker_caches() # Else, this is the first decoration of this class by this decorator. # Record that this class has now been decorated by this decorator. # Technically, this should (probably) be performed *AFTER* this # decorator has actually successfully decorated this class. # Pragmatically, doing so here is simply faster and... simpler. type_names_beartyped.add(type_name) # Else, this class is *NOT* defined by a module. beartype-0.18.5/beartype/_decor/decorcache.py000066400000000000000000000152121461113517100211210ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Memoized beartype decorator.** This private submodule defines the core :func:`beartype.beartype` decorator, conditionally imported (in order): #. Into the parent :mod:`beartype._decor.decormain` submodule if this decorator is *not* currently reducing to a noop (e.g., due to ``python3 -O`` optimization). #. Into the root :mod:`beartype.__init__` submodule if the :mod:`beartype` package is *not* currently being installed by :mod:`setuptools`. This private submodule is literally the :func:`beartype.beartype` decorator, despite *not* actually being that decorator (due to being unmemoized). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeConfException from beartype.typing import ( Dict, Optional, ) from beartype._conf.confcls import ( BEARTYPE_CONF_DEFAULT, BeartypeConf, ) from beartype._data.hint.datahinttyping import ( BeartypeConfedDecorator, BeartypeReturn, BeartypeableT, ) from beartype._decor.decorcore import beartype_object # ....................{ DECORATORS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Synchronize the signature of this non-identity decorator with the # identity decorator defined by the "beartype._decor.decormain" submodule. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: The parent "beartype._decor.decormain" submodule intentionally # defines the docstring for this decorator. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! def beartype( # Optional positional or keyword parameters. obj: Optional[BeartypeableT] = None, # Optional keyword-only parameters. *, conf: BeartypeConf = BEARTYPE_CONF_DEFAULT, ) -> BeartypeReturn: # If "conf" is *NOT* a configuration, raise an exception. if not isinstance(conf, BeartypeConf): raise BeartypeConfException( f'{repr(conf)} not beartype configuration.') # Else, "conf" is a configuration. # # If passed an object to be decorated, this decorator is in decoration # rather than configuration mode. In this case, decorate this object with # type-checking configured by this configuration. # # Note this branch is typically *ONLY* entered when the "conf" parameter # is *NOT* explicitly passed and thus defaults to the default # configuration. While callers may technically run this decorator in # decoration mode with a non-default configuration, doing so would be both # highly irregular *AND* violate PEP 561-compliance by violating the # decorator overloads declared above. Nonetheless, we're largely permissive # here; callers that are doing this are sufficiently intelligent to be # trusted to violate PEP 561-compliance if they so choose. So... *shrug* elif obj is not None: return beartype_object(obj, conf) # Else, we were passed *NO* object to be decorated. In this case, this # decorator is in configuration rather than decoration mode. # Private decorator (possibly previously generated and cached by a prior # call to this decorator also in configuration mode) generically applying # this configuration to any beartypeable object passed to that decorator # if a prior call to this public decorator has already been passed the same # configuration (and thus generated and cached this private decorator) *OR* # "None" otherwise (i.e., if this is the first call to this public # decorator passed this configuration in configuration mode). Phew! beartype_confed_cached = _bear_conf_to_decor.get(conf) # If a prior call to this public decorator has already been passed the same # configuration (and thus generated and cached this private decorator), # return this private decorator for subsequent use in decoration mode. if beartype_confed_cached: return beartype_confed_cached # Else, this is the first call to this public decorator passed this # configuration in configuration mode. # Define a private decorator generically applying this configuration to any # beartypeable object passed to this decorator. def beartype_confed(obj: BeartypeableT) -> BeartypeableT: ''' Decorate the passed **beartypeable** (i.e., pure-Python callable or class) with optimal type-checking dynamically generated unique to that beartypeable under the beartype configuration passed to a prior call to the :func:`beartype.beartype` decorator. Parameters ---------- obj : BeartypeableT Beartypeable to be decorated. Returns ---------- BeartypeableT Either: * If the passed object is a class, this existing class embellished with dynamically generated type-checking. * If the passed object is a callable, a new callable wrapping that callable with dynamically generated type-checking. See Also ---------- :func:`beartype.beartype` Further details. ''' # Decorate this object with type-checking configured by this # configuration. return beartype_object(obj, conf) # Cache this private decorator against this configuration. _bear_conf_to_decor[conf] = beartype_confed # Return this private decorator. return beartype_confed # ....................{ SINGLETONS }.................... _bear_conf_to_decor: Dict[BeartypeConf, BeartypeConfedDecorator] = {} ''' Non-thread-safe **beartype decorator cache.** This cache is implemented as a singleton dictionary mapping from each **beartype configuration** (i.e., self-caching dataclass encapsulating all flags, options, settings, and other metadata configuring the current decoration of the decorated callable or class) to the corresponding **configured beartype decorator** (i.e., closure created and returned from the :func:`beartype.beartype` decorator when passed a beartype configuration via the optional ``conf`` parameter rather than an object to be decorated via the optional ``obj`` parameter). Caveats ---------- **This cache is not thread-safe.** Although rendering this cache thread-safe would be trivial, doing so would needlessly reduce efficiency. This cache is merely a runtime optimization and thus need *not* be thread-safe. ''' beartype-0.18.5/beartype/_decor/decorcore.py000066400000000000000000000255721461113517100210200ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Unmemoized beartype decorators** (i.e., core lower-level unmemoized decorators underlying the higher-level memoized :func:`beartype.beartype` decorator, whose implementation in the parent :mod:`beartype._decor.decorcache` submodule is a thin wrapper efficiently memoizing closures internally created and returned by that decorator; in turn, those closures directly defer to this submodule). This private submodule is effectively the :func:`beartype.beartype` decorator despite *not* actually being that decorator (due to being unmemoized). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeException from beartype._conf.confcls import BeartypeConf from beartype._data.hint.datahinttyping import ( BeartypeableT, TypeWarning, ) from beartype._decor._decornontype import beartype_nontype from beartype._decor._decortype import beartype_type from beartype._util.cls.utilclstest import is_type_subclass from beartype._util.error.utilerrwarn import issue_warning from beartype._util.text.utiltextlabel import ( label_exception, label_object_context, ) from beartype._util.text.utiltextmunge import ( truncate_str, uppercase_str_char_first, ) from beartype._util.text.utiltextprefix import prefix_beartypeable from traceback import format_exc from warnings import warn # ....................{ DECORATORS }.................... def beartype_object( # Mandatory parameters. obj: BeartypeableT, conf: BeartypeConf, # Variadic keyword parameters. **kwargs ) -> BeartypeableT: ''' Decorate the passed **beartypeable** (i.e., caller-defined object that may be decorated by the :func:`beartype.beartype` decorator) with optimal type-checking dynamically generated unique to that beartypeable. Parameters ---------- obj : BeartypeableT **Beartypeable** (i.e., pure-Python callable or class) to be decorated. conf : BeartypeConf **Beartype configuration** (i.e., dataclass encapsulating all flags, options, settings, and other metadata configuring the current decoration of the decorated callable or class). All remaining keyword parameters are passed as is to whichever lower-level decorator this higher-level decorator calls on the passed beartypeable. Returns ---------- BeartypeableT Either: * If the passed object is a class, this existing class embellished with dynamically generated type-checking. * If the passed object is a callable, a new callable wrapping that callable with dynamically generated type-checking. See Also ---------- :func:`beartype._decor.decormain.beartype` Memoized parent decorator wrapping this unmemoized child decorator. ''' # print(f'Decorating object {repr(obj)}...') # Return either... return ( _beartype_object_fatal(obj, conf=conf, **kwargs) # If this beartype configuration requests that this decorator raise # fatal exceptions at decoration time, defer to the lower-level # decorator doing so; if conf.warning_cls_on_decorator_exception is None else # Else, this beartype configuration requests that this decorator emit # fatal warnings at decoration time. In this case, defer to the # lower-level decorator doing so. _beartype_object_nonfatal(obj, conf=conf, **kwargs) ) # ....................{ PRIVATE ~ decorators }.................... def _beartype_object_fatal(obj: BeartypeableT, **kwargs) -> BeartypeableT: ''' Decorate the passed **beartypeable** (i.e., caller-defined object that may be decorated by the :func:`beartype.beartype` decorator) with optimal type-checking dynamically generated unique to that beartypeable. Parameters ---------- obj : BeartypeableT **Beartypeable** (i.e., pure-Python callable or class) to be decorated. All remaining keyword parameters are passed as is to a lower-level decorator defined by this submodule (e.g., :func:`.beartype_func`). Returns ---------- BeartypeableT Either: * If the passed object is a class, this existing class embellished with dynamically generated type-checking. * If the passed object is a callable, a new callable wrapping that callable with dynamically generated type-checking. See Also ---------- :func:`beartype._decor.decormain.beartype` Memoized parent decorator wrapping this unmemoized child decorator. ''' # Return either... return ( # If this object is a class, this class decorated with type-checking. beartype_type(obj, **kwargs) # type: ignore[return-value] if isinstance(obj, type) else # Else, this object is a non-class. In this case, this non-class # decorated with type-checking. beartype_nontype(obj, **kwargs) # type: ignore[return-value] ) #FIXME: Unit test us up, please. def _beartype_object_nonfatal( # Mandatory parameters. obj: BeartypeableT, conf: BeartypeConf, # Variadic keyword parameters. **kwargs ) -> BeartypeableT: ''' Decorate the passed **beartypeable** (i.e., pure-Python callable or class) with optimal type-checking dynamically generated unique to that beartypeable and any otherwise uncaught exception raised by doing so safely coerced into a warning instead. Motivation ---------- This decorator is principally intended to be called by our **import hook API** (i.e., public functions exported by the :mod:`beartype.claw` subpackage). Raising detailed exception tracebacks on unexpected error conditions is: * The right thing to do for callables and classes manually type-checked with the :func:`beartype.beartype` decorator. * The wrong thing to do for callables and classes automatically type-checked by import hooks installed by public functions exported by the :mod:`beartype.claw` subpackage. Why? Because doing so would render those import hooks fragile to the point of being practically useless on real-world packages and codebases by unexpectedly failing on the first callable or class defined *anywhere* under a package that is not type-checkable by :func:`beartype.beartype` (whether through our fault or that package's). Instead, the right thing to do is to: * Emit a warning for each callable or class that :func:`beartype.beartype` fails to generate a type-checking wrapper for. * Continue to the next callable or class. Parameters ---------- obj : BeartypeableT **Beartypeable** (i.e., pure-Python callable or class) to be decorated. conf : BeartypeConf **Beartype configuration** (i.e., dataclass encapsulating all flags, options, settings, and other metadata configuring the current decoration of the decorated callable or class). All remaining keyword parameters are passed as is to the lower-level :func:`._beartype_object_fatal` decorator internally called by this higher-level decorator on the passed beartypeable. Returns ---------- BeartypeableT Either: * If :func:`.beartype_object_fatal` raises an exception, the passed object unmodified as is. * If :func:`.beartype_object_fatal` raises no exception: * If the passed object is a class, this existing class embellished with dynamically generated type-checking. * If the passed object is a callable, a new callable wrapping that callable with dynamically generated type-checking. Warns ---------- warning_category If :func:`.beartype_object_fatal` fails to generate a type-checking wrapper for this callable or class by raising a fatal exception, this decorator coerces that exception into a non-fatal warning instead. ''' # Attempt to decorate the passed beartypeable. try: return _beartype_object_fatal(obj, conf=conf, **kwargs) # If doing so unexpectedly raises an exception, coerce that fatal exception # into a non-fatal warning for nebulous safety. except Exception as exception: # Category of warning to be emitted. warning_category: TypeWarning = conf.warning_cls_on_decorator_exception # type: ignore[assignment] assert is_type_subclass(warning_category, Warning), ( f'{repr(warning_category)} not warning category.') # Original lower-level error message to be embedded in the higher-level # warning message to be emitted below, defined as either... error_message = ( # If this exception is beartype-specific, this exception's message # is probably human-readable as is. In this case, maximize brevity # and readability by coercing *ONLY* this message (rather than both # this message *AND* traceback) truncated to a reasonable maximum # length into a warning message. truncate_str(text=label_exception(exception), max_len=1024) if isinstance(exception, BeartypeException) else # Else, this exception is *NOT* beartype-specific. In this case, # this exception's message is probably *NOT* human-readable as is. # Prepend that non-human-readable message by this exception's # traceback for disambiguity and debuggability. Note that the # format_exc() function appends this exception's message to this # traceback and thus suffices as is. format_exc() ) # Indent this message by globally replacing *EVERY* newline in this # message with a newline followed by four spaces. Doing so visually # offsets this lower-level exception message from the higher-level # warning message embedding this exception message below. error_message = f'\n{error_message}'.replace('\n', '\n ') # Warning message to be emitted, consisting of: # * A human-readable label contextually describing this beartypeable, # capitalized such that the first character is uppercase. # * This indented exception message. warning_message = uppercase_str_char_first( f'{prefix_beartypeable(obj)}{label_object_context(obj)}:' f'{error_message}' ) # Emit this message under this category. issue_warning(cls=warning_category, message=warning_message) # Return this object unmodified, as @beartype failed to successfully wrap # this object with a type-checking class or callable. So it goes, fam. return obj # type: ignore[return-value] beartype-0.18.5/beartype/_decor/decormain.py000066400000000000000000000256171461113517100210140ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Public beartype decorator.** This private submodule defines the core :func:`beartype` decorator, which the :mod:`beartype.__init__` submodule then imports for importation as the public :mod:`beartype.beartype` decorator by downstream callers -- completing the virtuous cycle of code life. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... # All "FIXME:" comments for this submodule reside in this package's "__init__" # submodule to improve maintainability and readability here. # ....................{ IMPORTS }.................... from beartype.typing import ( TYPE_CHECKING, Callable, ) from beartype._conf.confcls import ( BEARTYPE_CONF_DEFAULT, BeartypeConf, ) from beartype._data.hint.datahinttyping import ( BeartypeReturn, BeartypeableT, ) from beartype._util.py.utilpyinterpreter import is_python_optimized # Intentionally import the standard mypy-friendly @typing.overload decorator # rather than a possibly mypy-unfriendly @beartype.typing.overload decorator -- # which, in any case, would be needlessly inefficient and thus bad. from typing import overload # ....................{ OVERLOADS }.................... # Declare PEP 484-compliant overloads to avoid breaking downstream code # statically type-checked by a static type checker (e.g., mypy). The concrete # @beartype decorator declared below is permissively annotated as returning a # union of multiple types desynchronized from the types of the passed arguments # and thus fails to accurately convey the actual public API of that decorator. # See also: # https://www.python.org/dev/peps/pep-0484/#function-method-overloading # # Note that the "Callable[[BeartypeableT], BeartypeableT]" type hint should # ideally instead be a reference to our "BeartypeConfedDecorator" type hint. # Indeed, it used to be. Unfortunately, a significant regression in mypy # required us to inline that type hint away. See also this issue: # https://github.com/beartype/beartype/issues/332 @overload # type: ignore[misc,no-overload-impl] def beartype(obj: BeartypeableT) -> BeartypeableT: ... @overload def beartype(*, conf: BeartypeConf) -> Callable[ [BeartypeableT], BeartypeableT]: ... # ....................{ DECORATORS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: *THE ORDER OF CONDITIONAL STATEMENTS BELOW IS SIGNIFICANT.* Notably, # mypy 0.940 erroneously emits this fatal error when the "TYPE_CHECKING or" # condition is *NOT* the first condition of this "if" statement: # beartype/_decor/main.py:294: error: Condition can't be inferred, unable # to merge overloads [misc] # See also: https://github.com/python/mypy/issues/12335#issuecomment-1065591703 #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # If the active Python interpreter is either... if ( # Running under an external static type checker -- in which case there is # no benefit to attempting runtime type-checking whatsoever... # # Note that this test is largely pointless. By definition, static type # checkers should *NOT* actually run any code -- merely parse and analyze # that code. Ergo, this boolean constant should *ALWAYS* be false from the # runtime context under which @beartype is only ever run. Nonetheless, this # test is only performed once per process and is thus effectively free. TYPE_CHECKING or # Optimized at process invocation time (e.g., at least one "-O" command-line # option was set when this interpreter forked) *OR*... not __debug__ or # Optimized *AFTER* process invocation time (e.g., in an interactive REPL by # the external user). Yes, our awesome userbase actually requested this. is_python_optimized() ): # Then unconditionally disable @beartype-based type-checking across the entire # codebase by reducing the @beartype decorator to the identity decorator. # Ideally, this would have been implemented at the top rather than bottom of # this submodule as a conditional resembling: # if __debug__: # def beartype(func: CallableTypes) -> CallableTypes: # return func # return # # Tragically, Python fails to support module-scoped "return" statements. *sigh* #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Synchronize the signature of this identity decorator with the # non-identity decorator imported below. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! def beartype( # type: ignore[no-redef] obj: BeartypeableT, # pyright: ignore[reportInvalidTypeVarUse] # Optional keyword-only parameters. *, conf: BeartypeConf = BEARTYPE_CONF_DEFAULT, ) -> BeartypeReturn: return obj # Else, the active Python interpreter is in a standard runtime state. In this # case, define the @beartype decorator in the standard way. else: # This is where @beartype *REALLY* lives. Grep here for all the goods. from beartype._decor.decorcache import beartype # ....................{ DECORATORS ~ doc }.................... # Document the @beartype decorator with the same documentation regardless of # which of the above implementations currently implements that decorator. beartype.__doc__ = ( ''' Decorate the passed **beartypeable** (i.e., pure-Python callable or class) with optimal type-checking dynamically generated unique to that beartypeable under the passed beartype configuration. This decorator supports two distinct (albeit equally efficient) modes of operation: * **Decoration mode.** The caller activates this mode by passing this decorator a type-checkable object via the ``obj`` parameter; this decorator then creates and returns a new callable wrapping that object with optimal type-checking. Specifically: * If this object is a callable, this decorator creates and returns a new **runtime type-checker** (i.e., pure-Python function validating all parameters and returns of all calls to that callable against all PEP-compliant type hints annotating those parameters and returns). The type-checker returned by this decorator is: * Optimized uniquely for the passed callable. * Guaranteed to run in ``O(1)`` constant-time with negligible constant factors. * Type-check effectively instantaneously. * Add effectively no runtime overhead to the passed callable. * If the passed object is a class, this decorator iteratively applies itself to all annotated methods of this class by dynamically wrapping each such method with a runtime type-checker (as described previously). * **Configuration mode.** The caller activates this mode by passing this decorator a beartype configuration via the ``conf`` parameter; this decorator then creates and returns a new beartype decorator enabling that configuration. That decorator may then be called (in decoration mode) to create and return a new callable wrapping the passed type-checkable object with optimal type-checking configured by that configuration. If optimizations are enabled by the active Python interpreter (e.g., due to option ``-O`` passed to this interpreter), this decorator silently reduces to a noop. Parameters ---------- obj : Optional[BeartypeableT] **Beartypeable** (i.e., pure-Python callable or class) to be decorated. Defaults to ``None``, in which case this decorator is in configuration rather than decoration mode. In configuration mode, this decorator creates and returns an efficiently cached private decorator that generically applies the passed beartype configuration to any beartypeable object passed to that decorator. Look... It just works. conf : BeartypeConf, optional **Beartype configuration** (i.e., self-caching dataclass encapsulating all settings configuring type-checking for the passed object). Defaults to ``BeartypeConf()``, the default ``O(1)`` constant-time configuration. Returns ---------- BeartypeReturn Either: * If in decoration mode (i.e., ``obj`` is *not* ``None` while ``conf`` is ``None``) *and*: * If ``obj`` is a callable, a new callable wrapping that callable with dynamically generated type-checking. * If ``obj`` is a class, this existing class embellished with dynamically generated type-checking. * If in configuration mode (i.e., ``obj`` is ``None` while ``conf`` is *not* ``None``), a new beartype decorator enabling this configuration. Raises ---------- BeartypeConfException If the passed configuration is *not* actually a configuration (i.e., instance of the :class:`BeartypeConf` class). BeartypeDecorHintException If any annotation on this callable is neither: * A **PEP-compliant type** (i.e., instance or class complying with a PEP supported by :mod:`beartype`), including: * :pep:`484` types (i.e., instance or class declared by the stdlib :mod:`typing` module). * A **PEP-noncompliant type** (i.e., instance or class complying with :mod:`beartype`-specific semantics rather than a PEP), including: * **Fully-qualified forward references** (i.e., strings specified as fully-qualified classnames). * **Tuple unions** (i.e., tuples containing one or more classes and/or forward references). BeartypePep563Exception If :pep:`563` is active for this callable and evaluating a **postponed annotation** (i.e., annotation whose value is a string) on this callable raises an exception (e.g., due to that annotation referring to local state no longer accessible from this deferred evaluation). BeartypeDecorParamNameException If the name of any parameter declared on this callable is prefixed by the reserved substring ``__beartype_``. BeartypeDecorWrappeeException If this callable is either: * Uncallable. * A class, which :mod:`beartype` currently fails to support. * A C-based callable (e.g., builtin, third-party C extension). BeartypeDecorWrapperException If this decorator erroneously generates a syntactically invalid wrapper function. This should *never* happen, but here we are, so this probably happened. Please submit an upstream issue with our issue tracker if you ever see this. (Thanks and abstruse apologies!) ''' ) beartype-0.18.5/beartype/_decor/wrap/000077500000000000000000000000001461113517100174375ustar00rootroot00000000000000beartype-0.18.5/beartype/_decor/wrap/__init__.py000066400000000000000000001047221461113517100215560ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. # ....................{ TODO }.................... #FIXME: Major optimization: duplicate the signature of the decorated callable #as the signature of our wrapper function. Why? Because doing so obviates the #need to explicitly test whether each possible parameter was passed and how #that parameter was passed (e.g., positional, keyword) as well as the need to #localize "__beartype_args_len" and so on. In short, this is a massive win. #Again, see the third-party "makefun" package, which purports to already do so. #FIXME: Cray-cray optimization: don't crucify us here, folks, but eliminating #the innermost call to the original callable in the generated wrapper may be #technically feasible. It's probably a BadIdea™, but the idea goes like this: # # # Source code for this callable as a possibly multiline string, # # dynamically parsed at runtime with hacky regular expressions from # # the physical file declaring this callable if any *OR* "None" otherwise # # (e.g., if this callable is defined dynamically or cannot be parsed from # # that file). # func_source = None # # # Attempt to find the source code for this callable. # try: # func_source = inspect.getsource(func) # # If the inspect.getsource() function fails to do so, shrug. # except OSError: # pass # # # If the source code for this callable cannot be found, fallback to # # simply calling this callable in the conventional way. # if func_source is None: # #FIXME: Do what we currently do here. # # Else, the source code for this callable was found. In this case, # # carefully embed this code into the code generated for this wrapper. # else: # #FIXME: Do something wild, crazy, and dangerous here. # #Extreme care will need to be taken, including: # #* Ensuring code is indented correctly. #* Preserving the signature (especially with respect to passed parameters) of # the original callable in the wrapper. See the third-party "makefun" package, # which purports to already do so. So, this is mostly a solved problem -- # albeit still non-trivial, as "beartype" will never have dependencies. #* Don't bother reading any of this. Just skip to the synopsis below: # * Preventing local attributes defined by this wrapper as well as global # attributes imported into this wrapper's namespace from polluting the # namespace expected by the original callable. The former is trivial; simply # explicitly "del {attr_name1},...,{attr_nameN}" immediately before # embedding the source code for that callable. The latter is tricky; we'd # probably want to stop passing "globals()" to exec() below and instead pass # a much smaller list of attributes explicitly required by this wrapper. # Even then, though, there's probably no means of perfectly insulating the # original code from all wrapper-specific global attributes. Or: # * Perhaps this isn't an issue? After all, *ALL* locals and globals exposed # to decorated callables are now guaranteed to be "__bear"-prefixed. This # implies that searching the body of the decorated callable for the # substring "\b__bear" and then raising an exception if any such # substrings are found should suffice to prevent name collision. # * Rewriting return values and yielded values. Oh, boy. That's the killer, # honestly. Regular expression-based parsing only gets us so far. We could # try analyzing the AST for that code, but... yikes. Each "return" and # "yield" statement would need to be replaced by a beartype-specific # "return" or "yield" statement checking the types of the values to be # returned or # yielded. We can guarantee that that rapidly gets cray-cray, especially # when implementing non-trivial PEP 484-style type checking requiring # multiple Python statements and local variables and... yeah. Actually: # * Why *CAN'T* regex-based parsing suffice? Python's Backus-Naur form (BNF) # is almost certainly quite constrained. We'll have to check where exactly # "return" and "yield" statements are permissible, but we're fairly sure # they're permissible only after newlines followed by sufficient # indentation. # * Note that the objects produced by Python's standard "ast" *AND* "dis" # modules contain line number attributes yielding the line numbers on # which those syntactic object were parsed. Ergo, whichever of these is # the more efficient almost certainly the simplest (and possibly even) # fastest approach. Is this worth benchmarking? Perhaps we should simply # adopt the "ast" approach, as that's likely to be substantially more # robust *AND* generalize to the case of annotated local variables, where # naive regexes (and probably "dis" as well) fall down. Of course, "dis" # is likely to be *MUCH* more space- and time-performant than "ast". # * *SIGH.* Yes, absolutely use the standard "ast" module. Absolutely do # *NOT* use either hand-rolled regexes or the standard "dis" module. Why? # Because: # * The low-level internals of the "ast" module are implemented in C. That # means it's likely to be fast enough for our purposes. # * CPython *ALREADY* has to do all (or at least, enough) of the AST # analysis performed by the "ast" module. Since that cost has to be paid # anyway, we'd might as well avoid paying additional regex or "dis" # costs by reusing "ast" with @beartype. Oh, wait... No, that's not how # things work at all. You basically can't reverse-engineer an AST from a # callable code object. Since Python doesn't preserve the AST it # internally produces to generate byte-code for a callable on that # callable, we have no choice but to: # * Get the source for that callable (e.g., with dill.source.getsource() # or inspect.getsource()). # * Pass that source string to ast.parse(). Man, that sure blows chunks. # * So, ignore the prior point. The only additional meaningful point is # that, unlike the "dis" module, the "ast" module makes it trivial to: # * Transform the produced AST by injecting additional nodes (e.g., # dynamically generated statements) into the AST. # * Compile that AST down into a code object. # Does any of the above help us? Maybe not. All we really need from "ast" # and "dis" are line numbers and the ability to crudely identify: # * "return" statements. "dis" trivially does this. # * "yield" statements. "dis" trivially does this. # * Local annotated variable assignments. "dis" *PROBABLY* does not # trivially do this. Indeed, it's not necessarily clear that "ast" does # this either. Actually, that's absolutely *NOT* true. "ast" appears to # trivially detect local annotated variable assignments, which is nice. # Hilariously, regexes *DO* trivially detect local annotated variable # assignments, because that's just a search for # r"\n\s*[a-zA-Z_][a-zA-Z0-9_]*\s*:". Like, seriously. That's by far the # easiest way to do that. Detecting "return" and "yield" statements is # similarly trivial (we think, anyway) with regexes. # *WAIT.* Regexes may vary well detect the *START* of a local annotated # variable assignment, but they clearly fail to detect the *END*, as that # requires context-free parsing. Welp. That's the death-knell for both # regexes and "dis", then. "ast" is it! # #In synopsis, don't bother reading the above. Just know that parsing "return" #and "yield" statements as well as annotated local variable assignments #unsurprisingly requires use of the standard "ast" module. Specifically: #* Get the source for the decorated callable. Ideally, we'll want to do so by # implementing our own get_callable_source() utility getter inspired by the # third-party "dill" implementation at dill.source.getsource() rather than the # standard inspect.getsource(). #* Pass that source string to ast.parse(). Note that the following snippet # appears to be the most robust means of doing so, as it implicitly accounts # for encoding issues that we do *NOT* want to concern ourselves with: # import ast # import tokenize # # def parse_file(filename): # with tokenize.open(filename) as f: # return ast.parse(f.read(), filename=filename) # Please cite the original source for this, which is this blog article: # https://julien.danjou.info/finding-definitions-from-a-source-file-and-a-line-number-in-python #* Search the resulting AST for any nodes referencing an object name (e.g., # variable, callable, class) prefixed by "__bear" and raise an exception on # the first such node to prevent name collision. #* Munge that AST as required. #* Compile that AST -- ideally directly into a callable (but possibly first # indirectly into a code object into then that directly into a callable). # #I suppose we could gradually roll out support by (in order): #* Initially duplicating the signature of the decorated callable onto the # wrapper function. Since this is both a hard prerequisite for all subsequent # work *AND* yields tangible benefits in and of itself (e.g., for runtime # introspection), this is absolutely the first big ticket item here. Note that # several approaches exist here: # * Programmatically reconstruct this signature. This is almost certainly the # optimal technique. # * Use "ast" to find the line interval for the signature of the decorated # callable in its source file. # * Use "dis" to find the same. # # Note that this is complicated by default values, which will need to be # propagated from the decorated callable onto the wrapper function. As we # recall, the "callable.__defaults__" dunder variable contains these defaults, # so that's probably trivial. Just copy that variable, right? Similarly, the # "callable.__annotations__" dunder variable should also be propagated. # # Actually, just see the standard inspect._signature_from_function() function, # which implements the core callable signature parsing logic. Alternately, I # believe we'd previously found a third-party library or two whose sole reason # for existence was parsing and duplicating callable signatures, wasn't it? #* Then optimizing callables annotated by either no return type hint *OR* a # deeply ignorable return hint, which reduces to a significantly simpler edge # case requiring *NO* "ast" use. #* Then optimizing callables returning and yielding nothing by falling back to # the unoptimized approach for callables that do so. #* Then optimizing callables terminating in a single "return" or "yield" # statement that *DIRECTLY* return a local or global variable. This is the # easy common case, as we can then immediately precede that statement with a # type-check on that variable. #* Then optimizing callables terminating in a single "return" or "yield" # statement that return an arbitrary expression. If that expression is *NOT* a # local or global variable, we need to capture that expression into a new # local variable *BEFORE* type-checking that variable *BEFORE* returning that # variable. So it goes. #* Then optimizing callables containing multiple such statements. # #Note lastly that the third-party "dill" package provides a #dill.source.getsource() function with the same API as the stdlib #inspect.getsource() function but augmented in various favourable ways. *shrug* # #Although this will probably never happen, it's still mildly fun to ponder. #FIXME: Actually, this should probably happen -- but not necessarily for the #reasons stipulated above. Don't get us wrong; optimizing away the additional #stack frame by embedding the body of the decorated callable directly into the #wrapper function wrapping that callable is a clever (albeit highly #non-trivial) optimization. # #The *REAL* tangible benefit, however, is in type-checking annotated local #variables. Currently, neither @beartype nor any other runtime type checker has #the means to check annotated local variables: e.g., # @beartype # def muh_func(muh_list: list[int]) -> int: # list_item: int = list[0] # <- can't check this # return list_item # #The reason, of course, is that those variables and thus variable annotations #are effectively "locked" behind the additional stack frame separating the #decorated callable from its wrapper function. Integrating the former into the #latter, however, trivially dissolves this barrier; indeed, since Python #currently has no notion of a variable decorator and prohibits function return #values from being assigned to as l-values, there is no pragmatic alternative. # #The idea here is that we could augment the body of the decorated callable when #merged into its wrapper function as follows: #* Iteratively search that body for local annotated variable declarations. #* For each such declaration: # * Inject one or more statements after each such declaration type-checking # that variable against its annotation. # #The issue here then becomes: *WHERE* after each such declaration? This is a #pertinent question, because we could type-check a variable immediately after #its declaration, only to have a subsequent assignment to that variable later #in the body of the decorated callable silently invalidate the prior #type-check. Technically, since @beartype is an O(1) type-checker, we could #re-perform type-checks after each assignment to an annotated local variable. #But that seems a bit heavy-handed. Perhaps we should simply inject that code #at the last possible moment -- which is to say, immediately *BEFORE* each #"return" or "yield" statement in that callable. We have to inject code there #anyway to type-check that "return" or "yield" statement, so we'd be hitting #two birds with one beating stick to additionally type-check annotated local #variables there as well. # #Note that the answer to where we type-check local variables has a profound #impact on whether we adopt a regex- or "ast"-based solution. If we type-check #everything before "return" or "yield" statements, regex suffices. If we check #variables immediately after their declaration or assignment, however, only #"ast" suffices. This is, of course, yet another point in favour of checking #everything before "return" or "yield" statements, as regex is likely to be #substantially faster and more portable (due to changes in "ast" design and #implementation across Python versions) than the "ast"-based approach. # #For example, this regex should (in theory) suffice to detect all annotated #local variable declarations in a callable: r"\n\s+[a-zA-Z_][a-zA-Z0-9_]*\s*:". #Oh... wait. No. Even that doesn't generalize. Why? Literal triple-quoted #strings, obviously. Welp. "ast" it is, then! No point in beating around that #context-free bush then, is there? Consider using the third-party "astor" #package if available, which purportedly improves upon the standard "ast" #module in various ways and is internally leveraged by "pylint" to perform its #magic. In any case, Relevant articles include: #* "Static Modification of Python With Python: The AST Module", a well-written # introduction to the topic: # https://dzone.com/articles/static-modification-python # #Note that we have two significant high-level choices here: #* Use the "ast" module just to obtain line number intervals for the desired # statements. Note that the existence of the rarely used optional statement # terminator ";" makes this less trivial than desired. We can't simply assume # that statements begin and end on newlines, for example. Instead, we need to # employ either the Python >= 3.8-specific ast.get_source_segment() function # *OR* the Python >= 3.8-specific "end_lineno" and "end_col_offset" attributes # of AST nodes. In either case, Python >= 3.8 will absolutely be required. #* Use the "ast" to dynamically transform the AST itself. This is considerably # less trivial *AND* invites significant issues. Sanely transforming the AST # would probably require refactoring our entire workflow to generate new # low-level AST nodes rather than new high-level Python code. Issues include: # * Efficiency. "ast" is both space- and time-inefficient, given both the # large number of objects it creates *AND* the inherent inefficiency of # binary trees as O(n log n) structures. # * Portably. "ast" commonly changes in significant ways between major Python # versions, casting doubts on our ability to reasonably port code # transforming the AST between major Python versions, which is unacceptable. # #Actually, we'll probably end up combining the two approaches above. We #definitely *WILL* want to apply trivial AST transformations, including: #* For "return" and "yield" statements, we'll need to split the AST nodes # representing those statements into at least three nodes plus a few new ones: # * The AST node representing each "return" and "yield" statement should be # transformed into a node instead localizing that statement's expression # into a new local variable named "__beartype_pith_0". # * Adding a new AST node returning or yielding the value of that variable. # #We can't reasonably do that transformation by any other means. Note that this #then requires calling the Python >= 3.9-specific ast.unparse() function to #losslessly generate source code from that transformed tree, which we then #split into lines and inject our desired code after the desired line number #corresponding to each shifted "return" and "yield" statement. # #After performing that hopefully simple transform, we then get the line number #of the new AST node returning or yielding the value of that variable and then #manually inject our code type-checking "__beartype_pith_0" there. Phew! # #Alternately, rather than ast.unparse() AST back into source code, we might #instead try injecting AST nodes that we auto-generate by: #* Passing our code type-checking the current "return" or "yield" statement to # the ast.parse() function. #* Inject the target sub-AST returned by that call into the desired node of # the source full AST of the decorated callable. Note that this will probably # require prefixing the body of the decorated callable with our parameter # type-checking code *BEFORE* parsing that body with ast.parse(), to ensure # that references in our code type-checking the current "return" or "yield" # statement are properly resolved when merged back into the full AST. #FIXME: Lastly, note that the above is likely to make beartype's #decoration-time cost prohibitive under CPython, regardless of the call-time #improvements due to stack frame compaction. Ergo, we may want to adopt the #following defaults: #* Under PyPy, *ENABLE* AST modification by default. #* Under all other interpreters (especially including CPython), *DISABLE* AST # modification by default. # #Naturally, profile this to decide what we should do. To facilitate choice, #we'll need to refactor the @beartype decorator to support a new optional #"is_ast" parameter defaulting to something resembling these defaults. When #this parameter is false, @beartype defaults to the current approach; else, #@beartype modifies the AST of decorated callables as above. #FIXME: *AH HA!* We just realized that the prior AST approach can be #significantly optimized to a degree that might make this reasonably tractable #under CPython as well. How? As follows (in order): #* Dynamically synthesize the *PRELIMINARY* body of the wrapper function from # (in order): # * Code declaring the signature of the wrapper function. Note that we # *SHOULD* (in theory) be able to trivially extract this *WITHOUT* needing # to programmatically generate this ourselves this by performing a # preliminary walk over the AST of the decorated callable for the node(s) # responsible for declaring that callable's signature. Hopefully trivial. # Why? Because AST nodes provide line number ranges, which leads directly to # trivial extraction of callable signatures. That said... we probably # already need to programmatically generate signatures ourselves for the # common edge case in which the decorated callable is *NOT* annotated by a # return type hint. So, who knows! # * Code typing-checking all parameters, as above. # * Code typing-checking the "return" value. Don't worry about "yield" # statements for now. *YES,* we are intentionally type-checking the "return" # early in the body of the wrapper function. Why? So that we can have the # "ast" module generate a full AST tree containing a node performing that # type-check. Of course, that node will *NOT* be in the correct node # position. But that's fine. A subsequent step will shift that node to its # desired final position in the AST. This code should resemble: # __beartype_pith_0 = True # if ({code_checking_beartype_pith_0_value_here}): # raise {code_raising_beartype_pith_0_exception_here} # This is, of course, valid code that should generate valid AST nodes. # * The body of the decorated callable. #* Parse that preliminary body of the wrapper function through the ast.parse() # function, producing an AST. #* Transform that AST as follows: # * Iteratively walk that AST until finding a node assigning "True" to # "__beartype_pith_0". This shouldn't be troublesome. # * Extract both that node and the subsequent node subtree consisting of the # type-check and exception raising out of their current position in the AST. # Naturally, save these two nodes for subsequent reinsertion back into the # AST at a different position. # * Iteratively walk the remainder of the AST until finding a node performing # a return. # * Inject the two previously extracted nodes into that node position. # * Repeat until all "return" statements have been transformed. # * Voila! #* Compile that AST directly into a code object by calling the ast.compile() # function. #* Evaluate that code object by calling either the exec() or eval() builtin to # produce the actual wrapper function. # #Note that there is a significant annoyance associated with AST #transformations: *LINE NUMBERS.* Specifically, the ast.compile() function #called above absolutely requires that line numbers be coherent (i.e., #monotonically increase). To ensure this, we'll need to "fix up" line numbers #for basically *ALL* nodes following those representing the code #typing-checking all parameters (whose line numbers require no modification). #This is annoying but inexpensive, given that we have to walk all nodes anyway. #Note that the "ast" modules provides functions for repairing line numbers as #well (e.g., ast.increment_lineno()), but that those functions are almost #certainly inefficient and inapplicable for us. # #Note that the ast.copy_location() function appears to already do a *BIT* of #what we need. Since we need cutting instead of copying, however, we'll #probably just want to use that function's implementation as inspiration rather #than directly calling that function. # #And... don't get us wrong. This is absolutely still going to be expensive. But #the fact that we can flow directly from: # decorated callable -> source code -> AST -> code object -> wrapper func #...does imply that this should be considerable faster than previously thought. #FIXME: We just realized that there's a significant optimization here that #renders stack frame reduction unconditionally worthwhile across all Python #interpreters and versions in a simple common case: callables annotated either #with no return type hints *OR* deeply ignorable type hints. Why? Because we #can trivially eliminate the additional stack frame in this edge case by #unconditionally prefixing the body of the decorated callable by (in order): # #1. Code type-checking parameters passed to that callable. #2. Code deleting *ALL* beartype-specific "__bear"-prefixed locals and globals # referenced by the code type-checking those parameters. This is essential, # as it implies that we then no longer need to iteratively search the body of # the decorated callable for local variables with conflicting names, which # due to strings we can't reliably do without "ast"- or "dis"-style parsing. # #Note this edge case only applies to callables: #* Whose return hint is either: # * Unspecified. # * Deeply ignorable. # * "None", implying this callable to return nothing. Callables explicitly # returning a "None" value should instead be annotated with a return hint of # "beartype.cave.NoneType"; this edge case would *NOT* apply to those. #* *DIRECTLY* decorated by @beartype: e.g., # @beartype # def muh_func(): pass # This edge case does *NOT* apply to callables directly decorated by another # decorator first, as in that case the above procedure would erroneously # discard the dynamic decoration of that other decorator: e.g., # @beartype # @other_decorator # def wat_func(): pass #* *NOT* implicitly transformed by one or more other import hooks. If any other # import hooks are in effect, this edge case does *NOT* apply, as in that case # the above procedure could again erroneously discard the dynamic # transformations applied by those other import hooks. #FIXME: *GENERALIZATION:* All of the above would seem to pertain to a #prospective higher-level package, which has yet to be officially named but #which we are simply referring to as "beartypecache" for now. "beartypecache" #has one dependency: unsurprisingly, this is "beartype". The principal goal of #"beartypecache" is *NOT* to perform AST translations as detailed above, #although that certainly is a laudable secondary goal. # #The principal goal of "beartypecache" is, as the name suggests, to cache #wrapper functions dynamically generated by the @beartype decorator across #Python processes. This goal succinctly ties in to the above AST transform #concepts, because the *ONLY* sane means of performing these transforms (even #under PyPy and similarly fast Python environments) is to cache the results of #these transformations across Python processes. # #The underlying idea here is that the @beartype decorator only needs to be #applied once to each version of a callable. If that callable has not changed #since the last application of @beartype to that decorator (or since @beartype #itself has changed, obviously), then the previously cached application of #@beartype to the current version of that callable suffices. Naturally, of #course, there exists *NO* efficient means of deciding when a callable has #changed over multiple Python invocations. There does, however, exist an #efficient means of deciding when an on-disk module defining standard callables #has changed: the "__pycache__" directory formalized by "PEP 3147 -- PYC #Repository Directories" at: # https://www.python.org/dev/peps/pep-3147 # #Ergo, we soften the above idea to the following: "The @beartype decorator only #needs to be applied once to each callable defined by each version of a #module." If this sounds like import hooks, you would not be wrong. Sadly, #there currently exists no public API in the stdlib for generically applying #AST transformations via import hooks. But all is not lost, since we'll simply #do it ourselves. In fact, unsurprisingly, this is a sufficiently useful #concept that it's already been done by a number of third-party projects -- the #most significant of which is "MacroPy3": # https://github.com/lihaoyi/macropy # #The "MacroPy3" synopsis reads: # "MacroPy provides a mechanism for user-defined functions (macros) to # perform transformations on the abstract syntax tree (AST) of a Python # program at import time." # #...which is exactly what we need. We certainly are *NOT* going to depend upon #"MacroPy3" as a mandatory dependency, however. Like "beartype" before it, #"beartypecache" should ideally only depend upon "beartype" as a mandatory #dependency. Ideology aside, however, there exists a more significant reason: #"beartypecache" is intended to be brutally fast. That's what the "cache" #means. "MacroPy3" is undoubtedly slow by compare to a highly micro-optimized #variant of that package, because no in the Python world cares about #efficiency -- perhaps justifiably, but perhaps not. Moreover, generalization #itself incurs space and time efficiency costs. We can eliminate those costs by #developing our own internal, private, ad-hoc AST-transform-on-import-hook #implementation micro-optimized for our specific use case. # #Amusingly, even the abandoned prominently references "MacroPy3": # The MacroPy project uses an import hook: it adds its own module finder in # sys.meta_path to hook its AST transformer. # #Note that "sys.meta_path" is *NOT* necessarily the optimum approach for #"beartypecache". Since the @beartype decorator can only, by definition, be #applied to third-party user-defined modules, "sys.meta_path" is might or might #not be overkill for us, because "sys.meta_path" even applies to builtin #stdlib modules. In any case, what we principally care about is the capacity to #directly feed low-level *CODE OBJECTS* (rather than high-level *SOURCE CODE*) #from our AST transformations into some sort of import hook machinery. # #Note this relevant StackOverflow answer: # https://stackoverflow.com/a/43573798/2809027 #The synopsis of that answer reads: # You will also need to examine if you want to use a MetaPathFinder or a # PathEntryFinder as the system to invoke them is different. That is, the # meta path finder goes first and can override builtin modules, whereas the # path entry finder works specifically for modules found on sys.path. #That answer then goes on to succinctly define example implementations of both, #which is ludicrously helpful. Again, we should adopt whichever allows us to #most efficiently generate low-level *CODE OBJECTS* from AST transformations. # #Note that the public importlib.util.source_from_cache(path) function trivially #enables us to obtain the absolute filename of the previously cached byte code #file if any from the absolute filename of any arbitrary Python module. That's #nice. Additionally, note this preamble to PEP 3147: # # Byte code files [in "__pycache__" directories] contain two 32-bit # big-endian numbers followed by the marshaled code object. The 32-bit # numbers represent a magic number and a timestamp. The magic number changes # whenever Python changes the byte code format, e.g. by adding new byte # codes to its virtual machine. This ensures that pyc files built for # previous versions of the VM won't cause problems. The timestamp is used to # make sure that the pyc file match the py file that was used to create it. # When either the magic number or timestamp do not match, the py file is # recompiled and a new pyc file is written. # #Presumably, there exists some efficient programmatic means of deciding from #pure Python whether "the magic number or timestamp do not match" for the byte #code file cached for an arbitrary module. # #We're almost there. We then need some efficient means of deciding whether an #arbitrary byte code file has been instrumented by "beartypecache" yet. #That's... a much tougher nut to crack. We can think of two possible approaches #here, both equally valid but one probably easier to implement than the other. #For each byte code file cached in a "__pycache__" directory, the #"beartypecache" package should either: #* The easiest way *BY FAR* is probably to just emit one 0-byte # "beartypecache"-specific file named # "__pycache__/{module_name}.{python_name}.beartypecache" or something. # There's *NO* way any other package is writing that sort of file, so filename # collisions should in theory be infeasible. Given such a file, the "mtime" of # this file should coincide with that of the source module from which this # file is generated. Indeed, this approach suggests we don't even need to # extract the magic number and timestamp from the byte code file. Nice! So, # this is the way... probably. #* The harder way *BY FAR* is probably to suffix the contents of this file by a # superfluous byte code statement specific to "beartypecache", effectively the # equivalent of: # __beartypecache_is_cached = True # That's more-or-less a noop and more-or-less trivially generated during our # AST transformation of this source module from an import hook. Given that, # we'd then just to need to compare the end of this file with the expected # byte sequence. This *DOES* entail some I/O overhead and considerably more # complexity than the prior approach, however. # #In any case, the above then enables us to efficiently cache @beartype #decorations and AST transformations across an entire codebase as follows: # #* The root "__init__.py" module of the top-level package for downstream # third-party consumers should contain the following import: # import beartypecache.all # As a side effect, the "beartypecache.all" submodule then installs an import # hook globally decorating all callables across all subsequently imported # modules with @beartype as well as applying AST transformations. This is the # default approach. Of course, subsequent revisions could then provide some # degree of configurability via different submodules or subpackages. #* This "beartypecache.all" import hook then confines itself to each # user-defined submodule *OF THE CALLING PACKAGE THAT IMPORTED* # "beartypecache.all". This is critical. We can't simply globally apply the # "beartypecache.all" import hook to *EVERYTHING*, because many callables will # neither be intended nor able to support decoration by @beartype, which has # rather firm views on PEP-compliant type hints and so on. #* For each user-defined submodule of the calling package, this # "beartypecache.all" import hook then performs the following: # * Decide whether the previously cached byte code file for this submodule is # still synchronized with this submodule and has been previously # instrumented by "beartypecache", using one of the above approaches. # * If so, avoid uselessly re-instrumenting this file. # * Else, instrument this file as detailed above. As a first draft # implementation, "beartypecache" should simply: # * Replace the name of each function and method defined in this source # submodule by "__beartype_wrapped_{func_name}". Note this will require a # trivial sort of AST instrumentation. We can't avoid that. # * Define the replacement wrapper function with the name "{func_name}", # thus replacing the original callable with our decorated callable. # This draft implementation efficiently caches @beartype decorations across # the entire codebase, thus serving as a pragmatically useful demonstration # of the underlying concept. # #All in all, this requires funding. Technically feasible, but cray-cray. beartype-0.18.5/beartype/_decor/wrap/_wrapargs.py000066400000000000000000000546641461113517100220150ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator parameter code generator** (i.e., low-level callables dynamically generating Python expressions type-checking all annotated parameters of the callable currently being decorated by the :func:`beartype.beartype` decorator in a general-purpose manner). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... # All "FIXME:" comments for this submodule reside in this package's "__init__" # submodule to improve maintainability and readability here. # ....................{ IMPORTS }.................... from beartype.roar import ( BeartypeDecorHintParamDefaultForwardRefWarning, BeartypeDecorHintParamDefaultViolation, BeartypeDecorHintPepException, BeartypeDecorParamNameException, ) from beartype.roar._roarexc import _BeartypeHintForwardRefExceptionMixin from beartype._check.checkcall import BeartypeCall from beartype._check.checkmake import make_code_raiser_func_pith_check from beartype._check.convert.convsanify import sanify_hint_root_func from beartype._conf.confcls import BeartypeConf from beartype._data.error.dataerrmagic import EXCEPTION_PLACEHOLDER from beartype._data.func.datafuncarg import ARG_NAME_RETURN from beartype._decor.wrap.wrapsnip import ( CODE_INIT_ARGS_LEN, EXCEPTION_PREFIX_DEFAULT, ARG_KIND_TO_CODE_LOCALIZE, ) from beartype._decor.wrap._wraputil import unmemoize_func_wrapper_code from beartype._util.error.utilerrraise import reraise_exception_placeholder from beartype._util.error.utilerrwarn import ( issue_warning, reissue_warnings_placeholder, ) from beartype._util.func.arg.utilfuncargiter import ( ARG_META_INDEX_DEFAULT, ARG_META_INDEX_KIND, ARG_META_INDEX_NAME, ArgKind, ArgMandatory, iter_func_args, ) from beartype._util.hint.utilhinttest import ( is_hint_ignorable, is_hint_needs_cls_stack, ) from beartype._util.kind.map.utilmapset import update_mapping from beartype._util.text.utiltextmunge import lowercase_str_char_first from beartype._util.text.utiltextprefix import ( prefix_callable_arg_name, prefix_pith_value, ) from beartype._util.utilobject import SENTINEL from warnings import catch_warnings # ....................{ CODERS }.................... def code_check_args(bear_call: BeartypeCall) -> str: ''' Generate a Python code snippet type-checking all annotated parameters of the decorated callable if any *or* the empty string otherwise (i.e., if these parameters are unannotated). Parameters ---------- bear_call : BeartypeCall Decorated callable to be type-checked. Returns ------- str Code type-checking all annotated parameters of the decorated callable. Raises ------ BeartypeDecorParamNameException If the name of any parameter declared on this callable is prefixed by the reserved substring ``__bear``. BeartypeDecorHintNonpepException If any type hint annotating any parameter of this callable is neither: * A PEP-noncompliant type hint. * A supported PEP-compliant type hint. ''' assert isinstance(bear_call, BeartypeCall), ( f'{repr(bear_call)} not beartype call.') # ..................{ LOCALS ~ func }.................. # If *NO* callable parameters are annotated, silently reduce to a noop. # # Note that this is purely an optimization short-circuit mildly improving # efficiency for the common case of callables accepting either no # parameters *OR* one or more parameters, all of which are unannotated. if ( # That callable is annotated by only one type hint *AND*... len(bear_call.func_arg_name_to_hint) == 1 and # That type hint annotates that callable's return rather than a # parameter accepted by that callable... ARG_NAME_RETURN in bear_call.func_arg_name_to_hint ): return '' # Else, one or more callable parameters are annotated. # Python code snippet to be returned. func_wrapper_code = '' # ..................{ LOCALS ~ parameter }.................. #FIXME: Remove this *AFTER* optimizing signature generation, please. # True only if this callable possibly accepts one or more positional # parameters. is_args_positional = False # ..................{ LOCALS ~ hint }.................. # Type hint annotating the current parameter if any *OR* "_PARAM_HINT_EMPTY" # otherwise (i.e., if this parameter is unannotated). hint_insane = None # This type hint sanitized into a possibly different type hint more readily # consumable by @beartype's code generator. hint = None # ..................{ GENERATE }.................. #FIXME: Locally remove the "arg_index" local variable (and thus avoid #calling the enumerate() builtin here) AFTER* refactoring @beartype to #generate callable-specific wrapper signatures. # For the 0-based index of each parameter accepted by this callable and the # "ParameterMeta" object describing this parameter (in declaration order)... for arg_index, arg_meta in enumerate(iter_func_args( # Possibly lowest-level wrappee underlying the possibly higher-level # wrapper currently being decorated by the @beartype decorator. The # latter typically fails to convey the same callable metadata conveyed # by the former -- including the names and kinds of parameters accepted # by the possibly unwrapped callable. This renders the latter mostly # useless for our purposes. func=bear_call.func_wrappee_wrappee, func_codeobj=bear_call.func_wrappee_wrappee_codeobj, is_unwrap=False, )): # Kind and name of this parameter. arg_kind: ArgKind = arg_meta[ARG_META_INDEX_KIND] # type: ignore[assignment] arg_name: str = arg_meta[ARG_META_INDEX_NAME] # type: ignore[assignment] # Default value of this parameter if this parameter is optional *OR* the # "ArgMandatory" singleton otherwise (i.e., if this parameter is # mandatory). arg_default: object = arg_meta[ARG_META_INDEX_DEFAULT] # Type hint annotating this parameter if any *OR* the sentinel # placeholder otherwise (i.e., if this parameter is unannotated). # # Note that "None" is a semantically meaningful PEP 484-compliant type # hint equivalent to "type(None)". Ergo, we *MUST* explicitly # distinguish between that type hint and unannotated parameters. hint_insane = bear_call.func_arg_name_to_hint_get(arg_name, SENTINEL) # If this parameter is unannotated, continue to the next parameter. if hint_insane is SENTINEL: continue # Else, this parameter is annotated. # Attempt to... try: # With a context manager "catching" *ALL* non-fatal warnings emitted # during this logic for subsequent "playrback" below... with catch_warnings(record=True) as warnings_issued: # If this parameter's name is reserved for use by the @beartype # decorator, raise an exception. if arg_name.startswith('__bear'): raise BeartypeDecorParamNameException( f'{EXCEPTION_PLACEHOLDER}reserved by @beartype.') # If either the type of this parameter is silently ignorable, # continue to the next parameter. elif arg_kind in _ARG_KINDS_IGNORABLE: continue # Else, this parameter is non-ignorable. # Sanitize this hint into a possibly different type hint more # readily consumable by @beartype's code generator *BEFORE* # passing this hint to any further callables. hint = sanify_hint_root_func( hint=hint_insane, pith_name=arg_name, bear_call=bear_call) # If this hint is ignorable, continue to the next parameter. # # Note that this is intentionally tested *AFTER* this hint has # been coerced into a PEP-compliant type hint to implicitly # ignore PEP-noncompliant type hints as well (e.g., "(object, # int, str)"). if is_hint_ignorable(hint): # print(f'Ignoring {bear_call.func_name} parameter {arg_name} hint {repr(hint)}...') continue # Else, this hint is unignorable. #FIXME: Fundamentally unsafe and thus temporarily disabled *FOR #THE MOMENT.* The issue is that our current implementation of #the is_bearable() tester internally called by this function #refuses to resolve relative forward references -- which is #obviously awful. Ideally, that tester *ABSOLUTELY* should #resolve relative forward references. Until it does, however, #this is verboten dark magic that is unsafe in the general case. #FIXME: Note that there exist even *MORE* edge cases, however: #@dataclass fields, which violate typing semantics: e.g., # from dataclasses import dataclass, field # from typing import Dict # # from beartype import beartype # # @beartype # @dataclass # class A: # test_dict: Dict[str, str] = field(default_factory=dict) #FIXME: Once this has been repaired, please reenable: #* The "test_decor_arg_kind_flex_optional" unit test. # # If this parameter is optional *AND* the default value of this # # optional parameter violates this hint, raise an exception. # _die_if_arg_default_unbearable( # bear_call=bear_call, arg_default=arg_default, hint=hint) # # Else, this parameter is either optional *OR* the default value # # of this optional parameter satisfies this hint. # If this parameter either may *OR* must be passed positionally, # record this fact. # # Note this conditional branch *MUST* be tested after validating # this parameter to be unignorable; if this branch were instead # nested *BEFORE* validating this parameter to be unignorable, # beartype would fail to reduce to a noop for otherwise # ignorable callables -- which would be rather bad, really. if arg_kind in _ARG_KINDS_POSITIONAL: is_args_positional = True # Else, this parameter *CANNOT* be passed positionally. # Python code template localizing this parameter if this kind of # parameter is supported *OR* "None" otherwise. ARG_LOCALIZE_TEMPLATE = ARG_KIND_TO_CODE_LOCALIZE.get( # type: ignore arg_kind, None) # If this kind of parameter is unsupported, raise an exception. # # Note this edge case should *NEVER* occur, as the parent # function should have simply ignored this parameter. if ARG_LOCALIZE_TEMPLATE is None: raise BeartypeDecorHintPepException( f'{EXCEPTION_PLACEHOLDER}kind {repr(arg_kind)} ' f'currently unsupported by @beartype.' ) # Else, this kind of parameter is supported. Ergo, this code is # non-"None". # Type stack if required by this hint *OR* "None" otherwise. See # the is_hint_needs_cls_stack() tester for further discussion. # # Note that the original unsanitized "hint_insane" (e.g., # "typing.Self") rather than the new sanitized "hint" (e.g., the # class currently being decorated by @beartype) is passed to # that tester. Why? Because the latter may already have been # reduced above to a different (and seemingly innocuous) type # hint that does *NOT* appear to require a type stack at late # *EXCEPTION RAISING TIME* (i.e., the # beartype._check.error.errorget.get_func_pith_violation() # function) but actually does. Only the original unsanitized # "hint_insane" is truth. cls_stack = ( bear_call.cls_stack # if is_hint_needs_cls_stack(hint) else if is_hint_needs_cls_stack(hint_insane) else None ) # print(f'arg "{arg_name}" hint {repr(hint)} cls_stack: {repr(cls_stack)}') # Code snippet type-checking *ANY* parameter with *ANY* # arbitrary name. ( code_arg_check_pith, func_scope, hint_refs_type_basename, ) = make_code_raiser_func_pith_check( hint, bear_call.conf, cls_stack, True, # <-- True only for parameters ) # Merge the local scope required to check this parameter into # the local scope currently required by the current wrapper # function. update_mapping(bear_call.func_wrapper_scope, func_scope) # Python code snippet localizing this parameter. code_arg_localize = ARG_LOCALIZE_TEMPLATE.format( arg_name=arg_name, arg_index=arg_index) # Unmemoize this snippet against the current parameter. code_arg_check = unmemoize_func_wrapper_code( bear_call=bear_call, func_wrapper_code=code_arg_check_pith, pith_repr=repr(arg_name), hint_refs_type_basename=hint_refs_type_basename, ) # Append code type-checking this parameter against this hint. func_wrapper_code += f'{code_arg_localize}{code_arg_check}' # If one or more warnings were issued, reissue these warnings with # each placeholder substring (i.e., "EXCEPTION_PLACEHOLDER" # instance) replaced by a human-readable description of this # callable and annotated parameter. if warnings_issued: # print(f'warnings_issued: {warnings_issued}') reissue_warnings_placeholder( warnings=warnings_issued, target_str=prefix_callable_arg_name( func=bear_call.func_wrappee, arg_name=arg_name, is_color=bear_call.conf.is_color, ), ) # Else, *NO* warnings were issued. # If any exception was raised, reraise this exception with each # placeholder substring (i.e., "EXCEPTION_PLACEHOLDER" instance) # replaced by a human-readable description of this callable and # annotated parameter. except Exception as exception: reraise_exception_placeholder( exception=exception, #FIXME: Embed the kind of parameter both here and above as well #(e.g., "positional-only", "keyword-only", "variadic #positional"), ideally by improving the existing #prefix_callable_arg_name() function to introspect this kind from #the callable code object. target_str=prefix_callable_arg_name( func=bear_call.func_wrappee, arg_name=arg_name, is_color=bear_call.conf.is_color, ), ) # If this callable accepts one or more positional type-checked parameters, # prefix this code by a snippet localizing the number of these parameters. if is_args_positional: func_wrapper_code = f'{CODE_INIT_ARGS_LEN}{func_wrapper_code}' # Else, this callable accepts *NO* positional type-checked parameters. In # this case, preserve this code as is. # Return this code. return func_wrapper_code # ....................{ PRIVATE ~ constants }.................... #FIXME: Remove this set *AFTER* handling these kinds of parameters. _ARG_KINDS_IGNORABLE = frozenset(( ArgKind.VAR_KEYWORD, )) ''' Frozen set of all :attr:`ArgKind` enumeration members to be ignored during annotation-based type checking in the :func:`beartype.beartype` decorator. This includes: * Constants specific to variadic keyword parameters (e.g., ``**kwargs``), which are currently unsupported by :func:`beartype`. * Constants specific to positional-only parameters, which apply only to non-pure-Python callables (e.g., defined by C extensions). The :func:`beartype` decorator applies *only* to pure-Python callables, which provide no syntactic means for specifying positional-only parameters. ''' _ARG_KINDS_POSITIONAL = frozenset(( ArgKind.POSITIONAL_ONLY, ArgKind.POSITIONAL_OR_KEYWORD, )) ''' Frozen set of all **positional parameter kinds** (i.e., :attr:`ArgKind` enumeration members signifying that a callable parameter either may *or* must be passed positionally). ''' # ....................{ PRIVATE ~ raisers }.................... def _die_if_arg_default_unbearable( bear_call: BeartypeCall, arg_default: object, hint: object) -> None: ''' Raise a violation exception if the annotated optional parameter of the decorated callable with the passed default value violates the type hint annotating that parameter at decoration time. Parameters ---------- bear_call : BeartypeCall Decorated callable to be type-checked. arg_default : object Either: * If this parameter is mandatory, the :data:`.ArgMandatory` singleton. * If this parameter is optional, the default value of this optional parameter to be type-checked. hint : object Type hint to type-check against this default value. Warns ----- BeartypeDecorHintParamDefaultForwardRefWarning If this type hint contains one or more forward references that *cannot* be resolved at decoration time. While this does *not* necessarily constitute a fatal error from the end user perspective, this does constitute a non-fatal issue worth informing the end user of. Raises ------ BeartypeDecorHintParamDefaultViolation If this default value violates this type hint. ''' # ..................{ PREAMBLE }.................. # If this parameter is mandatory, silently reduce to a noop. if arg_default is ArgMandatory: return # Else, this parameter is optional and thus defaults to a default value. assert isinstance(bear_call, BeartypeCall), ( f'{repr(bear_call)} not beartype call.') # ..................{ IMPORTS }.................. # Defer heavyweight imports prohibited at global scope. from beartype.door import ( die_if_unbearable, is_bearable, ) # ..................{ MAIN }.................. # Attempt to... try: # If this default value satisfies this hint, silently reduce to a noop. # # Note that this is a non-negligible optimization. Technically, this # preliminary test is superfluous: only the call to the # die_if_unbearable() raiser below is required. Pragmatically, this # preliminary test avoids a needlessly expensive dictionary copy in the # common case that this value satisfies this hint. if is_bearable( obj=arg_default, hint=hint, conf=bear_call.conf, ): return # Else, this default value violates this hint. # If doing so raises a forward hint exception, this hint contains one or # more unresolvable forward references to user-defined objects that have yet # to be defined. In all likelihood, these objects are subsequently defined # after the definition of this decorated callable. While this does *NOT* # necessarily constitute a fatal error from the end user perspective, this # does constitute a non-fatal issue worth informing the end user of. In this # case, we coerce this exception into a warning. except _BeartypeHintForwardRefExceptionMixin as exception: # Forward hint exception message raised above. To readably embed this # message in the longer warning message emitted below, the first # character of this message is lowercased as well. exception_message = lowercase_str_char_first(str(exception)) # Emit this non-fatal warning. issue_warning( cls=BeartypeDecorHintParamDefaultForwardRefWarning, message=( f'{EXCEPTION_PREFIX_DEFAULT}value ' f'{prefix_pith_value(pith=arg_default, is_color=bear_call.conf.is_color)}' f'uncheckable at @beartype decoration time, as ' f'{exception_message}' ), ) # Loudly reduce to a noop. Since this forward reference is unresolvable, # further type-checking attempts are entirely fruitless. return # Modifiable keyword dictionary encapsulating this beartype configuration. conf_kwargs = bear_call.conf.kwargs.copy() #FIXME: This should probably be configurable as well. For now, this is fine. #We shrug noncommittally. We shrug, everyone! *shrug* # Set the type of violation exception raised by the subsequent call to the # die_if_unbearable() function to the expected type. conf_kwargs['violation_door_type'] = BeartypeDecorHintParamDefaultViolation # New beartype configuration initialized by this dictionary. conf = BeartypeConf(**conf_kwargs) # Raise this type of violation exception. die_if_unbearable( obj=arg_default, hint=hint, conf=conf, exception_prefix=EXCEPTION_PREFIX_DEFAULT, ) beartype-0.18.5/beartype/_decor/wrap/_wrapreturn.py000066400000000000000000000265341461113517100223730ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator return code generator** (i.e., low-level callables dynamically generating Python expressions type-checking the annotated return of the callable currently being decorated by the :func:`beartype.beartype` decorator in a general-purpose manner). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import NoReturn from beartype._check.checkcall import BeartypeCall from beartype._check.checkmake import ( make_code_raiser_func_pith_check, make_code_raiser_func_pep484_noreturn_check, ) from beartype._check.convert.convsanify import sanify_hint_root_func from beartype._data.func.datafuncarg import ( ARG_NAME_RETURN, ARG_NAME_RETURN_REPR, ) from beartype._data.hint.datahinttyping import LexicalScope from beartype._decor.wrap.wrapsnip import ( CODE_RETURN_CHECK_PREFIX, CODE_RETURN_CHECK_SUFFIX, PEP484_CODE_CHECK_NORETURN, ) from beartype._decor.wrap._wraputil import unmemoize_func_wrapper_code from beartype._util.error.utilerrraise import reraise_exception_placeholder from beartype._util.error.utilerrwarn import reissue_warnings_placeholder from beartype._util.hint.utilhinttest import ( is_hint_ignorable, is_hint_needs_cls_stack, ) from beartype._util.kind.map.utilmapset import update_mapping from beartype._util.text.utiltextprefix import prefix_callable_return from beartype._util.utilobject import SENTINEL from warnings import catch_warnings # ....................{ CODERS }.................... def code_check_return(bear_call: BeartypeCall) -> str: ''' Generate a Python code snippet type-checking the annotated return declared by the decorated callable if any *or* the empty string otherwise (i.e., if this return is unannotated). Parameters ---------- bear_call : BeartypeCall Decorated callable to be type-checked. Returns ------- str Code type-checking any annotated return of the decorated callable. Raises ------ BeartypeDecorHintPep484585Exception If this callable is either: * A coroutine *not* annotated by a :obj:`typing.Coroutine` type hint. * A generator *not* annotated by a :obj:`typing.Generator` type hint. * An asynchronous generator *not* annotated by a :obj:`typing.AsyncGenerator` type hint. BeartypeDecorHintNonpepException If the type hint annotating this return (if any) of this callable is neither: * **PEP-compliant** (i.e., :mod:`beartype`-agnostic hint compliant with annotation-centric PEPs). * **PEP-noncompliant** (i.e., :mod:`beartype`-specific type hint *not* compliant with annotation-centric PEPs)). ''' assert isinstance(bear_call, BeartypeCall), ( f'{repr(bear_call)} not beartype call.') # Type hint annotating this callable's return if any *OR* "SENTINEL" # otherwise (i.e., if this return is unannotated). # # Note that "None" is a semantically meaningful PEP 484-compliant type hint # equivalent to "type(None)". Ergo, we *MUST* explicitly distinguish # between that type hint and an unannotated return. hint = bear_call.func_arg_name_to_hint_get(ARG_NAME_RETURN, SENTINEL) # If this return is unannotated, silently reduce to a noop. if hint is SENTINEL: return '' # Else, this return is annotated. # Python code snippet to be returned, defaulting to the empty string # implying this callable's return to either be unannotated *OR* annotated by # a safely ignorable type hint. func_wrapper_code = '' # Lexical scope (i.e., dictionary mapping from the relative unqualified name # to value of each locally or globally scoped attribute accessible to a # callable or class), initialized to "None" for safety. func_scope: LexicalScope = None # type: ignore[assignment] # Attempt to... try: # With a context manager "catching" *ALL* non-fatal warnings emitted # during this logic for subsequent "playrback" below... with catch_warnings(record=True) as warnings_issued: # Preserve the original unsanitized type hint for subsequent # reference *BEFORE* sanitizing this type hint. hint_insane = hint # Sanitize this hint to either: # * If this hint is PEP-noncompliant, the PEP-compliant type hint # converted from this PEP-noncompliant type hint. # * If this hint is PEP-compliant and supported, this hint as is. # * Else, raise an exception. # # Do this first *BEFORE* passing this hint to any further callables. hint = sanify_hint_root_func( hint=hint, pith_name=ARG_NAME_RETURN, bear_call=bear_call) # print(f'Sanified {repr(bear_call.func_wrappee)} return hint {repr(hint_insane)} to {repr(hint)}...') # If this is the PEP 484-compliant "typing.NoReturn" type hint # permitted *ONLY* as a return annotation... if hint is NoReturn: # Pre-generated code snippet validating this callable to *NEVER* # successfully return by unconditionally generating a violation. code_noreturn_check = PEP484_CODE_CHECK_NORETURN.format( func_call_prefix=bear_call.func_wrapper_code_call_prefix) # Code snippet handling the previously generated violation by # either raising that violation as a fatal exception or emitting # that violation as a non-fatal warning. ( code_noreturn_violation, func_scope, _ ) = make_code_raiser_func_pep484_noreturn_check(bear_call.conf) # Full code snippet to be returned. func_wrapper_code = ( f'{code_noreturn_check}{code_noreturn_violation}') # Else, this is *NOT* "typing.NoReturn". In this case... else: # If this PEP-compliant hint is unignorable, generate and return # a snippet type-checking this return against this hint. if not is_hint_ignorable(hint): # Type stack if required by this hint *OR* "None" otherwise. # See is_hint_needs_cls_stack() for details. # # Note that the original unsanitized "hint_insane" (e.g., # "typing.Self") rather than the new sanitized "hint" (e.g., # the class currently being decorated by @beartype) is # passed to that tester. See _code_check_args() for details. cls_stack = ( bear_call.cls_stack if is_hint_needs_cls_stack(hint_insane) else None ) # print(f'return hint {repr(hint_insane)} -> {repr(hint)} cls_stack: {repr(cls_stack)}') # Empty tuple, passed below to satisfy the # _unmemoize_func_wrapper_code() API. hint_refs_type_basename = () # Code snippet type-checking any arbitrary return. ( code_return_check_pith, func_scope, hint_refs_type_basename, ) = make_code_raiser_func_pith_check( # type: ignore[assignment] hint, bear_call.conf, cls_stack, False, # <-- True only for parameters ) # Unmemoize this snippet against this return. code_return_check = unmemoize_func_wrapper_code( bear_call=bear_call, func_wrapper_code=code_return_check_pith, pith_repr=ARG_NAME_RETURN_REPR, hint_refs_type_basename=hint_refs_type_basename, ) #FIXME: [SPEED] Optimize the following two string munging #operations into a single string-munging operation resembling: # func_wrapper_code = CODE_RETURN_CHECK.format( # func_call_prefix=bear_call.func_wrapper_code_call_prefix, # check_expr=code_return_check_pith_unmemoized, # ) # #Then define "CODE_RETURN_CHECK" in the "wrapsnip" submodule to #resemble: # CODE_RETURN_CHECK = ( # f'{CODE_RETURN_CHECK_PREFIX}{{check_expr}}' # f'{CODE_RETURN_CHECK_SUFFIX}' # ) # Code snippet type-checking this return. code_return_check_prefix = CODE_RETURN_CHECK_PREFIX.format( func_call_prefix=( bear_call.func_wrapper_code_call_prefix)) # Full code snippet to be returned, consisting of: # * Calling the decorated callable and localize its return # *AND*... # * Type-checking this return *AND*... # * Returning this return from this wrapper function. func_wrapper_code = ( f'{code_return_check_prefix}' f'{code_return_check}' f'{CODE_RETURN_CHECK_SUFFIX}' ) # Else, this hint is ignorable. # if not func_wrapper_code: print(f'Ignoring {bear_call.func_name} return hint {repr(hint)}...') # If one or more warnings were issued, reissue these warnings with each # placeholder substring (i.e., "EXCEPTION_PLACEHOLDER" instance) # replaced by a human-readable description of this callable and # annotated return. if warnings_issued: reissue_warnings_placeholder( warnings=warnings_issued, target_str=prefix_callable_return( func=bear_call.func_wrappee, is_color=bear_call.conf.is_color, ), ) # Else, *NO* warnings were issued. # If any exception was raised, reraise this exception with each placeholder # substring (i.e., "EXCEPTION_PLACEHOLDER" instance) replaced by a # human-readable description of this callable and annotated return. except Exception as exception: reraise_exception_placeholder( exception=exception, target_str=prefix_callable_return( func=bear_call.func_wrappee, is_color=bear_call.conf.is_color, ), ) # If a local scope is required to type-check this return, merge this scope # into the local scope currently required by the current wrapper function. if func_scope: update_mapping(bear_call.func_wrapper_scope, func_scope) # Else, *NO* local scope is required to type-check this return. # Return this code. return func_wrapper_code beartype-0.18.5/beartype/_decor/wrap/_wraputil.py000066400000000000000000000140151461113517100220200ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator code generator utilities** (i.e., low-level callables assisting the parent :func:`beartype._decor.wrap.wrapmain` submodule). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._check.checkcall import BeartypeCall from beartype._check.checkmagic import CODE_PITH_ROOT_NAME_PLACEHOLDER from beartype._check.code.codescope import add_func_scope_ref from beartype._check.code.snip.codesnipstr import ( CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_PREFIX, CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_SUFFIX, ) from beartype._data.error.dataerrmagic import EXCEPTION_PLACEHOLDER from beartype._util.hint.pep.proposal.pep484585.utilpep484585ref import ( get_hint_pep484585_ref_names_relative_to) from beartype._util.text.utiltextmunge import replace_str_substrs from collections.abc import Iterable # ....................{ CACHERS }.................... def unmemoize_func_wrapper_code( bear_call: BeartypeCall, func_wrapper_code: str, pith_repr: str, hint_refs_type_basename: tuple, ) -> str: ''' Convert the passed memoized code snippet type-checking any parameter or return of the decorated callable into an "unmemoized" code snippet type-checking a specific parameter or return of that callable. Specifically, this function (in order): #. Globally replaces all references to the :data:`.CODE_PITH_ROOT_NAME_PLACEHOLDER` placeholder substring cached into this code with the passed ``pith_repr`` parameter. #. Unmemoizes this code by globally replacing all relative forward reference placeholder substrings cached into this code with Python expressions evaluating to the classes referred to by those substrings relative to that callable when accessed via the private ``__beartypistry`` parameter. Parameters ---------- bear_call : BeartypeCall Decorated callable to be type-checked. func_wrapper_code : str Memoized callable-agnostic code snippet type-checking any parameter or return of the decorated callable. pith_repr : str Machine-readable representation of the name of this parameter or return. hint_refs_type_basename : tuple Tuple of the unqualified classnames referred to by all relative forward reference type hints visitable from the current root type hint. Returns ------- str This memoized code unmemoized by globally resolving all relative forward reference placeholder substrings cached into this code relative to the currently decorated callable. ''' assert bear_call.__class__ is BeartypeCall, ( f'{repr(bear_call)} not @beartype call.') assert isinstance(func_wrapper_code, str), ( f'{repr(func_wrapper_code)} not string.') assert isinstance(pith_repr, str), f'{repr(pith_repr)} not string.' assert isinstance(hint_refs_type_basename, Iterable), ( f'{repr(hint_refs_type_basename)} not iterable.') # Generate an unmemoized parameter-specific code snippet type-checking this # parameter by replacing in this parameter-agnostic code snippet... func_wrapper_code = replace_str_substrs( text=func_wrapper_code, # This placeholder substring cached into this code with... old=CODE_PITH_ROOT_NAME_PLACEHOLDER, # This object representation of the name of this parameter or return. new=pith_repr, ) # If this code contains one or more relative forward reference placeholder # substrings memoized into this code, unmemoize this code by globally # resolving these placeholders relative to the decorated callable. if hint_refs_type_basename: # Metadata describing the callable currently being decorated by # beartype, localized purely as a negligible optimization. func = bear_call.func_wrappee func_scope = bear_call.func_wrapper_scope cls_stack = bear_call.cls_stack # For each unqualified classname referred to by a relative forward # reference type hints visitable from the current root type hint... for ref_basename in hint_refs_type_basename: # Possibly undefined fully-qualified module name and possibly # unqualified classname referred to by this relative forward # reference, relative to the decorated type stack and callable. ref_module_name, ref_name = get_hint_pep484585_ref_names_relative_to( hint=ref_basename, cls_stack=cls_stack, func=func, exception_prefix=EXCEPTION_PLACEHOLDER, ) # Name of the hidden parameter providing this forward reference # proxy to be passed to this wrapper function. ref_expr = add_func_scope_ref( func_scope=func_scope, ref_module_name=ref_module_name, ref_name=ref_name, exception_prefix=EXCEPTION_PLACEHOLDER, ) # Generate an unmemoized callable-specific code snippet checking # this class by globally replacing in this callable-agnostic code... func_wrapper_code = replace_str_substrs( text=func_wrapper_code, # This placeholder substring cached into this code with... old=( f'{CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_PREFIX}' f'{ref_name}' f'{CODE_HINT_REF_TYPE_BASENAME_PLACEHOLDER_SUFFIX}' ), # Python expression evaluating to this class when accessed via # this hidden parameter. new=ref_expr, ) # Return this unmemoized callable-specific code snippet. return func_wrapper_code beartype-0.18.5/beartype/_decor/wrap/wrapmain.py000066400000000000000000000177511461113517100216420ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator code generator.** This private submodule dynamically generates both the signature and body of the wrapper function type-checking all annotated parameters and return value of the the callable currently being decorated by the :func:`beartype.beartype` decorator in a general-purpose manner. For genericity, this relatively high-level submodule implements *no* support for annotation-based PEPs (e.g., :pep:`484`); other lower-level submodules do so instead. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... # All "FIXME:" comments for this submodule reside in this package's "__init__" # submodule to improve maintainability and readability here. # ....................{ IMPORTS }.................... from beartype._check.checkcall import BeartypeCall from beartype._check.checkmagic import ARG_NAME_FUNC from beartype._check.util.checkutilmake import make_func_signature from beartype._decor.wrap.wrapsnip import ( CODE_RETURN_UNCHECKED_format, CODE_SIGNATURE, ) from beartype._decor.wrap._wrapargs import ( code_check_args as _code_check_args) from beartype._decor.wrap._wrapreturn import ( code_check_return as _code_check_return) # ....................{ GENERATORS }.................... def generate_code(bear_call: BeartypeCall) -> str: ''' Generate a Python code snippet dynamically defining the wrapper function type-checking the passed decorated callable. This high-level function implements this decorator's core type-checking, converting all unignorable PEP-compliant type hints annotating this callable into pure-Python code type-checking the corresponding parameters and return values of each call to this callable. Parameters ---------- bear_call : BeartypeCall Decorated callable to be type-checked. Returns ------- str Generated function wrapper code. Specifically, either: * If the decorated callable requires *no* type-checking (e.g., due to all type hints annotating this callable being ignorable), the empty string. Note this edge case is distinct from a related edge case at the head of the :func:`beartype.beartype` decorator reducing to a noop for unannotated callables. By compare, this boolean is ``True`` only for callables annotated with **ignorable type hints** (i.e., :class:`object`, :class:`beartype.cave.AnyType`, :class:`typing.Any`): e.g., .. code-block:: python >>> from beartype.cave import AnyType >>> from typing import Any >>> def muh_func(muh_param1: AnyType, muh_param2: object) -> Any: pass >>> muh_func is beartype(muh_func) True * Else, a code snippet defining the wrapper function type-checking the decorated callable, including (in order): * A signature declaring this wrapper, accepting both beartype-agnostic and -specific parameters. The latter include: * A private ``__beartype_func`` parameter initialized to the decorated callable. In theory, this callable should be accessible as a closure-style local in this wrapper. For unknown reasons (presumably, a subtle bug in the exec() builtin), this is *not* the case. Instead, a closure-style local must be simulated by passing this callable at function definition time as the default value of an arbitrary parameter. To ensure this default is *not* overwritten by a function accepting a parameter of the same name, this unlikely edge case is guarded against elsewhere. * Statements type checking parameters passed to the decorated callable. * A call to the decorated callable. * A statement type checking the value returned by the decorated callable. Raises ------ BeartypeDecorParamNameException If the name of any parameter declared on this callable is prefixed by the reserved substring ``__bear``. BeartypeDecorHintNonpepException If any type hint annotating any parameter of this callable is neither: * **PEP-compliant** (i.e., :mod:`beartype`-agnostic hint compliant with annotation-centric PEPs). * **PEP-noncompliant** (i.e., :mod:`beartype`-specific type hint *not* compliant with annotation-centric PEPs)). _BeartypeUtilMappingException If generated code type-checking any pair of parameters and returns erroneously declares an optional private beartype-specific parameter of the same name with differing default value. Since this should *never* happen, a private non-human-readable exception is raised in this case. ''' # Python code snippet type-checking all callable parameters if one or more # such parameters are annotated with unignorable type hints *OR* the empty # string otherwise. code_check_params = _code_check_args(bear_call) # Python code snippet type-checking the callable return if this return is # annotated with an unignorable type hint *OR* the empty string otherwise. code_check_return = _code_check_return(bear_call) # If the callable return requires *NO* type-checking... # # Note that this branch *CANNOT* be embedded in the prior call to the # code_check_return() function, as doing so would prevent us from # efficiently reducing to a noop here. if not code_check_return: # If all callable parameters also require *NO* type-checking, this # callable itself requires *NO* type-checking. In this case, return the # empty string instructing the parent @beartype decorator to reduce to a # noop (i.e., the identity decorator returning this callable as is). if not code_check_params: return '' # Else, one or more callable parameters require type-checking. # Python code snippet calling this callable unchecked, returning the # value returned by this callable from this wrapper. code_check_return = CODE_RETURN_UNCHECKED_format( func_call_prefix=bear_call.func_wrapper_code_call_prefix) # Else, the callable return requires type-checking. # Dictionary mapping from the name to value of each attribute referenced in # the signature of this wrapper function, localized merely for readability. func_scope = bear_call.func_wrapper_scope # Pass parameters unconditionally required by *ALL* wrapper functions. func_scope[ARG_NAME_FUNC] = bear_call.func_wrappee # Python code snippet declaring the signature of this type-checking wrapper # function, deferred for efficiency until *AFTER* confirming that a wrapper # function is even required. code_signature = make_func_signature( func_name=bear_call.func_wrapper_name, func_scope=func_scope, code_signature_format=CODE_SIGNATURE, code_signature_prefix=bear_call.func_wrapper_code_signature_prefix, conf=bear_call.conf, ) # Return Python code defining the wrapper type-checking this callable. # While there exist numerous alternatives to string formatting (e.g., # appending to a list or bytearray before joining the items of that # iterable into a string), these alternatives are either: # * Slower, as in the case of a list (e.g., due to the high up-front cost # of list construction). # * Cumbersome, as in the case of a bytearray. # # Since string concatenation is heavily optimized by the official CPython # interpreter, the simplest approach is the most ideal. KISS, bro. return f'{code_signature}{code_check_params}{code_check_return}' beartype-0.18.5/beartype/_decor/wrap/wrapsnip.py000066400000000000000000000233411461113517100216570ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype decorator **wrapper function code snippets** (i.e., triple-quoted pure-Python string constants formatted and concatenated together to dynamically generate the implementations of wrapper functions type-checking :func:`beartype.beartype`-decorated callables). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._check.checkmagic import ( ARG_NAME_FUNC, ARG_NAME_GET_VIOLATION, VAR_NAME_ARGS_LEN, VAR_NAME_PITH_ROOT, ) from beartype._util.func.arg.utilfuncargiter import ArgKind from beartype._data.code.datacodeindent import CODE_INDENT_1 from beartype._data.error.dataerrmagic import EXCEPTION_PLACEHOLDER from collections.abc import Callable # ....................{ STRINGS }.................... EXCEPTION_PREFIX_DEFAULT = f'{EXCEPTION_PLACEHOLDER}default ' ''' Non-human-readable source substring to be globally replaced by a human-readable target substring in the messages of memoized exceptions passed to the :func:`reraise_exception` function caused by violations raised when type-checking the default values of optional parameters for :func:`beartype.beartype`-decorated callables. ''' # ....................{ CODE }.................... CODE_SIGNATURE = f'''{{code_signature_prefix}}def {{func_name}}( *args, {{code_signature_args}}{CODE_INDENT_1}**kwargs ):''' ''' Code snippet declaring the signature of a type-checking callable. Note that: * ``code_signature_prefix`` is usually either: * For synchronous callables, the empty string. * For asynchronous callables (e.g., asynchronous generators, coroutines), the space-suffixed keyword ``"async "``. ''' CODE_INIT_ARGS_LEN = f''' # Localize the number of passed positional arguments for efficiency. {VAR_NAME_ARGS_LEN} = len(args)''' ''' Code snippet localizing the number of passed positional arguments for callables accepting one or more such arguments. ''' # ....................{ CODE ~ arg }.................... ARG_KIND_TO_CODE_LOCALIZE = { # Snippet localizing any positional-only parameter (e.g., # "{posonlyarg}, /") by lookup in the wrapper's "*args" dictionary. ArgKind.POSITIONAL_ONLY: f''' # If this positional-only parameter was passed... if {VAR_NAME_ARGS_LEN} > {{arg_index}}: # Localize this positional-only parameter. {VAR_NAME_PITH_ROOT} = args[{{arg_index}}]''', # Snippet localizing any positional or keyword parameter as follows: # # * If this parameter's 0-based index (in the parameter list of the # decorated callable's signature) does *NOT* exceed the number of # positional parameters passed to the wrapper function, localize this # positional parameter from the wrapper's variadic "*args" tuple. # * Else if this parameter's name is in the dictionary of keyword # parameters passed to the wrapper function, localize this keyword # parameter from the wrapper's variadic "*kwargs" tuple. # * Else, this parameter is unpassed. In this case, localize this parameter # as a placeholder value guaranteed to *NEVER* be passed to any wrapper # function: the private "__beartypistry" singleton passed to this wrapper # function as a hidden default parameter and thus accessible here. While # we could pass a "__beartype_sentinel" parameter to all wrapper # functions defaulting to "object()" and then use that here instead, # doing so would slightly reduce efficiency for no tangible gain. *shrug* ArgKind.POSITIONAL_OR_KEYWORD: f''' # Localize this positional or keyword parameter if passed *OR* to the # sentinel "__beartype_raise_exception" guaranteed to never be passed. {VAR_NAME_PITH_ROOT} = ( args[{{arg_index}}] if {VAR_NAME_ARGS_LEN} > {{arg_index}} else kwargs.get({{arg_name!r}}, {ARG_NAME_GET_VIOLATION}) ) # If this parameter was passed... if {VAR_NAME_PITH_ROOT} is not {ARG_NAME_GET_VIOLATION}:''', # Snippet localizing any keyword-only parameter (e.g., "*, {kwarg}") by # lookup in the wrapper's variadic "**kwargs" dictionary. (See above.) ArgKind.KEYWORD_ONLY: f''' # Localize this keyword-only parameter if passed *OR* to the sentinel value # "__beartype_raise_exception" guaranteed to never be passed. {VAR_NAME_PITH_ROOT} = kwargs.get({{arg_name!r}}, {ARG_NAME_GET_VIOLATION}) # If this parameter was passed... if {VAR_NAME_PITH_ROOT} is not {ARG_NAME_GET_VIOLATION}:''', # Snippet iteratively localizing all variadic positional parameters. ArgKind.VAR_POSITIONAL: f''' # For all passed variadic positional parameters... for {VAR_NAME_PITH_ROOT} in args[{{arg_index!r}}:]:''', #FIXME: Probably impossible to implement under the standard decorator #paradigm, sadly. This will have to wait for us to fundamentally revise #our signature generation algorithm. # # Snippet iteratively localizing all variadic keyword parameters. # ArgKind.VAR_KEYWORD: f''' # # For all passed variadic keyword parameters... # for {VAR_NAME_PITH_ROOT} in kwargs[{{arg_index!r}}:]:''', } ''' Dictionary mapping from the type of each callable parameter supported by the :func:`beartype.beartype` decorator to a code snippet localizing that callable's next parameter to be type-checked. ''' # ....................{ CODE ~ return ~ check }.................... CODE_RETURN_CHECK_PREFIX = f''' # Call this function with all passed parameters and localize the value # returned from this call. {VAR_NAME_PITH_ROOT} = {{func_call_prefix}}{ARG_NAME_FUNC}(*args, **kwargs) # Noop required to artificially increase indentation level. Note that # CPython implicitly optimizes this conditional away. Isn't that nice? if True:''' ''' Code snippet calling the decorated callable and localizing the value returned by that call. Note that this snippet intentionally terminates on a noop increasing the indentation level, enabling subsequent type-checking code to effectively ignore indentation level and thus uniformly operate on both: * Parameters localized via values of the :data:`PARAM_KIND_TO_PEP_CODE_LOCALIZE` dictionary. * Return values localized via this snippet. See Also -------- https://stackoverflow.com/a/18124151/2809027 Bytecode disassembly demonstrating that CPython optimizes away the spurious ``if True:`` conditional hardcoded into this snippet. ''' CODE_RETURN_CHECK_SUFFIX = f''' return {VAR_NAME_PITH_ROOT}''' ''' Code snippet returning from the wrapper function the successfully type-checked value returned from the decorated callable. ''' # ....................{ CODE ~ return ~ check ~ noreturn }.................... #FIXME: *FALSE.* The following comment is entirely wrong, sadly. Although that #comment does, in fact, apply to asynchronous generators, that comment does #*NOT* apply to coroutines. PEP 484 stipulates that the returns of coroutines #are annotated in the exact same standard way as the returns of synchronous #callables are annotated: e.g., # # This is valid, but @beartype currently fails to support this. # async def muh_coroutine() -> typing.NoReturn: # await asyncio.sleep(0) # raise ValueError('Dude, who stole my standards compliance?') # #Generalize this snippet to contain a "{{func_call_prefix}}" substring prefixing #the "{ARG_NAME_FUNC}(*args, **kwargs)" call, please. # Unlike above, this snippet intentionally omits the "{{func_call_prefix}}" # substring prefixing the "{ARG_NAME_FUNC}(*args, **kwargs)" call. Why? Because # callables whose returns are annotated by "typing.NoReturn" *MUST* necessarily # be synchronous (rather than asynchronous) and thus require no such prefix. # Why? Because the returns of asynchronous callables are either unannotated # *OR* annotated by either "Coroutine[...]" *OR* "AsyncGenerator[...]" type # hints. Since "typing.NoReturn" is neither, "typing.NoReturn" *CANNOT* # annotate the returns of asynchronous callables. The implication then follows. PEP484_CODE_CHECK_NORETURN = f''' # Call this function with all passed parameters and localize the value # returned from this call. {VAR_NAME_PITH_ROOT} = {{func_call_prefix}}{ARG_NAME_FUNC}(*args, **kwargs) # Since this function annotated by "typing.NoReturn" successfully returned a # value rather than raising an exception or halting the active Python # interpreter, unconditionally raise an exception. # # Noop required to artificially increase indentation level. Note that # CPython implicitly optimizes this conditional away. Isn't that nice? if True''' ''' :pep:`484`-compliant code snippet calling the decorated callable annotated by the :attr:`typing.NoReturn` singleton and raising an exception if this call successfully returned a value rather than raising an exception or halting the active Python interpreter. ''' # ....................{ CODE ~ return ~ uncheck }.................... CODE_RETURN_UNCHECKED = f''' # Call this function with all passed parameters and return the value # returned from this call. return {{func_call_prefix}}{ARG_NAME_FUNC}(*args, **kwargs)''' ''' Code snippet calling the decorated callable *without* type-checking the value returned by that call (if any). ''' # ..................{ FORMATTERS }.................. # str.format() methods, globalized to avoid inefficient dot lookups elsewhere. # This is an absurd micro-optimization. *fight me, github developer community* CODE_RETURN_UNCHECKED_format: Callable = CODE_RETURN_UNCHECKED.format beartype-0.18.5/beartype/_util/000077500000000000000000000000001461113517100163475ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/__init__.py000066400000000000000000000000001461113517100204460ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/api/000077500000000000000000000000001461113517100171205ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/api/__init__.py000066400000000000000000000000001461113517100212170ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/api/utilapibeartype.py000066400000000000000000000106351461113517100227020ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **beartype-generated wrapper function utilities** (i.e., callables specifically applicable to wrapper functions generated by the :func:`beartype.beartype` decorator for beartype-decorated callables). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._util.func.pep.utilpep484func import ( is_func_pep484_notypechecked) from beartype._util.func.utilfuncget import get_func_annotations_or_none from beartype._util.api.utilapisphinx import is_sphinx_autodocing from beartype._util.py.utilpyinterpreter import is_python_optimized from collections.abc import Callable # ....................{ TESTERS }.................... #FIXME: Unit test us up, please. def is_func_unbeartypeable(func: Callable) -> bool: ''' :data:`True` only if the passed callable is **unbeartypeable** (i.e., if the :func:`beartype.beartype` decorator should preserve that callable as is by reducing to the identity decorator rather than wrap that callable with constant-time type-checking). Parameters ---------- func : Callable Callable to be inspected. Returns ------- bool :data:`True` only if that callable is unbeartypeable. ''' # Return true only if either... return ( # That callable is unannotated *OR*... get_func_annotations_or_none(func) is None or # That callable is decorated by the @typing.no_type_check decorator # defining this dunder instance variable on this callable *OR*... is_func_pep484_notypechecked(func) or # That callable is a @beartype-specific wrapper previously generated by # this decorator *OR*... is_func_beartyped(func) or # The active Python process was optimized *AFTER* process invocation # time (e.g., in an interactive REPL by the external user manually # setting the ${PYTHONOPTIMIZED} environment variable to a non-zero # integer) *OR*... is_python_optimized() or # Sphinx is currently autogenerating documentation (i.e., if this # decorator has been called from a Python call stack invoked by the # "autodoc" extension bundled with the optional third-party build-time # "sphinx" package)... # # Why? Because of mocking. When @beartype-decorated callables are # annotated with one more classes mocked by "autodoc_mock_imports", # @beartype frequently raises exceptions at decoration time. Why? # Because mocking subverts our assumptions and expectations about # classes used as annotations. is_sphinx_autodocing() ) def is_func_beartyped(func: Callable) -> bool: ''' :data:`True` only if the passed callable is a **beartype-generated wrapper function** (i.e., function dynamically generated by the :func:`beartype.beartype` decorator for a user-defined callable decorated by that decorator, wrapping that callable with constant-time type-checking). Parameters ---------- func : Callable Callable to be inspected. Returns ------- bool :data:`True` only if that callable is a beartype-generated wrapper function. ''' # Return true only if this callable is a @beartype-specific wrapper # previously generated by this decorator. return hasattr(func, '__beartype_wrapper') # ....................{ SETTERS }.................... def set_func_beartyped(func: Callable) -> None: ''' Declare the passed callable to be a **beartype-generated wrapper function** (i.e., function dynamically generated by the :func:`beartype.beartype` decorator for a user-defined callable decorated by that decorator, wrapping that callable with constant-time type-checking). Parameters ---------- func : Callable Callable to be modified. ''' # Declare this callable to be generated by @beartype, which tests for the # existence of this attribute above to avoid re-decorating callables # already decorated by @beartype by efficiently reducing to a noop. func.__beartype_wrapper = True # type: ignore[attr-defined] beartype-0.18.5/beartype/_util/api/utilapicontextlib.py000066400000000000000000000075341461113517100232460ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :mod:`contextlib` utilities (i.e., low-level callables handling the standard :mod:`contextlib` module). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import ( Any, ) from beartype._data.hint.datahintfactory import TypeGuard from beartype._util.func.utilfunccodeobj import ( get_func_codeobj_or_none, get_func_codeobj_basename, ) from beartype._util.py.utilpyversion import IS_PYTHON_AT_MOST_3_10 from collections.abc import ( Callable, # Generator, ) # ....................{ TESTERS }.................... def is_func_contextlib_contextmanager(func: Any) -> TypeGuard[Callable]: ''' :data:`True` only if the passed object is a :func:`contextlib.contextmanager`-based **isomorphic decorator closure** (i.e., closure both defined and returned by the standard :func:`contextlib.contextmanager` decorator where that closure isomorphically preserves both the number and types of all passed parameters and returns by accepting only a variadic positional argument and variadic keyword argument). This tester enables callers to detect when a user-defined callable has been decorated by :func:`contextlib.contextmanager` and thus has a mismatch between the type hints annotating that decorated callable and the type of the object created and returned by that decorated callable. Parameters ---------- func : object Object to be inspected. Returns ------- bool :data:`True` only if this object is a :func:`contextlib.contextmanager`-based isomorphic decorator closure. See Also -------- beartype._data.func.datafunc.CONTEXTLIB_CONTEXTMANAGER_CO_NAME_QUALNAME Further discussion. ''' # Avoid circular import dependencies. from beartype._util.func.utilfunctest import is_func_closure # If either... if ( # The active Python interpreter targets Python < 3.10 and thus fails to # define the "co_qualname" attribute on code objects required to # robustly implement this test *OR*... IS_PYTHON_AT_MOST_3_10 or # The passed callable is *NOT* a closure... not is_func_closure(func) ): # Then immediately return false. return False # Else, that callable is a closure. # Code object underlying that callable as is (rather than possibly unwrapped # to another code object entirely) if that callable is pure-Python *OR* # "None" otherwise (i.e., if that callable is C-based). func_codeobj = get_func_codeobj_or_none(func) # If that callable is C-based, immediately return false. if func_codeobj is None: return False # Else, that callable is pure-Python. # Defer heavyweight tester-specific imports with potential side effects -- # notably, increased costs to space and time complexity. from beartype._data.module.datamodcontextlib import ( CONTEXTLIB_CONTEXTMANAGER_CODEOBJ_NAME) # Fully-qualified name of that code object. func_codeobj_name = get_func_codeobj_basename(func_codeobj) # Return true only if the fully-qualified name of that code object is that # of the isomorphic decorator closure created and returned by the standard # @contextlib.contextmanager decorator. # # Note that we *COULD* technically also explicitly test whether that # callable satisfies the is_func_wrapper_isomorphic() tester, but that # there's no benefit and a minor efficiency cost to doing so. return func_codeobj_name == CONTEXTLIB_CONTEXTMANAGER_CODEOBJ_NAME beartype-0.18.5/beartype/_util/api/utilapifunctools.py000066400000000000000000000226711461113517100231060ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :mod:`functools` utilities (i.e., low-level callables handling the standard :mod:`functools` module). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilCallableException from beartype.typing import ( Any, Tuple, ) from beartype._cave._cavefast import ( CallableFunctoolsLruCacheType, CallableFunctoolsPartialType, ) from beartype._data.hint.datahintfactory import TypeGuard from beartype._data.hint.datahinttyping import ( DictStrToAny, TypeException, ) from collections.abc import Callable # ....................{ TESTERS }.................... def is_func_functools_lru_cache(func: Any) -> TypeGuard[Callable]: ''' :data:`True` only if the passed object is a :func:`functools.lru_cache`-memoized **pseudo-callable** (i.e., low-level C-based callable object both created and returned by the standard :func:`functools.lru_cache` decorator). This tester enables callers to detect when a user-defined callable has been decorated by the :func:`functools.lru_cache` decorator, which creates low-level C-based callable objects requiring special handling elsewhere. Parameters ---------- func : object Object to be inspected. Returns ------- bool :data:`True` only if this object is a :func:`functools.lru_cache`-memoized callable. ''' # Defer heavyweight tester-specific imports with potential side effects -- # notably, increased costs to space and time complexity. # Return true only if the type of that callable is the low-level C-based # private type of all objects created and returned by the standard # @functools.lru_cache decorator. return isinstance(func, CallableFunctoolsLruCacheType) def is_func_functools_partial(func: Any) -> TypeGuard[ CallableFunctoolsPartialType]: ''' :data:`True` only if the passed object is a **partial** (i.e., pure-Python callable :class:`functools.partial` object wrapping a possibly C-based callable). Parameters ---------- func : object Object to be inspected. Returns ------- bool :data:`True` only if this object is a :func:`functools.partial`-wrapped callable. ''' # Return true only if the type of that callable is the high-level # pure-Python public type of all objects created and returned by the # standard functools.partial() factory. return isinstance(func, CallableFunctoolsPartialType) # ....................{ GETTERS }.................... def get_func_functools_partial_args( func: CallableFunctoolsPartialType) -> Tuple[tuple, DictStrToAny]: ''' 2-tuple ``(args, kwargs)`` providing the positional and keyword parameters with which the passed **partial** (i.e., pure-Python callable :class:`functools.partial` object directly wrapping this possibly C-based callable) was originally partialized. Parameters ---------- func : CallableFunctoolsPartialType Partial to be inspected. Returns ------- Tuple[tuple, DictStrToAny] 2-tuple ``(args, kwargs)`` such that: * ``args`` is the tuple of the zero or more positional parameters passed to the callable partialized by this partial. * ``kwargs`` is the dictionary mapping from the name to value of the zero or more keyword parameters passed to the callable partialized by this partial. ''' assert isinstance(func, CallableFunctoolsPartialType), ( f'{repr(func)} not "function.partial"-wrapped callable.') # Return a 2-tuple providing the positional and keyword parameters with # which this partial was originally partialized. return (func.args, func.keywords) def get_func_functools_partial_args_flexible_len( # Mandatory parameters. func: CallableFunctoolsPartialType, # Optional parameters. is_unwrap: bool = True, exception_cls: TypeException = _BeartypeUtilCallableException, exception_prefix: str = '', ) -> int: ''' Number of **flexible parameters** (i.e., parameters passable as either positional or keyword arguments but *not* positional-only, keyword-only, variadic, or other more constrained kinds of parameters) accepted by the passed **partial** (i.e., pure-Python callable :class:`functools.partial` object directly wrapping this possibly C-based callable). Specifically, this getter transparently returns the total number of flexible parameters accepted by the lower-level callable wrapped by this partial minus the number of flexible parameters partialized away by this partial. Parameters ---------- func : CallableFunctoolsPartialType Partial to be inspected. is_unwrap: bool, optional :data:`True` only if this getter implicitly calls the :func:`beartype._util.func.utilfuncwrap.unwrap_func_all` function. Defaults to :data:`True` for safety. See :func:`.get_func_codeobj` for further commentary. exception_cls : type, optional Type of exception to be raised in the event of a fatal error. Defaults to :class:`._BeartypeUtilCallableException`. exception_prefix : str, optional Human-readable label prefixing the message of any exception raised in the event of a fatal error. Defaults to the empty string. Returns ------- int Number of flexible parameters accepted by this callable. Raises ------ exception_cls If that callable is *not* pure-Python. ''' assert isinstance(func, CallableFunctoolsPartialType), ( f'{repr(func)} not "function.partial"-wrapped callable.') # Avoid circular import dependencies. from beartype._util.func.arg.utilfuncargget import ( get_func_args_flexible_len) # Pure-Python wrappee callable wrapped by that partial. wrappee = unwrap_func_functools_partial_once(func) # Positional and keyword parameters implicitly passed by this partial to # this wrappee. partial_args, partial_kwargs = get_func_functools_partial_args(func) # Number of flexible parameters accepted by this wrappee. # # Note that this recursive function call is guaranteed to immediately bottom # out and thus be safe. Why? Because a partial *CANNOT* wrap itself, because # a partial has yet to be defined when the functools.partial.__init__() # method defining that partial is called. Technically, the caller *COULD* # violate sanity by directly interfering with the "func" instance variable # of this partial after instantiation. Pragmatically, a malicious edge case # like that is unlikely in the extreme. You are now reading this comment # because this edge case just blew up in your face, aren't you!?!? *UGH!* wrappee_args_flexible_len = get_func_args_flexible_len( func=wrappee, is_unwrap=is_unwrap, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # Number of flexible parameters passed by this partial to this wrappee. partial_args_flexible_len = len(partial_args) + len(partial_kwargs) # Number of flexible parameters accepted by this wrappee minus the number of # flexible parameters passed by this partial to this wrappee. func_args_flexible_len = ( wrappee_args_flexible_len - partial_args_flexible_len) # If this number is negative, the caller maliciously defined an invalid # partial passing more flexible parameters than this wrappee accepts. In # this case, raise an exception. # # Note that the "functools.partial" factory erroneously allows callers to # define invalid partials passing more flexible parameters than their # wrappees accept. Ergo, validation is required to guarantee sanity. if func_args_flexible_len < 0: raise exception_cls( f'{exception_prefix}{repr(func)} passes ' f'{partial_args_flexible_len} parameter(s) to ' f'{repr(wrappee)} accepting only ' f'{wrappee_args_flexible_len} parameter(s) ' f'(i.e., {partial_args_flexible_len} > ' f'{wrappee_args_flexible_len}).' ) # Else, this number is non-negative. The caller correctly defined a valid # partial passing no more flexible parameters than this wrappee accepts. # Return this number. return func_args_flexible_len # ....................{ UNWRAPPERS }.................... def unwrap_func_functools_partial_once( func: CallableFunctoolsPartialType) -> Callable: ''' Possibly C-based callable directly wrapped by the passed **partial** (i.e., pure-Python callable :class:`functools.partial` object directly wrapping this possibly C-based callable). Parameters ---------- func : CallableFunctoolsPartialType Partial to be unwrapped. Returns ------- Callable Possibly C-based callable directly wrapped by this partial. ''' assert isinstance(func, CallableFunctoolsPartialType), ( f'{repr(func)} not "function.partial"-wrapped callable.') # Return the public "func" instance variable of this partial wrapper as is. return func.func beartype-0.18.5/beartype/_util/api/utilapisphinx.py000066400000000000000000000076201461113517100224000ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **Sphinx utilities** (i.e., low-level callables handling the third-party :mod:`sphinx` package as an optional runtime dependency of this project). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To prevent this project from accidentally requiring third-party # packages as mandatory runtime dependencies, avoid importing from *ANY* such # package via a module-scoped import. These imports should be isolated to the # bodies of callables declared below. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype._util.func.utilfuncframe import iter_frames from sys import modules as module_imported_names # ....................{ TESTERS }.................... def is_sphinx_autodocing() -> bool: ''' :data:`True` only if Sphinx is currently **autogenerating documentation** (i.e., if this function has been called from a Python call stack invoked by the ``autodoc`` extension bundled with the optional third-party build-time :mod:`sphinx` package). ''' # If the "autodoc" extension has *NOT* been imported, Sphinx by definition # *CANNOT* be autogenerating documentation. In this case, return false. # # Note this technically constitutes an optional (albeit pragmatically # critical) optimization. This test is O(1) with negligible constants, # whereas the additional test below is O(n) with non-negligible constants. # Ergo, this efficient test short-circuits the inefficient test below. if _SPHINX_AUTODOC_SUBPACKAGE_NAME not in module_imported_names: return False # Else, the "autodoc" extension has been imported. Since this does *NOT* # conclusively imply that Sphinx is currently autogenerating documentation, # further testing is required to avoid returning false positives (and thus # erroneously reducing @beartype to a noop, which would be horrifying). # # Specifically, we iteratively search up the call stack for a stack frame # originating from the "autodoc" extension. If we find such a stack frame, # Sphinx is currently autogenerating documentation; else, Sphinx is not. #FIXME: Refactor this to leverage a genuinely valid working solution #hopefully provided out-of-the-box by some hypothetical new bleeding-edge #version of Sphinx *AFTER* they resolve our feature request for this: # https://github.com/sphinx-doc/sphinx/issues/9805 # For each stack frame on the call stack, ignoring the stack frame # encapsulating the call to this tester... for frame in iter_frames(func_stack_frames_ignore=1): # Fully-qualified name of this scope's module if this scope defines # this name *OR* "None" otherwise. frame_module_name = frame.f_globals.get('__name__') # print(f'Visiting frame (module: "{func_frame_module_name}")...') # If this scope's module is the "autodoc" extension, Sphinx is # currently autogenerating documentation. In this case, return true. if ( frame_module_name and frame_module_name.startswith(_SPHINX_AUTODOC_SUBPACKAGE_NAME) ): return True # Else, this scope's module is *NOT* the "autodoc" extension. # Else, *NO* scope's module is the "autodoc" extension. Return false. return False # ....................{ PRIVATE ~ magic }.................... _SPHINX_AUTODOC_SUBPACKAGE_NAME = 'sphinx.ext.autodoc' ''' Fully-qualified name of the subpackage providing the ``autodoc`` extension bundled with Sphinx. ''' beartype-0.18.5/beartype/_util/api/utilapityping.py000066400000000000000000000467461461113517100224150ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **typing module** utilities (i.e., callables dynamically testing and importing attributes declared at module scope by either the standard :mod:`typing` or third-party :mod:`typing_extensions` modules). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeModuleAttributeNotFoundWarning from beartype.roar._roarexc import _BeartypeUtilModuleException from beartype.typing import ( Any, Iterable, Union, ) from beartype._data.hint.datahinttyping import TypeException from beartype._data.module.datamodtyping import TYPING_MODULE_NAMES from beartype._util.cache.utilcachecall import callable_cached from beartype._util.error.utilerrwarn import issue_warning from beartype._util.module.utilmodimport import import_module_attr_or_none from collections.abc import Iterable as IterableABC # ....................{ TESTERS }.................... #FIXME: Unit test us up, please. def is_typing_attr( # Mandatory parameters. typing_attr_basename: str, # Optional parameters. exception_cls: TypeException = _BeartypeUtilModuleException, ) -> bool: ''' :data:`True` only if a **typing attribute** (i.e., object declared at module scope by either the :mod:`typing` or :mod:`typing_extensions` modules) with the passed unqualified name is importable from one or more of these modules. This function is effectively memoized for efficiency. Parameters ---------- typing_attr_basename : str Unqualified name of the attribute to be imported from a typing module. Returns ------- bool :data:`True` only if the :mod:`typing` or :mod:`typing_extensions` modules declare an attribute with this name. exception_cls : Type[Exception] Type of exception to be raised by this function. Defaults to :class:`._BeartypeUtilModuleException`. Raises ------ exception_cls If this name is syntactically invalid. Warns ----- BeartypeModuleUnimportableWarning If any of these modules raise module-scoped exceptions at importation time. That said, the :mod:`typing` and :mod:`typing_extensions` modules are scrupulously tested and thus unlikely to raise such exceptions. ''' # Return true only if an attribute with this name is importable from either # the "typing" *OR* "typing_extensions" modules. # # Note that positional rather than keyword arguments are intentionally # passed to optimize memoization efficiency. return import_typing_attr_or_none( typing_attr_basename, exception_cls) is not None # ....................{ IMPORTERS }.................... def import_typing_attr( # Mandatory parameters. typing_attr_basename: str, # Optional parameters. exception_cls: TypeException = _BeartypeUtilModuleException, ) -> Any: ''' Dynamically import and return the **typing attribute** (i.e., object declared at module scope by either the :mod:`typing` or :mod:`typing_extensions` modules) with the passed unqualified name if importable from one or more of these modules *or* raise an exception otherwise (i.e., if this attribute is *not* importable from these modules). This function is effectively memoized for efficiency. Parameters ---------- typing_attr_basename : str Unqualified name of the attribute to be imported from a typing module. exception_cls : Type[Exception] Type of exception to be raised by this function. Defaults to :class:`._BeartypeUtilModuleException`. Returns ------- object Attribute with this name dynamically imported from a typing module. Raises ------ exception_cls If either: * This name is syntactically invalid. * Neither the :mod:`typing` nor :mod:`typing_extensions` modules declare an attribute with this name. Warns ----- BeartypeModuleUnimportableWarning If any of these modules raise module-scoped exceptions at importation time. That said, the :mod:`typing` and :mod:`typing_extensions` modules are scrupulously tested and thus unlikely to raise such exceptions. See Also -------- :func:`beartype._util.module.utilmodimport.import_module_typing_any_attr_or_none` Further details. ''' # Avoid circular import dependencies. from beartype._util.module.utilmodtest import is_module # Attribute with this name imported from either the "typing" or # "typing_extensions" modules if one or more of these modules declare this # attribute *OR* "None" otherwise. # # Note that positional rather than keyword arguments are intentionally # passed to optimize memoization efficiency. typing_attr = import_typing_attr_or_none( typing_attr_basename, exception_cls) # If none of these modules declare this attribute... if typing_attr is None: # Substrings prefixing and suffixing exception messages raised below. EXCEPTION_PREFIX = ( f'Typing attributes "typing.{typing_attr_basename}" and ' f'"typing_extensions.{typing_attr_basename}" not found. ' ) EXCEPTION_SUFFIX = ( 'We apologize for the inconvenience and hope you had a ' 'great dev cycle flying with Air Beartype, ' '"Your Grizzled Pal in the Friendly Skies."' ) # If the "typing_extensions" module is importable, raise an # appropriate exception. if is_module('typing_extensions'): raise exception_cls( f'{EXCEPTION_PREFIX} Please either ' f'(A) update the "typing_extensions" package or ' f'(B) update to a newer Python version. {EXCEPTION_SUFFIX}' ) # Else, the "typing_extensions" module is unimportable. In this # case, raise an appropriate exception. else: raise exception_cls( f'{EXCEPTION_PREFIX} Please either ' f'(A) install the "typing_extensions" package or ' f'(B) update to a newer Python version. {EXCEPTION_SUFFIX}' ) # Else, one or more of these modules declare this attribute. # Return this attribute. return typing_attr #FIXME: Unit test us up, please. def import_typing_attr_or_none( # Mandatory parameters. typing_attr_basename: str, # Optional parameters. exception_cls: TypeException = _BeartypeUtilModuleException, ) -> Any: ''' Dynamically import and return the **typing attribute** (i.e., object declared at module scope by either the :mod:`typing` or :mod:`typing_extensions` modules) with the passed unqualified name if importable from one or more of these modules *or* :data:`None` otherwise otherwise (i.e., if this attribute is *not* importable from these modules). This function is effectively memoized for efficiency. Parameters ---------- typing_attr_basename : str Unqualified name of the attribute to be imported from a typing module. exception_cls : Type[Exception] Type of exception to be raised by this function. Defaults to :class:`._BeartypeUtilModuleException`. Returns ------- object Attribute with this name dynamically imported from a typing module. Raises ------ exception_cls If this name is syntactically invalid. Warns ----- BeartypeModuleUnimportableWarning If any of these modules raise module-scoped exceptions at importation time. That said, the :mod:`typing` and :mod:`typing_extensions` modules are scrupulously tested and thus unlikely to raise exceptions. See Also -------- :func:`import_typing_attr_or_fallback` Further details. ''' # One-liners in the rear view mirror may be closer than they appear. # # Note that parameters are intentionally passed positionally rather than by # keyword for memoization efficiency. return import_typing_attr_or_fallback( typing_attr_basename, None, exception_cls) #FIXME: Unit test us up, please. #FIXME: Leverage above, please. @callable_cached def import_typing_attr_or_fallback( # Mandatory parameters. typing_attr_basename: str, fallback: object, # Optional parameters. exception_cls: TypeException = _BeartypeUtilModuleException, ) -> Any: ''' Dynamically import and return the **typing attribute** (i.e., object declared at module scope by either the :mod:`typing` or :mod:`typing_extensions` modules) with the passed unqualified name if importable from one or more of these modules *or* the passed fallback otherwise otherwise (i.e., if this attribute is *not* importable from these modules). Specifically, this function (in order): #. If the official :mod:`typing` module bundled with the active Python interpreter declares that attribute, dynamically imports and returns that attribute from that module. #. Else if the third-party (albeit quasi-official) :mod:`typing_extensions` module requiring external installation under the active Python interpreter declares that attribute, dynamically imports and returns that attribute from that module. #. Else, returns the passed fallback value. This function is memoized for efficiency. Parameters ---------- typing_attr_basename : str Unqualified name of the attribute to be imported from a typing module. fallback : object Arbitrary value to be returned as a last-ditch fallback if *no* typing module declares this attribute. exception_cls : Type[Exception] Type of exception to be raised by this function. Defaults to :class:`._BeartypeUtilModuleException`. Returns ------- object Attribute with this name dynamically imported from a typing module. Raises ------ exception_cls If this name is syntactically invalid. Warns ----- BeartypeModuleUnimportableWarning If any of these modules raise module-scoped exceptions at importation time. That said, the :mod:`typing` and :mod:`typing_extensions` modules are scrupulously tested and thus unlikely to raise exceptions. ''' # Attribute with this name imported from the "typing" module if that module # declares this attribute *OR* "None" otherwise. typing_attr = import_module_attr_or_none( attr_name=f'typing.{typing_attr_basename}', exception_cls=exception_cls, exception_prefix='Typing attribute ', ) # If the "typing" module does *NOT* declare this attribute... if typing_attr is None: # Attribute with this name imported from the "typing_extensions" module # if that module declares this attribute *OR* "None" otherwise. typing_attr = import_module_attr_or_none( attr_name=f'typing_extensions.{typing_attr_basename}', exception_cls=exception_cls, exception_prefix='Typing attribute ', ) # If the "typing_extensions" module also does *NOT* declare this # attribute, fallback to the passed fallback value. if typing_attr is None: typing_attr = fallback # Else, the "typing_extensions" module declares this attribute. # Else, the "typing" module declares this attribute. # Return either this attribute if one or more of these modules declare this # attribute *OR* this fallback otherwise. return typing_attr # ....................{ GETTERS }.................... #FIXME: Unit test us up, please. @callable_cached def get_typing_attrs(typing_attr_basename: str) -> frozenset: ''' Frozen set of all attributes with the passed unqualified basename declared by all importable typing modules, silently ignoring those modules failing to declare this attribute. This getter intentionally returns a set rather than a list. Why? Duplicates. The third-party :mod:`typing_extensions` module duplicates *all* type hint factories implemented by the standard :mod:`typing` module under the most recently released version of Python. This getter is memoized for efficiency. Attributes ---------- typing_attr_basename : str Unqualified name of the attribute to be dynamically imported from each typing module. Yields ------ set Set of all attributes with the passed unqualified basename declared by all importable typing modules. ''' assert isinstance(typing_attr_basename, str), ( f'{repr(typing_attr_basename)} not string.') # Set of all importable attributes to be returned by this getter. typing_attrs: set = set() # For the fully-qualified name of each quasi-standard typing module... for typing_module_name in TYPING_MODULE_NAMES: # Attribute with this name dynamically imported from that module if # that module defines this attribute *OR* "None" otherwise. typing_attr = import_module_attr_or_none( f'{typing_module_name}.{typing_attr_basename}') # If that module fails to define this attribute, silently continue to # the next module. if typing_attr is None: continue # Else, that module declares this attribute. # Append this attribute to this list. typing_attrs.add(typing_attr) # Return this set, coerced into a frozen set for caching purposes. return frozenset(typing_attrs) # ....................{ ITERATORS }.................... #FIXME: Replace *ALL* calls to this by calls to get_typing_attrs(), please. #FIXME: Currently unused but preserved for posterity. Consider excising, please. def iter_typing_attrs( # Mandatory parameters. typing_attr_basenames: Union[str, Iterable[str]], # Optional parameters. is_warn: bool = False, typing_module_names: Iterable[str] = TYPING_MODULE_NAMES, ) -> IterableABC: ''' Generator iteratively yielding all attributes with the passed basename declared by the quasi-standard typing modules with the passed fully-qualified names, silently ignoring those modules failing to declare such an attribute. Attributes ---------- typing_attr_basenames : Union[str, Iterable[str]] Either: * Unqualified name of the attribute to be dynamically imported from each typing module, in which case either: * If the currently iterated typing module defines this attribute, this generator yields this attribute imported from that module. * Else, this generator silently ignores that module. * Iterable of one or more such names, in which case either: * If the currently iterated typing module defines *all* attributes, this generator yields a tuple whose items are these attributes imported from that module (in the same order). * Else, this generator silently ignores that module. is_warn : bool :data:`True` only if emitting non-fatal warnings for typing modules failing to define all passed attributes. If ``typing_module_names`` is passed, this parameter should typically also be passed as :data:`True` for safety. Defaults to :data:`False`. typing_module_names: Iterable[str] Iterable of the fully-qualified names of all typing modules to dynamically import this attribute from. Defaults to :data:`.TYPING_MODULE_NAMES`. Yields ------ Union[object, Tuple[object, ...]] Either: * If passed only an attribute basename, the attribute with that basename declared by each typing module. * If passed an iterable of one or more attribute basenames, a tuple whose items are the attributes with those basenames (in the same order) declared by each typing module. ''' assert isinstance(is_warn, bool), f'{is_warn} not boolean.' assert isinstance(typing_attr_basenames, (str, IterableABC)), ( f'{typing_attr_basenames} not string.') assert typing_attr_basenames, '"typing_attr_basenames" empty.' assert isinstance(typing_module_names, IterableABC), ( f'{repr(typing_module_names)} not iterable.') assert typing_module_names, '"typing_module_names" empty.' assert all( isinstance(typing_module_name, str) for typing_module_name in typing_module_names ), f'One or more {typing_module_names} items not strings.' # If passed an attribute basename, pack this into a tuple containing only # this basename for ease of use. if isinstance(typing_attr_basenames, str): typing_attr_basenames = (typing_attr_basenames,) # Else, an iterable of attribute basenames was passed. In this case... else: assert all( isinstance(typing_attr_basename, str) for typing_attr_basename in typing_attr_basenames ), f'One or more {typing_attr_basenames} items not strings.' # In either case, this parameter is now a tuple of attribute basenames. # List of all imported attributes to be yielded from each iteration of the # generator implicitly returned by this generator function. typing_attrs: list = [] # For the fully-qualified name of each quasi-standard typing module... for typing_module_name in typing_module_names: # Clear this list *BEFORE* appending to this list below. typing_attrs.clear() # For the basename of each attribute to be imported from that module... for typing_attr_basename in typing_attr_basenames: # Fully-qualified name of this attribute declared by that module. typing_attr_name = f'{typing_module_name}.{typing_attr_basename}' # Attribute with this name dynamically imported from that module if # that module defines this attribute *OR* "None" otherwise. typing_attr = import_module_attr_or_none(typing_attr_name) # If that module fails to define this attribute... if typing_attr is None: # If emitting non-fatal warnings, do so. if is_warn: issue_warning( cls=BeartypeModuleAttributeNotFoundWarning, message=( f'Ignoring undefined typing attribute ' f'"{typing_attr_name}"...' ), ) # Else, silently reduce to a noop. # Continue to the next module. break # Else, that module declares this attribute. # Append this attribute to this list. typing_attrs.append(typing_attr) # If that module declares *ALL* attributes... else: # If exactly one attribute name was passed, yield this attribute as # is (*WITHOUT* packing this attribute into a tuple). if len(typing_attrs) == 1: yield typing_attrs[0] # Else, two or more attribute names were passed. In this case, yield # these attributes as a tuple. else: yield tuple(typing_attrs) # Else, that module failed to declare one or more attributes. In this # case, silently continue to the next module. beartype-0.18.5/beartype/_util/ast/000077500000000000000000000000001461113517100171365ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/ast/__init__.py000066400000000000000000000000001461113517100212350ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/ast/utilastget.py000066400000000000000000000074041461113517100217020ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **abstract syntax tree (AST) getters** (i.e., low-level callables acquiring various properties of various nodes in the currently visited AST). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from ast import ( AST, Module, dump as ast_dump, parse as ast_parse, ) from beartype.roar._roarexc import _BeartypeUtilAstException from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_9 # ....................{ GETTERS ~ node }.................... #FIXME: Unit test us up, please. def get_node_repr_indented(node: AST) -> str: ''' Human-readable string pretty-printing the contents of the passed abstract syntax tree (AST), complete with readable indentation. Parameters ---------- node : AST AST to be pretty-printed. Returns ------- str Human-readable string pretty-printing the contents of this AST. ''' assert isinstance(node, AST), f'{repr(node)} not AST.' # Return either... return ( # If the active Python interpreter targets Python >= 3.9, the # pretty-printed contents of this AST. Sadly, the "indent=4" parameter # pretty-printing this AST was first introduced by Python 3.9. ast_dump(node, indent=4) # type: ignore[call-arg] if IS_PYTHON_AT_LEAST_3_9 else # Else, the active Python interpreter targets Python < 3.9. In this # case, the non-pretty-printed contents of this AST as a single line. ast_dump(node) ) # ....................{ GETTERS ~ node }.................... #FIXME: Unit test us up, please. When we do, remove the "pragma: no cover" from #the body of this getter below. def get_code_child_node(code: str) -> AST: ''' Abstract syntax tree (AST) node parsed from the passed (presumably) triple-quoted string defining a single child object. This function is principally intended to be called from our test suite as a convenient means of "parsing" triple-quoted strings into AST nodes. Caveats ------- **This function assumes that this string defines only a single child object.** If this string defines either no *or* two or more child objects, an exception is raised. Parameters ---------- code : str Triple-quoted string defining a single child object. Returns ------- AST AST node encapsulating the object defined by this string. Raises ------- _BeartypeUtilAstException If this string defines either no *or* two or more child objects. ''' assert isinstance(code, str), f'{repr(code)} not string.' # "ast.Module" AST tree parsed from this string. node_module = ast_parse(code) # If this node is *NOT* actually a module node, raise an exception. if not isinstance(node_module, Module): # pragma: no cover raise _BeartypeUtilAstException( f'{repr(node_module)} not AST module node.') # Else, this node is a module node. # List of all direct child nodes of this parent module name. nodes_child = node_module.body # If this module node contains either no *OR* two or more child nodes, raise # an exception. if len(nodes_child) != 1: # pragma: no cover raise _BeartypeUtilAstException( f'Python code {repr(code)} defines ' f'{len(nodes_child)} != 1 child objects.' ) # Else, this module node contains exactly one child node. # Return this child node. return nodes_child[0] beartype-0.18.5/beartype/_util/ast/utilastmake.py000066400000000000000000000423311461113517100220360ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **abstract syntax tree (AST) factories** (i.e., low-level callables creating and returning various types of nodes, typically for inclusion in the currently visited AST). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from ast import ( AST, Attribute, Call, Constant, Expr, FormattedValue, ImportFrom, Name, alias, keyword, ) from beartype.roar import BeartypeClawImportAstException from beartype.typing import ( List, Optional, ) from beartype._cave._cavemap import NoneTypeOr from beartype._data.ast.dataast import ( NODE_CONTEXT_LOAD, NODE_CONTEXT_STORE, ) from beartype._data.hint.datahinttyping import NodesList from beartype._data.kind.datakindsequence import LIST_EMPTY from beartype._util.ast.utilastmunge import copy_node_metadata # ....................{ FACTORIES }.................... #FIXME: Unit test us up, please. def make_node_importfrom( # Mandatory parameters. module_name: str, source_attr_name: str, node_sibling: AST, # Optional parameters. target_attr_name: Optional[str] = None, ) -> ImportFrom: ''' Create and return a new **import-from abstract syntax tree (AST) node** (i.e., node encapsulating an import statement of the alias-style format ``from {module_name} import {attr_name}``) importing the attribute with the passed source name from the module with the passed name into the currently visited module as a new attribute with the passed target name. Parameters ---------- module_name : str Fully-qualified name of the module to import this attribute from. source_attr_name : str Unqualified basename of the attribute to import from this module. target_attr_name : Optional[str] Either: * If this attribute is to be imported into the currently visited module under a different unqualified basename, that basename. * If this attribute is to be imported into the currently visited module under the same unqualified basename as ``source_attr_name``, :data:`None`. Defaults to :data:`None`. node_sibling : AST Sibling node to copy source code metadata from. Returns ------- ImportFrom Import-from node importing this attribute from this module. ''' assert isinstance(module_name, str), f'{repr(module_name)} not string.' assert isinstance(source_attr_name, str), ( f'{repr(source_attr_name)} not string.') assert isinstance(target_attr_name, NoneTypeOr[str]), ( f'{repr(target_attr_name)} neither string nor "None".') # Node encapsulating the name of the attribute to import from this module, # defined as either... node_importfrom_name = ( # If this attribute is to be imported into the currently visited module # under a different basename, do so; alias(name=source_attr_name, asname=target_attr_name) if target_attr_name else # Else, this attribute is to be imported into the currently visited # module under the same basename. In this case, do so. alias(name=source_attr_name) ) # Node encapsulating the name of the module to import this attribute from. node_importfrom = ImportFrom( module=module_name, names=[node_importfrom_name], # Force an absolute import for safety (i.e., prohibit relative imports). level=0, ) # Copy all source code metadata (e.g., line numbers) from this sibling node # onto these new nodes. copy_node_metadata( node_src=node_sibling, node_trg=(node_importfrom, node_importfrom_name)) # Return this import-from node. return node_importfrom # ....................{ FACTORIES ~ attribute }.................... #FIXME: Unit test us up, please. def make_node_object_attr_load( # Mandatory parameters. attr_name: str, node_sibling: AST, # Optional parameters. node_obj: Optional[AST] = None, obj_name: Optional[str] = None, ) -> Attribute: ''' Create and return a new **object attribute access abstract syntax tree (AST) node** (i.e., node encapsulating an access of an object attribute) of the passed object with the passed attribute name. Note that exactly one of the ``node_obj`` and ``obj_name`` parameters *must* be passed. If neither or both of these parameters are passed, an exception is raised. Parameters ---------- attr_name : str Unqualified basename of the attribute of this object to be accessed. node_sibling : AST Sibling node to copy source code metadata from. node_obj : Optional[AST] Either: * If the caller prefers supplying the name node accessing the parent object to load this attribute from, that node. * Else, :data:`None`. In this case, the caller *must* pass the ``obj_name`` parameter. Defaults to :data:`None`. obj_name : Optional[str] Either: * If the caller prefers supplying the unqualified basename of the parent object to load this attribute from in the current lexical scope, that basename. * Else, :data:`None`. In this case, the caller *must* pass the ``node_obj`` parameter. Defaults to :data:`None`. Returns ------- Attribute Object attribute node accessing this attribute of this object. Raises ------ BeartypeClawImportAstException If either: * Neither the ``node_obj`` nor ``obj_name`` parameters are passed. * Both of the ``node_obj`` and ``obj_name`` parameters are passed. ''' assert isinstance(attr_name, str), f'{repr(attr_name)} not string.' # If the caller passed *NO* name node accessing the parent object to load # this attribute from... if not node_obj: # If the caller also passed *NO* unqualified basename of that object, # raise an exception. if not obj_name: raise BeartypeClawImportAstException( f'Attribute "{attr_name}" parent object undefined ' f'(i.e., neither "node_obj" nor "obj_name" parameters passed).' ) # Else, the caller also passed the unqualified basename of that object. # Child node accessing that object with this basename. node_obj = make_node_name_load(name=obj_name, node_sibling=node_sibling) # Else, the caller passed a name node accessing that object. # # If the caller also passed the unqualified basename of that object, raise # an exception. elif obj_name: raise BeartypeClawImportAstException( f'Attribute "{attr_name}" parent object overly defined ' f'(i.e., both "node_obj" and "obj_name" parameters passed).' ) # Else, the caller passed *NO* unqualified basename of that object. # # In any case, the "node_obj" variable is now the desired object node. assert isinstance(node_obj, AST), ( f'{repr(node_obj)} not AST node.') # Object attribute node accessing this attribute of this object. node_attribute_load = Attribute( value=node_obj, attr=attr_name, ctx=NODE_CONTEXT_LOAD) # Copy source code metadata from this sibling node onto this new node. copy_node_metadata(node_src=node_sibling, node_trg=node_attribute_load) # Return this node. return node_attribute_load # ....................{ FACTORIES ~ call }.................... #FIXME: Unit test us up, please. def make_node_call_expr(*args, node_sibling: AST, **kwargs) -> Expr: ''' Create and return a new **callable call expression abstract syntax tree (AST) node** (i.e., node encapsulating a Python expression expressing a call to an arbitrary function or method) calling the function or method with the passed name, positional arguments, and keyword arguments. Parameters ---------- node_sibling : AST Sibling node to copy source code metadata from. All remaining passed positional and keyword parameters are passed to the lower-level :func:`.make_node_call` factory function as is. Returns ------- Expr Expression node calling this callable with these parameters. ''' # Child node calling this callable. node_func_call = make_node_call(*args, node_sibling=node_sibling, **kwargs) # type: ignore[misc] # Child node expressing this call as a Python expression. node_func = Expr(node_func_call) # Copy source code metadata from this sibling node onto this new node. copy_node_metadata(node_src=node_sibling, node_trg=node_func) # Return this expression node. return node_func #FIXME: Unit test us up, please. def make_node_call( # Mandatory parameters. func_name: str, node_sibling: AST, # Optional parameters. nodes_args: NodesList = LIST_EMPTY, nodes_kwargs: List[keyword] = LIST_EMPTY, ) -> Call: ''' Create and return a new **callable call abstract syntax tree (AST) node** (i.e., node encapsulating a call to an arbitrary function or method) calling the function or method with the passed name, positional arguments, and keyword arguments. Parameters ---------- func_name : str Fully-qualified name of the module to import this attribute from. node_sibling : AST Sibling node to copy source code metadata from. nodes_args : NodesList, optional List of zero or more **positional parameter AST nodes** comprising the tuple of all positional parameters to be passed to this call. Defaults to the empty list. nodes_kwargs : NodesList, optional List of zero or more **keyword parameter AST nodes** comprising the dictionary of all keyword parameters to be passed to this call. Defaults to the empty list. Returns ------- Call Callable call node calling this callable with these parameters. ''' assert isinstance(nodes_args, list), f'{repr(nodes_args)} not list.' assert isinstance(nodes_kwargs, list), f'{repr(nodes_kwargs)} not list.' assert all( isinstance(node_args, AST) for node_args in nodes_args), ( f'{repr(nodes_args)} not list of AST nodes.') assert all( isinstance(node_kwargs, keyword) for node_kwargs in nodes_kwargs), ( f'{repr(nodes_kwargs)} not list of keyword nodes.') # Child node referencing the callable to be called. node_func_name = make_node_name_load( name=func_name, node_sibling=node_sibling) # Child node calling this callable. node_func_call = Call( func=node_func_name, args=nodes_args, keywords=nodes_kwargs, ) # Copy source code metadata from this sibling node onto this new node. copy_node_metadata(node_src=node_sibling, node_trg=node_func_call) # Return this call node. return node_func_call # ....................{ FACTORIES ~ call : arg }.................... #FIXME: Unit test us up, please. def make_node_kwarg( kwarg_name: str, kwarg_value: AST, node_sibling: AST) -> keyword: ''' Create and return a new **keyword argument abstract syntax tree (AST) node** (i.e., node encapsulating a keyword argument of a call to an arbitrary function or method) passing the keyword argument with the passed name and value to some parent node encapsulating a call to some function or method. Parameters ---------- kwarg_name : str Name of this keyword argument. kwarg_value : AST Node passing the value of this keyword argument. node_sibling : AST Sibling node to copy source code metadata from. Returns ------- keyword Keyword node passing a keyword argument with this name and value. ''' assert isinstance(kwarg_name, str), f'{repr(kwarg_name)} not string.' assert isinstance(kwarg_value, AST), f'{repr(kwarg_value)} not AST node.' # Child node encapsulating this keyword argument. node_kwarg = keyword(arg=kwarg_name, value=kwarg_value) # Copy source code metadata from this sibling node onto this new node. copy_node_metadata(node_src=node_sibling, node_trg=node_kwarg) # Return this expression node. return node_kwarg # ....................{ FACTORIES ~ literal : string }.................... #FIXME: Unit test us up, please. def make_node_str(text: str, node_sibling: AST) -> Constant: ''' Create and return a new **string literal abstract syntax tree (AST) node** (i.e., node encapsulating the passed string). Parameters ---------- text : str String literal to be encapsulated in a new node. node_sibling : AST Sibling node to copy source code metadata from. Returns ------- Constant String literal node encapsulating this string. ''' assert isinstance(text, str), f'{repr(text)} not string.' # Child node encapsulating this string. node_str = Constant(value=text) # Copy source code metadata from this sibling node onto this new node. copy_node_metadata(node_src=node_sibling, node_trg=node_str) # Return this string literal node. return node_str # ....................{ FACTORIES ~ literal : f-string }.................... #FIXME: Unit test us up, please. def make_node_fstr_field(node_expr: AST, node_sibling: AST) -> FormattedValue: ''' Create and return a new **f-string formatting field abstract syntax tree (AST) node** (i.e., node embedding the substring created and returned by the evaluation of the passed arbitrary expression in some parent node encapsulating an f-string embedding this field). This factory function creates substrings resembling ``{some_fstr_field}`` in larger f-strings resembling ``f'This is {some_fstr_field}, isn't it?'``. Caveats ------- This field assumes *no* suffixing ``!``-prefixed conversion (e.g., "!a", "!r", "!s"). Thankfully, those conversions are only syntactic sugar for more human-readable builtins (e.g., ``repr()``, ``str()``). Ergo, this caveat does *not* actually constitute a hard constraint. Just prefer the builtins. Parameters ---------- node_expr : AST Formatting field to be embedded in some parent f-string node. node_sibling : AST Sibling node to copy source code metadata from. Returns ------- Name Name node accessing this attribute in the current lexical scope. ''' assert isinstance(node_expr, AST), f'{repr(node_expr)} not AST node.' # Child node encapsulating a formatting field "{node_expr.value}" in some # parent node encapsulating an f-string embedding this field. For unknown # reasons, the standard "ast" module requires that the "conversion" # parameter be passed as a non-standard magic integer constant. Whatevahs! node_fstr_field = FormattedValue(value=node_expr, conversion=-1) # Copy source code metadata from this sibling node onto this new node. copy_node_metadata(node_src=node_sibling, node_trg=node_fstr_field) # Return this f-string field node. return node_fstr_field # ....................{ FACTORIES ~ name }.................... #FIXME: Unit test us up. def make_node_name_load(name: str, node_sibling: AST) -> Name: ''' Create and return a new **attribute access abstract syntax tree (AST) node** (i.e., node encapsulating an access of an attribute) in the current lexical scope with the passed name. Parameters ---------- name : str Fully-qualified name of the attribute to be accessed. node_sibling : AST Sibling node to copy source code metadata from. Returns ------- Name Name node accessing this attribute in the current lexical scope. ''' assert isinstance(name, str), f'{repr(name)} not string.' # Child node accessing this attribute in the current lexical scope. node_name = Name(name, ctx=NODE_CONTEXT_LOAD) # Copy source code metadata from this sibling node onto this new node. copy_node_metadata(node_src=node_sibling, node_trg=node_name) # Return this child node. return node_name #FIXME: Unit test us up. def make_node_name_store(name: str, node_sibling: AST) -> Name: ''' Create and return a new **attribute assignment abstract syntax tree (AST) node** (i.e., node encapsulating an assignment of an attribute) in the current lexical scope with the passed name. Parameters ---------- name : str Fully-qualified name of the attribute to be assigned. node_sibling : AST Sibling node to copy source code metadata from. Returns ------- Name Name node assigning this attribute in the current lexical scope. ''' assert isinstance(name, str), f'{repr(name)} not string.' # Child node assigning this attribute in the current lexical scope. node_name = Name(name, ctx=NODE_CONTEXT_STORE) # Copy source code metadata from this sibling node onto this new node. copy_node_metadata(node_src=node_sibling, node_trg=node_name) # Return this child node. return node_name beartype-0.18.5/beartype/_util/ast/utilastmunge.py000066400000000000000000000104021461113517100222260ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **abstract syntax tree (AST) mungers** (i.e., low-level callables modifying various properties of various nodes in the currently visited AST). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from ast import AST from beartype.typing import ( Iterable, Union, ) # ....................{ COPIERS }.................... #FIXME: Unit test us up, please. def copy_node_metadata( node_src: AST, node_trg: Union[AST, Iterable[AST]]) -> None: ''' Copy all **source code metadata** (i.e., beginning and ending line and column numbers) from the passed source abstract syntax tree (AST) node onto the passed target AST node(s). This function is an efficient alternative to: * The extremely inefficient (albeit still useful) :func:`ast.fix_missing_locations` function. * The mildly inefficient (and mostly useless) :func:`ast.copy_location` function. The tradeoffs are as follows: * :func:`ast.fix_missing_locations` is :math:`O(n)` time complexity for :math:`n` the number of AST nodes across the entire AST tree, but requires only a single trivial call and is thus considerably more "plug-and-play" than this function. * This function is :math:`O(1)` time complexity irrespective of the size of the AST tree, but requires one still mostly trivial call for each synthetic AST node inserted into the AST tree by the :class:`BeartypeNodeTransformer` above. Caveats ------- **This function should only be passed nodes that support code metadata.** Although *most* nodes do, some nodes do not. Why? Because they are *not* actually nodes; they simply masquerade as nodes in documentation for the standard :mod:`ast` module, which inexplicably makes *no* distinction between the two. These pseudo-nodes include: * :class:`ast.Del` nodes. * :class:`ast.Load` nodes. * :class:`ast.Store` nodes. Indeed, this observation implies that these pseudo-nodes may be globalized as singletons for efficient reuse throughout our AST generation algorithms. Lastly, note that nodes may be differentiated from pseudo-nodes by passing the call to the :func:`ast.dump` function in the code snippet presented in the docstring for the :class:`BeartypeNodeTransformer` class an additional ``include_attributes=True`` parameter: e.g., .. code-block:: python print(ast.dump(ast.parse(CODE), indent=4, include_attributes=True)) Actual nodes have code metadata printed for them; pseudo-nodes do *not*. Parameters ---------- node_src : AST Source AST node to copy source code metadata from. node_trg : Union[AST, Iterable[AST]] Either: * A single target AST node to copy source code metadata onto. * An iterable of zero or more target AST nodes to copy source code metadata onto. See Also -------- :func:`ast.copy_location` Less efficient analogue of this function running in :math:`O(k)` time complexity for :math:`k` the number of types of source code metadata. Typically, :math:`k == 4`. ''' assert isinstance(node_src, AST), f'{repr(node_src)} not AST node.' # If passed only a single target node, wrap this node in a 1-tuple # containing only this node for simplicity. if isinstance(node_trg, AST): node_trg = (node_trg,) # In either case, "node_trg" is now an iterable of target nodes. # For each passed target node... for node_trg_cur in node_trg: assert isinstance(node_trg_cur, AST), ( f'{repr(node_trg_cur)} not AST node.') # Copy all source code metadata from this source to target node. node_trg_cur.lineno = node_src.lineno node_trg_cur.col_offset = node_src.col_offset node_trg_cur.end_lineno = node_src.end_lineno # type: ignore[attr-defined] node_trg_cur.end_col_offset = node_src.end_col_offset # type: ignore[attr-defined] beartype-0.18.5/beartype/_util/ast/utilasttest.py000066400000000000000000000151761461113517100221070ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **abstract syntax tree (AST) testers** (i.e., low-level callables testing various properties of various nodes in the currently visited AST). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._data.hint.datahinttyping import NodeCallable # ....................{ TESTERS }.................... def is_node_callable_typed(node: NodeCallable) -> bool: ''' :data:`True` only if the passed **callable node** (i.e., node signifying the definition of a pure-Python function or method) is **typed** (i.e., annotated by a return type hint and/or one or more parameter type hints). Parameters ---------- node : NodeCallable Callable node to be tested. Returns ------- bool :data:`True` only if this callable node is typed. ''' # Note that this algorithm is intentionally implemented in an unintuitive # order so as to increase the likelihood of efficiently deciding this # problem in best-case O(1) time. Specifically: # * It is most efficient to test whether that callable is annotated by a # return type hint. # * It is next-most efficient to test whether that callable accepts a # variadic positional or keyword argument annotated by a type hint. # * It is least efficient to test whether that callable accepts a # non-variadic parameter annotated by a type hint, as doing so requires # O(n) iteration for "n" the number of such arguments. # # Lastly, note that we could naively avoid doing this entirely and instead # unconditionally decorate *ALL* callables by @beartype -- in which case # @beartype would simply reduce to a noop for untyped callables annotated by # *NO* type hints. Technically, that works. Pragmatically, that would almost # certainly be slower than the current approach under the common assumption # that any developer annotating one or more non-variadic parameters of a # callable would also annotate the return of that callable -- in which case # this detection reduces to O(1) time complexity. Even where this is *NOT* # the case, however, this is still almost certainly slightly faster or of an # equivalent speed to the naive approach. Why? Because treating untyped # callables as typed would needlessly: # * Increase space complexity by polluting this AST with needlessly many # "Name" child nodes performing untyped @beartype decorations. # * Increase time complexity by instantiating, initializing, and inserting # (the three dread i's) those nodes. # # Admittedly, this approach is *CONSIDERABLY* slower for untyped callables, # where this detection exhibits worst-case O(n) time complexity. In theory, # the "beartype.claw" API that calls this tester function once per callable # should *NEVER* be applied to untyped callables. In practice, that API # almost certainly will be. We (largely) consider that the responsibility of # the caller, however. Beartype can't be faulted for optimizing for the # ideal case of well-typed packages... *CAN IT*!?!? o_O # If the return of that callable is typed, that callable is typed. In this # case, immediately and efficiently return true. if node.returns: return True # Else, the return of that callable is untyped. # Child arguments node of all arguments accepted by that callable. node_arg_nodes = node.args # If either... # # Note that PEP 484-compliant typed variadic positional arguments (e.g., # "*args: str") are considerably more common than PEP 692-compliant typed # variadic keyword arguments (e.g., "**kwargs: SomeTypedDict", where # "SomeTypedDict" is a user-defined "typing.TypedDict" subclass). Ergo, we # intentionally detect typed variadic positional arguments *BEFORE* typed # variadic keyword arguments. if ( ( # That callable accepts a variadic positional argument *AND*... node_arg_nodes.vararg and # That parameter is typed... node_arg_nodes.vararg.annotation # *OR*... ) or ( # That callable accepts a variadic keyword argument *AND*... node_arg_nodes.kwarg and # That parameter is typed... node_arg_nodes.kwarg.annotation ) # That callable is typed. In this case, return true. ): return True # Else, that callable is still possibly untyped. # Fallback to deciding whether that callable accepts one or more typed # non-variadic parameters. Since doing is considerably more computationally # expensive, we do so *ONLY* as needed. # # Note that manual iteration is considerably more efficient than more # syntactically concise any() and all() generator expressions. # # Specifically, if that callable accepts non-variadic flexible parameters... if node_arg_nodes.args: # For each non-variadic flexible parameter... for node_arg_nonvar in node_arg_nodes.args: # If this parameter is typed, that callable is typed. In this case, # return true. if node_arg_nonvar.annotation: return True # Else, this parameter is untyped. Continue to the next. # Else, that callable is still possibly untyped. # If that callable accepts non-variadic keyword-only parameters... if node_arg_nodes.kwonlyargs: # For each non-variadic keyword-only parameter... for node_arg_nonvar in node_arg_nodes.kwonlyargs: # If this parameter is typed, that callable is typed. In this case, # return true. if node_arg_nonvar.annotation: return True # Else, this parameter is untyped. Continue to the next. # Else, that callable is still possibly untyped. # If that callable accepts non-variadic positional-only parameters... if node_arg_nodes.posonlyargs: # For each non-variadic positional-only parameter... for node_arg_nonvar in node_arg_nodes.posonlyargs: # If this parameter is typed, that callable is typed. In this case, # return true. if node_arg_nonvar.annotation: return True # Else, this parameter is untyped. Continue to the next. # Else, that callable is now known to be untyped. # Return false. return False beartype-0.18.5/beartype/_util/cache/000077500000000000000000000000001461113517100174125ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/cache/__init__.py000066400000000000000000000000001461113517100215110ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/cache/map/000077500000000000000000000000001461113517100201675ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/cache/map/__init__.py000066400000000000000000000000001461113517100222660ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/cache/map/utilmapbig.py000066400000000000000000000236061461113517100227050ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **unbounded cache** utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import ( Callable, Dict, Union, ) from beartype._util.utilobject import SENTINEL from collections.abc import Hashable from contextlib import AbstractContextManager from threading import Lock # ....................{ CLASSES }.................... #FIXME: Submit back to StackOverflow, preferably under this question: # https://stackoverflow.com/questions/1312331/using-a-global-dictionary-with-threads-in-python class CacheUnboundedStrong(object): ''' **Thread-safe strongly unbounded cache** (i.e., mapping of unlimited size from strongly referenced arbitrary keys onto strongly referenced arbitrary values, whose methods are guaranteed to behave thread-safely). Design ------ Cache implementations typically employ weak references for safety. Employing strong references invites memory leaks by preventing objects *only* referenced by the cache (cache-only objects) from being garbage-collected. Nonetheless, this cache intentionally employs strong references to persist these cache-only objects across calls to callables decorated with :func:`beartype.beartype`. In theory, caching an object under a weak reference would result in immediate garbage-collection; with *no* external strong referents, that object would be garbage-collected with all other short-lived objects in the first generation (i.e., generation 0). This cache intentionally does *not* adhere to standard mapping semantics by subclassing a standard mapping API (e.g., :class:`dict`, :class:`collections.abc.MutableMapping`). Standard mapping semantics are sufficiently low-level as to invite race conditions between competing threads concurrently contesting the same instance of this class. For example, consider the following standard non-atomic logic for caching a new key-value into this cache: .. code-block:: python if key not in cache: # <-- If a context switch happens immediately # <-- after entering this branch, bad stuff! cache[key] = value # <-- We may overwrite another thread's work. Attributes ---------- _key_to_value : dict[Hashable, object] Internal **backing store** (i.e., thread-unsafe dictionary of unlimited size mapping from strongly referenced arbitrary keys onto strongly referenced arbitrary values). _key_to_value_get : Callable The :meth:`self._key_to_value.get` method, classified for efficiency. _key_to_value_set : Callable The :meth:`self._key_to_value.__setitem__` dunder method, classified for efficiency. _lock : AbstractContextManager **Instance-specific thread lock** (i.e., low-level thread locking mechanism implemented as a highly efficient C extension, defined as an instance variable for non-reentrant reuse by the public API of this class). Although CPython, the canonical Python interpreter, *does* prohibit conventional multithreading via its Global Interpreter Lock (GIL), CPython still coercively preempts long-running threads at arbitrary execution points. Ergo, multithreading concerns are *not* safely ignorable -- even under CPython. ''' # ..................{ CLASS VARIABLES }.................. # Slot all instance variables defined on this object to minimize the time # complexity of both reading and writing variables across frequently called # @beartype decorations. Slotting has been shown to reduce read and write # costs by approximately ~10%, which is non-trivial. __slots__ = ( '_key_to_value', '_key_to_value_get', '_key_to_value_set', '_lock', ) # ..................{ INITIALIZER }.................. def __init__( self, # Optional parameters. lock_type: Union[type, Callable[[], object]] = Lock, ) -> None: ''' Initialize this cache to an empty cache. Parameters ---------- lock_type : Union[type, Callable[[], object]] Type of thread-safe lock to internally use. Defaults to :class:`Lock` (i.e., the type of the standard non-reentrant lock) for efficiency. ''' # Initialize all instance variables. self._key_to_value: Dict[Hashable, object] = {} self._key_to_value_get = self._key_to_value.get self._key_to_value_set = self._key_to_value.__setitem__ self._lock: AbstractContextManager = lock_type() # type: ignore[assignment] # ..................{ GETTERS }.................. def cache_or_get_cached_value( self, # Mandatory parameters. key: Hashable, value: object, # Hidden parameters, localized for negligible efficiency. _SENTINEL=SENTINEL, ) -> object: ''' **Statically** (i.e., non-dynamically, rather than "statically" in the different semantic sense of "static" methods) associate the passed key with the passed value if this cache has yet to cache this key (i.e., if this method has yet to be passed this key) and, in any case, return the value associated with this key. Parameters ---------- key : Hashable **Key** (i.e., arbitrary hashable object) to return the associated value of. value : object **Value** (i.e., arbitrary object) to associate with this key if this key has yet to be associated with any value. Returns ---------- object **Value** (i.e., arbitrary object) associated with this key. ''' # assert isinstance(key, Hashable), f'{repr(key)} unhashable.' # Thread-safely (but non-reentrantly)... with self._lock: # Value previously cached under this key if any *OR* the sentinel # placeholder otherwise. value_old = self._key_to_value_get(key, _SENTINEL) # If this key has already been cached, return this value as is. if value_old is not _SENTINEL: return value_old # Else, this key has yet to be cached. # Cache this key with this value. self._key_to_value_set(key, value) # Return this value. return value #FIXME: Unit test us up. #FIXME: Generalize to accept a new mandatory "arg: object" parameter and #then pass rather than forcefully passing the passed key. \o/ def cache_or_get_cached_func_return_passed_arg( self, # Mandatory parameters. key: Hashable, value_factory: Callable[[object], object], arg: object, # Hidden parameters, localized for negligible efficiency. _SENTINEL=SENTINEL, ) -> object: ''' Dynamically associate the passed key with the value returned by the passed **value factory** (i.e., caller-defined function accepting this key and returning the value to be associated with this key) if this cache has yet to cache this key (i.e., if this method has yet to be passed this key) and, in any case, return the value associated with this key. Caveats ---------- **This value factory must not recursively call this method.** For efficiency, this cache is internally locked through a non-reentrant rather than reentrant thread lock. If this value factory accidentally recursively calls this method, the active thread will be indefinitely locked. Welcome to the risky world of high-cost efficiency gains. Parameters ---------- key : Hashable **Key** (i.e., arbitrary hashable object) to return the associated value of. value_factory : Callable[[object], object] **Value factory** (i.e., caller-defined function accepting the passed ``arg`` object and dynamically returning the value to be associated with this key). arg : object Arbitrary object to be passed as is to this value factory. Returns ---------- object **Value** (i.e., arbitrary object) associated with this key. ''' # assert isinstance(key, Hashable), f'{repr(key)} unhashable.' # assert callable(value_factory), f'{repr(value_factory)} uncallable.' # Thread-safely (but non-reentrantly)... with self._lock: # Value previously cached under this key if any *OR* the sentinel # placeholder otherwise. value_old = self._key_to_value_get(key, _SENTINEL) # If this key has already been cached, return this value as is. if value_old is not _SENTINEL: return value_old # Else, this key has yet to be cached. # Value created by this factory function, localized for negligible # efficiency to avoid the unnecessary subsequent dictionary lookup. value = value_factory(arg) # Cache this key with this value. self._key_to_value_set(key, value) # Return this value. return value # ..................{ CLEARERS }.................. #FIXME: Unit test us up, please. def clear(self) -> None: ''' Clear (i.e., empty) this cache. ''' # Clear your head and be at peace, one-liner. self._key_to_value.clear() beartype-0.18.5/beartype/_util/cache/map/utilmaplru.py000066400000000000000000000214271461113517100227450ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **Least Recently Used (LRU) cache** utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... #FIXME: The current "CacheLruStrong" implementation is overly low-level and #thus fundamentally *THREAD-UNSAFE.* The core issue here is that the current #approach encourages callers to perform thread-unsafe logic resembling: # if key not in lru_dict: # <-- if a context switch happens here, bad stuff # lru_dict[key] = value # #For thread-safety, the entire "CacheLruStrong" class *MUST* be rethought along #the manner of the comparable "utilmapbig.CacheUnboundedStrong" class. Notably: #* "CacheLruStrong" class should *NOT* directly subclass "dict" but instead # simply contain a "_dict" instance. #* Thread-unsafe dunder methods (particularly the "__setitem__" method) should # probably *NOT* be defined at all. Yeah, we know. #* A new CacheLruStrong.cache_entry() method resembling the existing # CacheUnboundedStrong.cache_entry() method should be declared. #* Indeed, we should (arguably) declare a new "CacheStrongABC" base class to # provide a common API here -- trivializing switching between different # caching strategies implemented by concrete subclasses. # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilCacheLruException from beartype.typing import Hashable from threading import Lock # ....................{ CLASSES }.................... class CacheLruStrong(dict): ''' **Thread-safe strong Least Recently Used (LRU) cache** (i.e., mapping limited to some maximum capacity of strongly referenced arbitrary keys mapped onto strongly referenced arbitrary values, whose methods are guaranteed to behave thread-safely). Design ------ Cache implementations typically employ weak references for safety. Employing strong references invites memory leaks by preventing objects *only* referenced by the cache (cache-only objects) from being garbage-collected. Nonetheless, this cache intentionally employs strong references to persist these cache-only objects across calls to callables decorated with :func:`beartype.beartype`. In theory, caching an object under a weak reference would result in immediate garbage-collection as, with no external strong referents, the object would get collected with all other short-lived objects in the first generation (i.e., generation 0). Note that: * The equivalent LRU cache employing weak references to keys and/or values may be trivially implemented by swapping this classes inheritance from the builtin :class:`dict` to either of the builtin :class:`weakref.WeakKeyDictionary` or :class:`weakref.WeakValueDictionary`. * The standard example of a cache-only object is a container iterator (e.g., :meth:`dict.items`). Attributes ---------- _size : int **Cache capacity** (i.e., maximum number of key-value pairs persisted by this cache). _lock : Lock **Non-reentrant instance-specific thread lock** (i.e., low-level thread locking mechanism implemented as a highly efficient C extension, defined as an instance variable for non-reentrant reuse by the public API of this class). Although CPython, the canonical Python interpreter, *does* prohibit conventional multithreading via its Global Interpreter Lock (GIL), CPython still coercively preempts long-running threads at arbitrary execution points. Ergo, multithreading concerns are *not* safely ignorable -- even under CPython. ''' # ..................{ CLASS VARIABLES }.................. # Slot all instance variables defined on this object to minimize the time # complexity of both reading and writing variables across frequently called # cache dunder methods. Slotting has been shown to reduce read and write # costs by approximately ~10%, which is non-trivial. __slots__ = ( '_size', '_lock', ) # ..................{ DUNDERS }.................. def __init__(self, size: int) -> None: ''' Initialize this cache to an empty cache with a capacity of this size. Parameters ---------- size : int **Cache capacity** (i.e., maximum number of key-value pairs held in this cache). Raises ------ _BeartypeUtilCacheLruException: If the capacity is *not* an integer or its a **non-positive integer** (i.e. less than 1). ''' super().__init__() if not isinstance(size, int): raise _BeartypeUtilCacheLruException( f'LRU cache capacity {repr(size)} not integer.') elif size < 1: raise _BeartypeUtilCacheLruException( f'LRU cache capacity {size} not positive.') self._size = size self._lock = Lock() def __getitem__( self, key: Hashable, # Superclass methods efficiently localized as default parameters. __contains = dict.__contains__, # pyright: ignore __getitem = dict.__getitem__, # pyright: ignore __delitem = dict.__delitem__, # pyright: ignore __pushitem = dict.__setitem__, # pyright: ignore ) -> object: ''' Return an item previously cached under the passed key *or* raise an exception otherwise. This implementation is *practically* identical to :meth:`self.__contains__` except we return an arbitrary object rather than a boolean. Parameters ---------- key : Hashable Arbitrary hashable key to retrieve the cached value of. Returns ------- object Arbitrary value cached under this key. Raises ------ TypeError If this key is not hashable. KeyError If this key isn't cached. ''' with self._lock: # Reset this key if it exists. if __contains(self, key): val = __getitem(self, key) __delitem(self, key) __pushitem(self, key, val) return val raise KeyError(f'Key Error: {key}') def __setitem__( self, key: Hashable, value: object, # Superclass methods efficiently localized as default parameters. __contains = dict.__contains__, # pyright: ignore __delitem = dict.__delitem__, # pyright: ignore __pushitem = dict.__setitem__, # pyright: ignore __iter = dict.__iter__, # pyright: ignore __len = dict.__len__, # pyright: ignore ) -> None: ''' Cache this key-value pair while preserving size constraints. Parameters ---------- key : Hashable Arbitrary hashable key to cache this value to. value : object Arbitrary value to be cached under this key. Raises ------ TypeError If this key is not hashable. ''' with self._lock: if __contains(self, key): __delitem(self, key) __pushitem(self, key, value) # Prune this cache. if __len(self) > self._size: __delitem(self, next(__iter(self))) def __contains__( self, key: Hashable, # Superclass methods efficiently localized as default parameters. __contains = dict.__contains__, # pyright: ignore __getitem = dict.__getitem__, # pyright: ignore __delitem = dict.__delitem__, # pyright: ignore __pushitem = dict.__setitem__, # pyright: ignore ) -> bool: ''' Return a boolean indicating whether this key is cached. If this key is cached, this method implicitly refreshes this key by popping and pushing this key back onto the top of this cache. Parameters ---------- key : Hashable Arbitrary hashable key to detect the existence of. Returns ------- bool :data:`True` only if this key is cached. Raises ---------- TypeError If this key is unhashable. ''' with self._lock: if __contains(self, key): val = __getitem(self, key) __delitem(self, key) __pushitem(self, key, val) return True return False beartype-0.18.5/beartype/_util/cache/pool/000077500000000000000000000000001461113517100203635ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/cache/pool/__init__.py000066400000000000000000000000001461113517100224620ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/cache/pool/utilcachepool.py000066400000000000000000000311461461113517100235750ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **key pool type** (i.e., object caching class implemented as a dictionary of lists of arbitrary objects to be cached, where objects cached to the same list are typically of the same type). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... #FIXME: Conditionally pass "is_debug=True" to the KeyPool.{acquire,release}() #methods defined below when the "BeartypeConfig.is_debug" parameter is "True" #for the current call to the @beartype decorator, please. #FIXME: Optimize the KeyPool.{acquire,release}() methods defined below. Rather #than unconditionally wrapping the bodies of each in a thread-safe #"self._thread_lock" context manager, we might be able to leverage the GIL by #only doing so "if threading.active_count():". Profile us up, please. # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilCachedKeyPoolException from beartype.typing import ( Dict, Union, ) from collections import defaultdict from collections.abc import Callable, Hashable from threading import Lock # ....................{ CLASSES }.................... class KeyPool(object): ''' Thread-safe **key pool** (i.e., object cache implemented as a dictionary of lists of arbitrary objects to be cached, where objects cached to the same list are typically of the same type). Key pools are thread-safe by design and thus safely usable as module-scoped globals accessed from module-scoped callables. Attributes ---------- _key_to_pool : defaultdict Dictionary mapping from an **arbitrary key** (i.e., hashable object) to corresponding **pool** (i.e., list of zero or more arbitrary objects referred to as "pool items" cached under that key). For both efficiency and simplicity, this dictionary is defined as a :class:`defaultdict` implicitly initializing missing keys on initial access to the empty list. _pool_item_id_to_is_acquired : dict Dictionary mapping from the unique object identifier of a **pool item** (i.e., arbitrary object cached under a pool of the :attr:`_key_to_pool` ditionary) to a boolean that is either: * :data:`True` if that item is currently **acquired** (i.e., most recently returned by a call to the :meth:`acquire` method). * :data:`False` if that item is currently **released** (i.e., most recently passed to a call to the :meth:`release` method). _pool_item_maker : Callable Caller-defined factory callable internally called by the :meth:`acquire` method on attempting to acquire a non-existent object from an **empty pool. See :meth:`__init__` for further details. _thread_lock : Lock **Non-reentrant instance-specific thread lock** (i.e., low-level thread locking mechanism implemented as a highly efficient C extension, defined as an instance variable for non-reentrant reuse by the public API of this class). Although CPython, the canonical Python interpreter, *does* prohibit conventional multithreading via its Global Interpreter Lock (GIL), CPython still coercively preempts long-running threads at arbitrary execution points. Ergo, multithreading concerns are *not* safely ignorable -- even under CPython. ''' # ..................{ CLASS VARIABLES }.................. # Slot all instance variables defined on this object to minimize the time # complexity of both reading and writing variables across frequently called # @beartype decorations. Slotting has been shown to reduce read and write # costs by approximately ~10%, which is non-trivial. __slots__ = ( '_key_to_pool', '_pool_item_id_to_is_acquired', '_pool_item_maker', '_thread_lock', ) # ..................{ INITIALIZER }.................. def __init__( self, item_maker: Union[type, Callable], ) -> None: ''' Initialize this key pool with the passed factory callable. Parameters ---------- item_maker : Union[type, Callable[[Hashable,], Any]] Caller-defined factory callable internally called by the :meth:`acquire` method on attempting to acquire a non-existent object from an **empty pool** (i.e., either a missing key *or* an empty list of an existing key of the underlying :attr:`_key_to_pool` dictionary). That method initializes the empty pool in question by calling this factory with the key associated with that pool and appending the object created and returned by this factory to that pool. This factory is thus expected to have a signature resembling: .. code-block:: python from collections.abc import Hashable def item_maker(key: Hashable) -> object: ... ''' assert callable(item_maker), f'{repr(item_maker)} not callable.' # Classify these parameters as instance variables. self._pool_item_maker = item_maker # Initialize all remaining instance variables. # # Note that "defaultdict" instances *MUST* be initialized with # positional rather than keyword parameters. For unknown reasons, # initializing such an instance with a keyword parameter causes that # instance to silently behave like a standard dictionary instead: e.g., # # >>> dd = defaultdict(default_factory=list) # >>> dd['ee'] # KeyError: 'ee' self._key_to_pool: Dict[Hashable, list] = defaultdict(list) self._pool_item_id_to_is_acquired: Dict[int, bool] = {} self._thread_lock = Lock() # ..................{ METHODS }.................. def acquire( self, # Optional parameters. key: Hashable = None, is_debug: bool = False, ) -> object: ''' Acquire an arbitrary object associated with the passed **arbitrary key** (i.e., hashable object). Specifically, this method tests whether there exists a non-empty list previously associated with this key. If so, this method pops the last item from that list and returns that item; else (i.e., if there either exists no such list or such a list exists but is empty), this method effectively (in order): #. If no such list exists, create a new empty list associated with this key. #. Create a new object to be returned by calling the user-defined :meth:`_pool_item_maker` factory callable. #. Append this object to this list. #. Add/Update acquisition state of the object to True #. Returns this object. Parameters ---------- key : Optional[HashableType] Hashable object associated with the pool item to be acquired. Defaults to ``None``. is_debug : bool, optional ``True`` only if enabling inefficient debugging logic. Notably, enabling this option notes this item to have now been acquired. Defaults to ``False``. Returns ---------- object Pool item associated with this hashable object. Raises ---------- TypeError If this key is unhashable and thus *not* a key. ''' # In a thread-safe manner... with self._thread_lock: #FIXME: This logic can *PROBABLY* be optimized into: # if not is_debug: # try: # return self._key_to_pool[key].pop() # except IndexError: # return self._pool_item_maker(key) # else: # try: # pool_item = self._key_to_pool[key].pop() # except IndexError: # pool_item = self._pool_item_maker(key) # # # Record this item to have now been acquired. # self._pool_item_id_to_is_acquired[id(pool_item)] = True # # return pool_item # #That said, this introduces additional complexity that will require #unit testing. So, only do so if the above is actually profiled as #being faster. It almost certainly is, but let's be certain please. # List associated with this key. # # If this is the first access of this key, this "defaultdict" # implicitly creates a new list and associates this key with that # list; else, this is the list previously associated with this key. # # Note that this statement implicitly raises a "TypeError" # exception if this key is unhashable, which is certainly more # efficient than our explicitly validating this constraint. pool = self._key_to_pool[key] # Pool item associated with this key, defined as either... pool_item = ( # The last item popped (i.e., removed) from this list... pool.pop() # If the list associated with this key is non-empty (i.e., this # method has been called less frequently than the corresponding # release() method for this key); if pool else # Else, the list associated with this key is empty (i.e., this # method has been called more frequently than the release() # method for this key). In this case, an arbitrary object # associated with this key. self._pool_item_maker(key) ) # If debugging, record this item to have now been acquired. if is_debug: self._pool_item_id_to_is_acquired[id(pool_item)] = True # Return this item. return pool_item def release( self, # Mandatory parameters. item: object, # Optional parameters. key: Hashable = None, is_debug: bool = False, ) -> None: ''' Release the passed object acquired by a prior call to the :meth:`acquire` method passed the same passed **arbitrary key** (i.e., hashable object). Specifically, this method tests whether there exists a list previously associated with this key. If not, this method creates a new empty list associated with this key. In either case, this method then appends this object to this list. Parameters ---------- item : object Arbitrary object previously associated with this key. key : Optional[HashableType] Hashable object previously associated with this pool item. Defaults to ``None``. is_debug : bool, optional ``True`` only if enabling inefficient debugging logic. Notably, enabling this option raises an exception if this item was *not* previously acquired. Defaults to ``False``. Raises ---------- TypeError If this key is unhashable (i.e. *not* a key). _BeartypeUtilCachedKeyPoolException If debugging *and* this pool item was not acquired (i.e., returned by a prior call to the :meth:`acquire` method), in which case this item is ineligible for release. ''' # In a thread-safe manner... with self._thread_lock: # If debugging... if is_debug: # Integer uniquely identifying this previously acquired pool # item. item_id = id(item) # If this item was *NOT* previously acquired, raise an # exception. if not self._pool_item_id_to_is_acquired.get(item_id, False): raise _BeartypeUtilCachedKeyPoolException( f'Unacquired key pool item {repr(item)} ' f'not releasable.' ) # Record this item to have now been released. self._pool_item_id_to_is_acquired[item_id] = False # Append this item to the pool associated with this key. self._key_to_pool[key].append(item) beartype-0.18.5/beartype/_util/cache/pool/utilcachepoollistfixed.py000066400000000000000000000357611461113517100255200ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Fixed list pool** (i.e., submodule whose thread-safe API caches previously instantiated :class:`list` subclasses constrained to fixed lengths defined at instantiation time of various lengths for space- and time-efficient reuse by the :func:`beartype.beartype` decorator across decoration calls). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... #FIXME: Consider submitting the "FixedList" type as a relevant StackOverflow #answer here: # https://stackoverflow.com/questions/10617045/how-to-create-a-fix-size-list-in-python # https://stackoverflow.com/questions/51558015/implementing-efficient-fixed-size-fifo-in-python # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilCachedFixedListException from beartype.typing import NoReturn from beartype._util.cache.pool.utilcachepool import KeyPool from beartype._util.text.utiltextrepr import represent_object # ....................{ CONSTANTS }.................... FIXED_LIST_SIZE_MEDIUM = 256 ''' Reasonably large length to constrain acquired and released fixed lists to. This constant is intended to be passed to the :func:`.acquire_fixed_list` function, which then returns a fixed list of this length suitable for use in contexts requiring a "reasonably large" list -- where "reasonably" and "large" are both subjective but *should* cover 99.9999% of use cases in this codebase. ''' # ....................{ CLASSES }.................... class FixedList(list): ''' **Fixed list** (i.e., :class:`list` constrained to a fixed length defined at instantiation time).** A fixed list is effectively a mutable tuple. Whereas a tuple is immutable and thus prohibits changes to its contained items, a fixed list is mutable and thus *permits* changes to its contained items. Design ------ This list enforces this constraint by overriding *all* :class:`list` dunder and standard methods that would otherwise modify the length of this list (e.g., :meth:`list.__delitem__`, :meth:`list.append`) to instead unconditionally raise an :class:`._BeartypeUtilCachedFixedListException` exception. ''' # ..................{ CLASS VARIABLES }.................. # Slot all instance variables defined on this object to minimize the time # complexity of both reading and writing variables across frequently # called @beartype decorations. Slotting has been shown to reduce read and # write costs by approximately ~10%, which is non-trivial. __slots__ = () # ..................{ INITIALIZER }.................. def __init__( self, # Mandatory parameters. size: int, # Optional parameters. obj_init: object = None, ) -> None: ''' Initialize this fixed list to the passed length and all items of this fixed list to the passed initialization object. Parameters ---------- size : IntType Length to constrain this fixed list to. obj_init : object, optional **Initialization object** (i.e., object to be unilaterally assigned to *all* indices of this fixed list). Defaults to :data:`None`. Raises ------ _BeartypeUtilCachedFixedListException If this length is either not an integer *or* is but is **non-positive** (i.e., is less than or equal to 0). ''' # If this length is *NOT* an integer, raise an exception. if not isinstance(size, int): raise _BeartypeUtilCachedFixedListException( f'Fixed list length {repr(size)} not integer.') # Else, this length is an integer. # # If this length is non-positive, raise an exception. elif size <= 0: raise _BeartypeUtilCachedFixedListException( f'Fixed list length {size} <= 0.') # Else, this length is positive. # Make it so with the standard Python idiom for preallocating list # space -- which, conveniently, is also the optimally efficient means # of doing so. See also the timings in this StackOverflow answer: # https://stackoverflow.com/a/10617221/2809027 super().__init__([obj_init]*size) # ..................{ GOOD ~ non-dunders }.................. # Permit non-dunder methods preserving list length but otherwise requiring # overriding. def copy(self) -> 'FixedList': # Nullified fixed list of the same length as this fixed list. list_copy = FixedList(len(self)) # Slice over the nullified contents of this copy with those of this # fixed list. list_copy[:] = self # Return this copy. return list_copy # ..................{ BAD ~ dunders }.................. # Prohibit dunder methods modifying list length by overriding these methods # to raise exceptions. def __delitem__(self, index) -> NoReturn: raise _BeartypeUtilCachedFixedListException( f'{self._label} index {repr(index)} not deletable.') def __iadd__(self, value) -> NoReturn: # type: ignore[misc] raise _BeartypeUtilCachedFixedListException( f'{self._label} not addable by {represent_object(value)}.') def __imul__(self, value) -> NoReturn: # type: ignore[misc] raise _BeartypeUtilCachedFixedListException( f'{self._label} not multipliable by {represent_object(value)}.') # ..................{ BAD ~ dunders : setitem }.................. #FIXME: Great idea, if efficiency didn't particularly matter. Since #efficiency is the entire raison d'etre of this class, however, this method #has been temporarily and probably permanently disabled. Extensive #profiling has shown this single method to substantially cost us elsewhere. #Moreover, this method is only relevant in the context of preventing #external callers who are *NOT* us from violating class constraints. No #external callers exist, though! We are it. Since we know better, we won't #violate class constraints by changing fixed list length with slicing. #Moreover, it's unlikely we ever even assign list slices anywhere. *sigh* # def __setitem__(self, index, value): # # # If these parameters indicate an external attempt to change the length # # of this fixed length with slicing, raise an exception. # self._die_if_slice_len_ne_value_len(index, value) # # # If this index is a tuple of 0-based indices and slice objects... # if isinstance(index, Iterable): # # For each index or slice in this tuple... # for subindex in index: # # If these parameters indicate an external attempt to change # # the length of this fixed length with slicing, raise an # # exception. # self._die_if_slice_len_ne_value_len(subindex, value) # # # Else, this list is either not being sliced or is but is being set to # # an iterable of the same length as that slice. In either case, this # # operation preserves the length of this list and is thus acceptable. # return super().__setitem__(index, value) #FIXME: Disabled as currently only called by __setitem__(). *sigh* # def _die_if_slice_len_ne_value_len(self, index, value) -> None: # ''' # Raise an exception only if the passed parameters when passed to the # parent :meth:`__setitem__` dunder method signify an external attempt to # change the length of this fixed length with slicing. # # This function is intended to be called by the :meth:`__setitem__` # dunder method to validate the passed parameters. # # Parameters # ---------- # index # 0-based index, slice object, or tuple of 0-based indices and slice # objects to index this fixed list with. # value # Object to set this index(s) of this fixed list to. # # Raises # ---------- # _BeartypeUtilCachedFixedListException # If this index is a **slice object** (i.e., :class:`slice` instance # underlying slice syntax) and this value is either: # # * **Unsized** (i.e., unsupported by the :func:`len` builtin). # * Sized but has a length differing from that of this fixed list. # ''' # # # If this index is *NOT* a slice, silently reduce to a noop. # if not isinstance(index, slice): # return # # Else, this index is a slice. # # # # If this value is *NOT* a sized container, raise an exception. # elif not isinstance(value, Sized): # raise _BeartypeUtilCachedFixedListException( # f'{self._label} slice {repr(index)} not settable to unsized ' # f'{represent_object(value)}.' # ) # # Else, this value is a sized container. # # # 0-based first and one-past-the-last indices sliced by this slice. # start, stop_plus_one, _ = index.indices(len(self)) # # # Number of items of this fixed list sliced by this slice. By # # definition, this is guaranteed to be a non-negative integer. # slice_len = stop_plus_one - start # # # Number of items of this sized container to set this slice to. # value_len = len(value) # # # If these two lengths differ, raise an exception. # if slice_len != value_len: # raise _BeartypeUtilCachedFixedListException( # f'{self._label} slice {repr(index)} of length {slice_len} not ' # f'settable to {represent_object(value)} of differing ' # f'length {value_len}.' # ) # ..................{ BAD ~ non-dunders }.................. # Prohibit non-dunder methods modifying list length by overriding these # methods to raise exceptions. def append(self, obj) -> NoReturn: raise _BeartypeUtilCachedFixedListException( f'{self._label} not appendable by {represent_object(obj)}.') def clear(self) -> NoReturn: raise _BeartypeUtilCachedFixedListException( f'{self._label} not clearable.') def extend(self, obj) -> NoReturn: raise _BeartypeUtilCachedFixedListException( f'{self._label} not extendable by {represent_object(obj)}.') def pop(self, *args) -> NoReturn: raise _BeartypeUtilCachedFixedListException( f'{self._label} not poppable.') def remove(self, *args) -> NoReturn: raise _BeartypeUtilCachedFixedListException( f'{self._label} not removable.') # ..................{ PRIVATE ~ property }.................. # Read-only properties intentionally prohibiting mutation. @property def _label(self) -> str: ''' Human-readable representation of this fixed list trimmed to a reasonable length. This string property is intended to be interpolated into exception messages and should probably *not* be called in contexts where efficiency is a valid concern. ''' # One-liners for magnanimous pusillanimousness. return f'Fixed list {represent_object(self)}' # ....................{ PRIVATE ~ factories }.................... _fixed_list_pool = KeyPool(item_maker=FixedList) ''' Thread-safe **fixed list pool** (i.e., :class:`.KeyPool` singleton caching previously instantiated :class:`FixedList` instances of various lengths). Caveats ------- **Avoid accessing this private singleton externally.** Instead, call the public :func:`.acquire_fixed_list` and :func:`.release_fixed_list` functions, which efficiently validate both input *and* output to conform to sane expectations. ''' # ....................{ (ACQUIRERS|RELEASERS) }.................... def acquire_fixed_list(size: int) -> FixedList: ''' Acquire an arbitrary **fixed list** (i.e., :class:`list` constrained to a fixed length defined at instantiation time) with the passed length. Caveats ------- **The contents of this list are arbitrary.** Callers should make *no* assumptions as to this list's initial items, but should instead reinitialize this list immediately after acquiring this list with standard list slice syntax: e.g., .. code-block:: pycon >>> from beartype._util.cache.pool.utilcachepoollistfixed import ( ... acquire_fixed_list) >>> fixed_list = acquire_fixed_list(size=5) >>> fixed_list[:] = ('Dirty', 'Deads', 'Done', 'Dirt', 'Cheap',) Parameters ---------- size : int Length to constrain the fixed list to be acquired to. Returns ------- FixedList Arbitrary fixed list with this length. Raises ------ _BeartypeUtilCachedFixedListException If this length is either not an integer *or* is but is **non-positive** (i.e., is less than or equal to 0). ''' # Note that the FixedList.__init__() method already validates this "size" # parameter to be an integer. # Thread-safely acquire a fixed list of this length. fixed_list = _fixed_list_pool.acquire(size) assert isinstance(fixed_list, FixedList), ( f'{repr(fixed_list)} not fixed list.') # Return this list. return fixed_list def release_fixed_list(fixed_list: FixedList) -> None: ''' Release the passed fixed list acquired by a prior call to the :func:`acquire_fixed_list` function. Caveats ------- **This list is not safely accessible after calling this function.** Callers should make *no* attempts to read, write, or otherwise access this list, but should instead nullify *all* variables referring to this list immediately after releasing this list (e.g., by setting these variables to the :data:`None` singleton *or* by deleting these variables): e.g., .. code-block:: pycon >>> from beartype._util.cache.pool.utilcachepoollistfixed import ( ... acquire_fixed_list, release_fixed_list) >>> fixed_list = acquire_fixed_list(size=7) >>> fixed_list[:] = ('If', 'You', 'Want', 'Blood', "You've", 'Got', 'It',) >>> release_fixed_list(fixed_list) # Either do this... >>> fixed_list = None # Or do this. >>> del fixed_list Parameters ---------- fixed_list : FixedList Previously acquired fixed list to be released. ''' assert isinstance(fixed_list, FixedList), ( f'{repr(fixed_list)} not fixed list.') # Thread-safely release this fixed list. _fixed_list_pool.release(key=len(fixed_list), item=fixed_list) beartype-0.18.5/beartype/_util/cache/pool/utilcachepoolobjecttyped.py000066400000000000000000000101221461113517100260210ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Typed object pool** (i.e., submodule whose thread-safe API caches previously instantiated objects of arbitrary types for space- and time-efficient reuse by the :func:`beartype.beartype` decorator across decoration calls). This private submodule is *not* intended for importation by downstream callers. Caveats ---------- **This submodule only pools objects defining an** ``__init__()`` **method accepting no parameters.** Why? Because this submodule unconditionally pools all objects of the same types under those types. This submodule provides *no* mechanism for pooling objects of the same types under different parameters instantiated with those parameters and thus only implements a coarse- rather than fine-grained object cache. If insufficient, consider defining a new submodule implementing a fine-grained object cache unique to those objects. For example: * This submodule unconditionally pools all instances of the :class:`beartype._check.checkcall.BeartypeCall` class under that type. * The parallel :mod:`beartype._util.cache.pool.utilcachepoollistfixed` submodule conditionally pools every instance of the :class:`beartype._util.cache.pool.utilcachepoollistfixed.FixedList` class of the same length under that length. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilCachedObjectTypedException from beartype.typing import Any from beartype._util.cache.pool.utilcachepool import KeyPool # ....................{ SINGLETONS ~ private }.................... _object_typed_pool = KeyPool(item_maker=lambda cls: cls()) ''' Thread-safe **typed object pool** (i.e., :class:`KeyPool` singleton caching previously instantiated objects of the same types under those types). Caveats ---------- **Avoid accessing this private singleton externally.** Instead, call the public :func:`acquire_object_typed` and :func:`release_object_typed` functions, which efficiently validate both input *and* output to conform to sane expectations. ''' # ....................{ (ACQUIRERS|RELEASERS) }.................... def acquire_object_typed(cls: type) -> Any: ''' Acquire an arbitrary object of the passed type. Caveats ---------- **The contents of this object are arbitrary.** Callers should make *no* assumptions as to this object's state, but should instead reinitialize this object immediately after acquiring this object. Parameters ---------- cls : type Type of the object to be acquired. Returns ---------- object Arbitrary object of this type. Raises ---------- _BeartypeUtilCachedObjectTypedException If this type is *not* actually a type. ''' # If this type is *NOT* actually a type, raise an exception. if not isinstance(cls, type): raise _BeartypeUtilCachedObjectTypedException( '{!r} not a class.'.format(cls)) # Thread-safely acquire an object of this type. object_typed = _object_typed_pool.acquire(cls) assert isinstance(object_typed, cls), ( '{!r} not a {!r}.'.format(object_typed, cls)) # Return this object. return object_typed def release_object_typed(obj: Any) -> None: ''' Release the passed object acquired by a prior call to the :func:`acquire_object_typed` function. Caveats ---------- **This object is not safely accessible after calling this function.** Callers should make *no* attempts to read, write, or otherwise access this object, but should instead nullify *all* variables referring to this object immediately after releasing this object (e.g., by setting these variables to the ``None`` singleton *or* by deleting these variables). Parameters ---------- obj : object Previously acquired object to be released. ''' # Thread-safely release this object. _object_typed_pool.release(key=obj.__class__, item=obj) beartype-0.18.5/beartype/_util/cache/utilcachecall.py000066400000000000000000000750561461113517100225760ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **callable caching utilities** (i.e., low-level callables performing general-purpose memoization of function and method calls). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... #FIXME: Generalize @callable_cached to revert to the body of the #@beartype._util.type.decorator.decmemo.func_cached decorator when the passed #callable accepts *NO* parameters, which can be trivially decided by inspecting #the code object of this callable. Why do this? Because the @func_cached #decorator is *INSANELY* fast for this edge case -- substantially faster than #the current general-purpose @callable_cached approach. # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilCallableCachedException from beartype.typing import Dict from beartype._data.hint.datahinttyping import CallableT from beartype._util.func.arg.utilfuncargtest import ( die_unless_func_args_len_flexible_equal, is_func_arg_variadic, ) from beartype._util.text.utiltextlabel import label_callable from beartype._util.utilobject import SENTINEL from functools import wraps # ....................{ DECORATORS ~ callable }.................... def callable_cached(func: CallableT) -> CallableT: ''' **Memoize** (i.e., efficiently re-raise all exceptions previously raised by the decorated callable when passed the same parameters (i.e., parameters that evaluate as equals) as a prior call to that callable if any *or* return all values previously returned by that callable otherwise rather than inefficiently recalling that callable) the passed callable. Specifically, this decorator (in order): #. Creates: * A local dictionary mapping parameters passed to this callable with the values returned by this callable when passed those parameters. * A local dictionary mapping parameters passed to this callable with the exceptions raised by this callable when passed those parameters. #. Creates and returns a closure transparently wrapping this callable with memoization. Specifically, this wrapper (in order): #. Tests whether this callable has already been called at least once with the passed parameters by lookup of those parameters in these dictionaries. #. If this callable previously raised an exception when passed these parameters, this wrapper re-raises the same exception. #. Else if this callable returned a value when passed these parameters, this wrapper re-returns the same value. #. Else, this wrapper: #. Calls that callable with those parameters. #. If that call raised an exception: #. Caches that exception with those parameters in that dictionary. #. Raises that exception. #. Else: #. Caches the value returned by that call with those parameters in that dictionary. #. Returns that value. Caveats ------- **The decorated callable must accept no keyword parameters.** While this decorator previously memoized keyword parameters, doing so incurred significant performance penalties defeating the purpose of caching. This decorator now intentionally memoizes *only* positional parameters. **The decorated callable must accept no variadic positional parameters.** While memoizing variadic parameters would of course be feasible, this decorator has yet to implement support for doing so. **The decorated callable should not be a property method** (i.e., either a property getter, setter, or deleter subsequently decorated by the :class:`property` decorator). Technically, this decorator *can* be used to memoize property methods; pragmatically, doing so would be sufficiently inefficient as to defeat the intention of memoizing in the first place. Efficiency ---------- For efficiency, consider calling the decorated callable with only: * **Hashable** (i.e., immutable) arguments. While technically supported, every call to the decorated callable passed one or more unhashable arguments (e.g., mutable containers like lists and dictionaries) will silently *not* be memoized. Equivalently, only calls passed only hashable arguments will be memoized. This flexibility enables decorated callables to accept unhashable PEP-compliant type hints. Although *all* PEP-noncompliant and *most* PEP-compliant type hints are hashable, some sadly are not. These include: * :pep:`585`-compliant type hints subscripted by one or more unhashable objects (e.g., ``collections.abc.Callable[[], str]``, the `PEP 585`_-compliant type hint annotating piths accepting callables accepting no parameters and returning strings). * :pep:`586`-compliant type hints subscripted by an unhashable object (e.g., ``typing.Literal[[]]``, a literal empty list). * :pep:`593`-compliant type hints subscripted by one or more unhashable objects (e.g., ``typing.Annotated[typing.Any, []]``, the :attr:`typing.Any` singleton annotated by an empty list). **This decorator is intentionally not implemented in terms of the stdlib** :func:`functools.lru_cache` **decorator,** as that decorator is inefficient in the special case of unbounded caching with ``maxsize=None``. Why? Because that decorator insists on unconditionally recording irrelevant statistics like cache misses and hits. While bounding the number of cached values is advisable in the general case (e.g., to avoid exhausting memory merely for optional caching), parameters and returns cached by this package are sufficiently small in size to render such bounding irrelevant. Consider the :func:`beartype._util.hint.pep.utilpeptest.is_hint_pep_type_typing` function, for example. Each call to that function only accepts a single class and returns a boolean. Under conservative assumptions of 4 bytes of storage per class reference and 4 byte of storage per boolean reference, each call to that function requires caching at most 8 bytes of storage. Again, under conservative assumptions of at most 1024 unique type annotations for the average downstream consumer, memoizing that function in full requires at most 1024 * 8 == 8096 bytes or ~8Kb of storage. Clearly, 8Kb of overhead is sufficiently negligible to obviate any space concerns that would warrant an LRU cache in the first place. Parameters ---------- func : CallableT Callable to be memoized. Returns ------- CallableT Closure wrapping this callable with memoization. Raises ------ _BeartypeUtilCallableCachedException If this callable accepts a variadic positional parameter (e.g., ``*args``). ''' assert callable(func), f'{repr(func)} not callable.' #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Synchronize against the @method_cached_arg_by_id decorator # below. For speed, this decorator violates DRY by duplicating logic. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Dictionary mapping a tuple of all flattened parameters passed to each # prior call of the decorated callable with the value returned by that call # if any (i.e., if that call did *NOT* raise an exception). args_flat_to_return_value: Dict[tuple, object] = {} # get() method of this dictionary, localized for efficiency. args_flat_to_return_value_get = args_flat_to_return_value.get # Dictionary mapping a tuple of all flattened parameters passed to each # prior call of the decorated callable with the exception raised by that # call if any (i.e., if that call raised an exception). args_flat_to_exception: Dict[tuple, Exception] = {} # get() method of this dictionary, localized for efficiency. args_flat_to_exception_get = args_flat_to_exception.get @wraps(func) def _callable_cached(*args): f''' Memoized variant of the {func.__name__}() callable. See Also -------- :func:`callable_cached` Further details. ''' # Object representing all passed positional arguments to be used as the # key of various memoized dictionaries, defined as either... args_flat = ( # If passed only one positional argument, minimize space consumption # by flattening this tuple of only that argument into that argument. # Since tuple items are necessarily hashable, this argument is # necessarily hashable and thus permissible as a dictionary key; args[0] if len(args) == 1 else # Else, one or more positional arguments are passed. In this case, # reuse this tuple as is. args ) # Attempt to... try: # Exception raised by a prior call to the decorated callable when # passed these parameters *OR* the sentinel placeholder otherwise # (i.e., if this callable either has yet to be called with these # parameters *OR* has but failed to raise an exception). # # Note that: # * This statement raises a "TypeError" exception if any item of # this flattened tuple is unhashable. # * A sentinel placeholder (e.g., "SENTINEL") is *NOT* needed here. # The values of the "args_flat_to_exception" dictionary are # guaranteed to *ALL* be exceptions. Since "None" is *NOT* an # exception, disambiguation between "None" and valid dictionary # values is *NOT* needed here. Although a sentinel placeholder # could still be employed, doing so would slightly reduce # efficiency for *NO* real-world gain. exception = args_flat_to_exception_get(args_flat) # If this callable previously raised an exception when called with # these parameters, re-raise the same exception. if exception: raise exception # pyright: ignore[reportGeneralTypeIssues] # Else, this callable either has yet to be called with these # parameters *OR* has but failed to raise an exception. # Value returned by a prior call to the decorated callable when # passed these parameters *OR* a sentinel placeholder otherwise # (i.e., if this callable has yet to be passed these parameters). return_value = args_flat_to_return_value_get( args_flat, SENTINEL) # If this callable has already been called with these parameters, # return the value returned by that prior call. if return_value is not SENTINEL: return return_value # Else, this callable has yet to be called with these parameters. # Attempt to... try: # Call this parameter with these parameters and cache the value # returned by this call to these parameters. return_value = args_flat_to_return_value[args_flat] = func( *args) # If this call raised an exception... except Exception as exception: # Cache this exception to these parameters. args_flat_to_exception[args_flat] = exception # Re-raise this exception. raise exception # If one or more objects either passed to *OR* returned from this call # are unhashable, perform this call as is *WITHOUT* memoization. While # non-ideal, stability is better than raising a fatal exception. except TypeError: #FIXME: If testing, emit a non-fatal warning or possibly even raise #a fatal exception. In either case, we want our test suite to notify #us about this. return func(*args) # Return this value. return return_value # Return this wrapper. return _callable_cached # type: ignore[return-value] # ....................{ DECORATORS ~ method }.................... def method_cached_arg_by_id(func: CallableT) -> CallableT: ''' **Memoize** (i.e., efficiently re-raise all exceptions previously raised by the decorated method when passed the same *exact* parameters (i.e., parameters whose object IDs are equals) as a prior call to that method if any *or* return all values previously returned by that method otherwise rather than inefficiently recalling that method) the passed method. Caveats ------- **This decorator is only intended to decorate bound methods** (i.e., either class or instance methods bound to a class or instance). This decorator is *not* intended to decorate functions or static methods. **This decorator is only intended to decorate a method whose sole argument is guaranteed to be a memoized singleton** (e.g., :class:`beartype.door.TypeHint` singleton). In this case, the object identifier of that argument uniquely identifies that argument across *all* calls to that method -- enabling this decorator to memoize that method. Conversely, if that argument is *not* guaranteed to be a memoized singleton, this decorator will fail to memoize that method while wasting considerable space and time attempting to do so. In short, care is warranted. This decorator is a micro-optimized variant of the more general-purpose :func:`callable_cached` decorator, which should be preferred in most cases. This decorator mostly exists for one specific edge case that the :func:`callable_cached` decorator *cannot* by definition support: user-defined classes implementing the ``__eq__`` dunder method to internally call another method decorated by :func:`callable_cached` accepting an instance of the same class. This design pattern appears astonishingly frequently, including in our prominent :class:`beartype.door.TypeHint` class. This edge case provokes infinite recursion. Consider this minimal-length example (MLE) exhibiting the issue: .. code-block:: python from beartype._util.cache.utilcachecall import callable_cached class MuhClass(object): def __eq__(self, other: object) -> bool: return isinstance(other, MuhClass) and self._is_equal(other) @callable_cached def _is_equal(self, other: 'MuhClass') -> bool: return True :func:`callable_cached` internally caches the ``other`` argument passed to the ``_is_equal()`` method as keys of various internal dictionaries. When passed the same ``other`` argument, subsequent calls to that method lookup that ``other`` argument in those dictionaries. Since dictionary lookups implicitly call the ``other.__eq__()`` method to resolve key collisions *and* since the ``__eq__()`` method has been overridden in terms of the ``_is_equal()`` method, infinite recursion results. This decorator circumvents this issue by internally looking up the object identifier of the passed argument rather than that argument itself, which then avoids implicitly calling the ``__eq__()`` method of that argument. Parameters ---------- func : CallableT Callable to be memoized. Returns ------- CallableT Closure wrapping this callable with memoization. Raises ------ _BeartypeUtilCallableCachedException If this callable accepts either: * *No* parameters. * Two or more parameters. * A variadic positional parameter (e.g., ``*args``). See Also -------- :func:`callable_cached` Further details. ''' assert callable(func), f'{repr(func)} not callable.' # Avoid circular import dependencies. from beartype._util.func.utilfuncwrap import unwrap_func_all # Lowest-level wrappee callable wrapped by this wrapper callable. func_wrappee = unwrap_func_all(func) # If this wrappee accepts either zero, one, *OR* three or more flexible # parameters (i.e., parameters passable as either positional or keyword # arguments), raise an exception. die_unless_func_args_len_flexible_equal( func=func_wrappee, func_args_len_flexible=2, exception_cls=_BeartypeUtilCallableCachedException, # Avoid unnecessary callable unwrapping as a negligible optimization. is_unwrap=False, ) # Else, this wrappee accepts exactly one flexible parameter. # If this wrappee accepts variadic arguments, raise an exception. if is_func_arg_variadic(func_wrappee): raise _BeartypeUtilCallableCachedException( f'@method_cached_arg_by_id {label_callable(func)} ' f'variadic arguments uncacheable.' ) # Else, this wrappee accepts *NO* variadic arguments. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Synchronize against the @callable_cached decorator above. For # speed, this decorator violates DRY by duplicating logic. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Dictionary mapping a tuple of all flattened parameters passed to each # prior call of the decorated callable with the value returned by that call # if any (i.e., if that call did *NOT* raise an exception). args_flat_to_return_value: Dict[tuple, object] = {} # get() method of this dictionary, localized for efficiency. args_flat_to_return_value_get = args_flat_to_return_value.get # Dictionary mapping a tuple of all flattened parameters passed to each # prior call of the decorated callable with the exception raised by that # call if any (i.e., if that call raised an exception). args_flat_to_exception: Dict[tuple, Exception] = {} # get() method of this dictionary, localized for efficiency. args_flat_to_exception_get = args_flat_to_exception.get @wraps(func) def _method_cached(self_or_cls, arg): f''' Memoized variant of the {func.__name__}() callable. See Also -------- :func:`callable_cached` Further details. ''' # Object identifiers of the sole positional parameters passed to the # decorated method. args_flat = (id(self_or_cls), id(arg)) # Attempt to... try: # Exception raised by a prior call to the decorated callable when # passed these parameters *OR* the sentinel placeholder otherwise # (i.e., if this callable either has yet to be called with these # parameters *OR* has but failed to raise an exception). # # Note that: # * This statement raises a "TypeError" exception if any item of # this flattened tuple is unhashable. # * A sentinel placeholder (e.g., "SENTINEL") is *NOT* needed here. # The values of the "args_flat_to_exception" dictionary are # guaranteed to *ALL* be exceptions. Since "None" is *NOT* an # exception, disambiguation between "None" and valid dictionary # values is *NOT* needed here. Although a sentinel placeholder # could still be employed, doing so would slightly reduce # efficiency for *NO* real-world gain. exception = args_flat_to_exception_get(args_flat) # If this callable previously raised an exception when called with # these parameters, re-raise the same exception. if exception: raise exception # pyright: ignore[reportGeneralTypeIssues] # Else, this callable either has yet to be called with these # parameters *OR* has but failed to raise an exception. # Value returned by a prior call to the decorated callable when # passed these parameters *OR* a sentinel placeholder otherwise # (i.e., if this callable has yet to be passed these parameters). return_value = args_flat_to_return_value_get( args_flat, SENTINEL) # If this callable has already been called with these parameters, # return the value returned by that prior call. if return_value is not SENTINEL: return return_value # Else, this callable has yet to be called with these parameters. # Attempt to... try: # Call this parameter with these parameters and cache the value # returned by this call to these parameters. return_value = args_flat_to_return_value[args_flat] = func( self_or_cls, arg) # If this call raised an exception... except Exception as exception: # Cache this exception to these parameters. args_flat_to_exception[args_flat] = exception # Re-raise this exception. raise exception # If one or more objects either passed to *OR* returned from this call # are unhashable, perform this call as is *WITHOUT* memoization. While # non-ideal, stability is better than raising a fatal exception. except TypeError: #FIXME: If testing, emit a non-fatal warning or possibly even raise #a fatal exception. In either case, we want our test suite to notify #us about this. return func(self_or_cls, arg) # Return this value. return return_value # Return this wrapper. return _method_cached # type: ignore[return-value] # ....................{ DECORATORS ~ property }.................... def property_cached(func: CallableT) -> CallableT: ''' **Memoize** (i.e., efficiently cache and return all previously returned values of the passed property method as well as all previously raised exceptions of that method previously rather than inefficiently recalling that method) the passed **property method method** (i.e., either a property getter, setter, or deleter subsequently decorated by the :class:`property` decorator). On the first access of a property decorated with this decorator (in order): #. The passed method implementing this property is called. #. The value returned by this property is internally cached into a private attribute of the object to which this method is bound. #. This value is returned. On each subsequent access of this property, this cached value is returned as is *without* calling the decorated method. Hence, the decorated method is called at most once for each object exposing this property. Caveats ------- **This decorator must be preceded by an explicit usage of the standard** :class:`property` **decorator.** Although this decorator could be trivially refactored to automatically decorate the returned property method by the :class:`property` decorator, doing so would violate static type-checking expectations -- introducing far more issues than it would solve. **This decorator should always be preferred over the standard** :func:`functools.cached_property` **decorator available under Python >= 3.8.** This decorator is substantially more efficient in both space and time than that decorator -- which is, of course, the entire point of caching. **This decorator does not destroy bound property methods.** Technically, the most efficient means of caching a property value into an instance is to replace the property method currently bound to that instance with an instance variable initialized to that value (e.g., as documented by this `StackOverflow answer`_). Since a property should only ever be treated as an instance variable, there superficially exists little harm in dynamically changing the type of the former to the latter. Sadly, doing so introduces numerous subtle issues with *no* plausible workaround. Notably, replacing property methods by instance variables: * Permits callers to erroneously set **read-only properties** (i.e., properties lacking setter methods), a profound violation of one of the principle use cases for properties. * Prevents pickling logic elsewhere from automatically excluding cached property values, forcing these values to *always* be pickled to disk. This is bad. Cached property values are *always* safely recreatable in memory (and hence need *not* be pickled) and typically space-consumptive in memory (and hence best *not* pickled). The slight efficiency gain from replacing property methods by instance variables is hardly worth the significant space loss from pickling these variables. .. _StackOverflow answer: https://stackoverflow.com/a/36684652/2809027 Parameters ---------- func : CallableT Property method to be memoized. Returns ------- CallableT Dynamically generated function wrapping this property with memoization. ''' assert callable(func), f'{repr(func)} not callable.' # Name of the private instance variable to which this decorator caches the # value returned by the decorated property method. property_var_name = ( _PROPERTY_CACHED_VAR_NAME_PREFIX + func.__name__) # Raw string of Python statements comprising the body of this wrapper. # # Note that this implementation intentionally avoids calling our # higher-level beartype._util.func.utilfuncmake.make_func() factory function # for dynamically generating functions. Although this implementation could # certainly be refactored in terms of that factory, doing so would # needlessly reduce debuggability and portability for *NO* tangible gain. func_body = _PROPERTY_CACHED_CODE.format( property_var_name=property_var_name) # Dictionary mapping from local attribute names to values. For efficiency, # only attributes required by the body of this wrapper are copied from the # current namespace. (See below.) local_attrs = {'__property_method': func} # Dynamically define this wrapper as a closure of this decorator. For # obscure and presumably uninteresting reasons, Python fails to locally # declare this closure when the locals() dictionary is passed; to capture # this closure, a local dictionary must be passed instead. exec(func_body, globals(), local_attrs) # Return this wrapper method. return local_attrs['property_method_cached'] # ....................{ PRIVATE ~ constants : var }.................... _CALLABLE_CACHED_VAR_NAME_PREFIX = '__beartype_cached__' ''' Substring prefixing the names of all private instance variables to which all caching decorators (e.g., :func:`property_cached`) cache values returned by decorated callables. This prefix: * Guarantees uniqueness across *all* instances -- including those instantiated from official Python and unofficial third-party classes and those internally defined by this application. Doing so permits logic elsewhere (e.g., pickling filtering) to uniquely match and act upon these variables. * Is intentionally prefixed by double rather than single underscores (i.e., ``"__"`` rather than ``"_"``), ensuring that our :meth:`beartype._check.forward.reference.fwdrefmeta.BeartypeForwardRefMeta.__getattr__` dunder method ignores the private instance variables cached by our cached :meth:`beartype._check.forward.reference.fwdrefmeta.BeartypeForwardRefMeta.__type_beartype__` property. ''' _FUNCTION_CACHED_VAR_NAME = ( f'{_CALLABLE_CACHED_VAR_NAME_PREFIX}function_value') ''' Name of the private instance variable to which the :func:`func_cached` decorator statically caches the value returned by the decorated function. ''' _PROPERTY_CACHED_VAR_NAME_PREFIX = ( f'{_CALLABLE_CACHED_VAR_NAME_PREFIX}property_') ''' Substring prefixing the names of all private instance variables to which the :func:`property_cached` decorator dynamically caches the value returned by the decorated property method. ''' # ....................{ PRIVATE ~ constants : code }.................... _PROPERTY_CACHED_CODE = ''' @wraps(__property_method) def property_method_cached(self, __property_method=__property_method): try: return self.{property_var_name} except AttributeError: self.{property_var_name} = __property_method(self) return self.{property_var_name} ''' ''' Raw string of Python statements comprising the body of the wrapper function dynamically generated by the :func:`property_cached` decorator. These statements include (in order): * A :mod:`functools.wraps` decoration propagating the name, docstring, and other identifying metadata of the original function to this wrapper. * A private ``__property_method`` parameter set to the underlying property getter method. In theory, the ``func`` parameter passed to the :func:`property_cached` decorator should be accessible as a closure-style local in this code. For unknown reasons (presumably, a subtle bug in the :func:`exec` builtin), this is not the case. Instead, a closure-style local must be simulated by passing the ``func`` parameter to this function at function definition time as the default value of an arbitrary parameter. Design ------ While there exist numerous alternative implementations for caching properties, the approach implemented below has been profiled to be the most efficient. Alternatives include (in order of decreasing efficiency): * Dynamically getting and setting a property-specific key-value pair of the internal dictionary for the current object, timed to be approximately 1.5 times as slow as exception handling: e.g., .. code-block:: python if not {property_name!r} in self.__dict__: self.__dict__[{property_name!r}] = __property_method(self) return self.__dict__[{property_name!r}] * Dynamically getting and setting a property-specific attribute of the current object (e.g., the internal dictionary for the current object), timed to be approximately 1.5 times as slow as exception handling: e.g., .. code-block:: python if not hasattr(self, {property_name!r}): setattr(self, {property_name!r}, __property_method(self)) return getattr(self, {property_name!r}) ''' #FIXME: Uncomment to debug memoization-specific issues. *sigh* # def callable_cached(func: _CallableT) -> _CallableT: return func # def property_cached(func: _CallableT) -> _CallableT: return func beartype-0.18.5/beartype/_util/cache/utilcachemeta.py000066400000000000000000000066651461113517100226110ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **caching metaclasses** (i.e., classes performing general-purpose memoization of classes that declare the former to be their metaclasses). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import Type from beartype._data.hint.datahinttyping import T from beartype._util.cache.utilcachecall import callable_cached # ....................{ METACLASSES }.................... class BeartypeCachingMeta(type): ''' **Caching metaclass** (i.e., metaclass caching immutable instances of classes whose metaclasses are this metaclass, cached via the positional arguments instantiating those classes). This metaclass is superior to the usual approach of caching immutable objects: overriding the ``__new__`` method to conditionally create a new instance of that class only if an instance has *not* already been created with the passed positional arguments. Why? Because that approach unavoidably re-calls the ``__init__`` method of a previously initialized instance on each instantiation of that class -- which is clearly harmful, especially where immutability is concerned. This metaclass instead guarantees that the ``__init__`` method of an instance is only called once on the first instantiation of that instance. Caveats ---------- **This metaclass assumes immutability.** Ideally, instances of classes whose metaclasses are this metaclass should be **immutable** (i.e., frozen). Where this is *not* the case, the behaviour of this metaclass is undefined. **This metaclass prohibits keyword arguments.** ``__init__`` methods of classes whose metaclass is this metaclass must accept *only* positional arguments. Why? Efficiency, the entire point of caching. While feasible, permitting ``__init__`` methods to also accept keyword arguments would be sufficiently slow as to entirely defeat the point of caching. That's bad. See Also ---------- https://stackoverflow.com/a/8665179/2809027 StackOverflow answers strongly inspiring this implementation. ''' # ..................{ INITIALIZERS }.................. @callable_cached def __call__(cls: Type[T], *args) -> T: # type: ignore[reportIncompatibleMethodOverride] ''' Instantiate the passed class with the passed positional arguments if this is the first instantiation of this class passed these arguments *or* simply return the previously instantiated instance of this class otherwise (i.e., if this is a subsequent instantiation of this class re-passed these same arguments). Caveats ---------- This method intentionally accepts *only* positional arguments. See the metaclass docstring for further details. Parameters ---------- cls : type Class whose class is this metaclass. All remaining parameters are passed as is to the superclass :meth:`type.__call__` method. ''' # Bear witness to the terrifying power of @callable_cached. return super().__call__(*args) # type: ignore[misc] beartype-0.18.5/beartype/_util/cls/000077500000000000000000000000001461113517100171305ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/cls/__init__.py000066400000000000000000000000001461113517100212270ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/cls/pep/000077500000000000000000000000001461113517100177145ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/cls/pep/__init__.py000066400000000000000000000000001461113517100220130ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/cls/pep/utilpep3119.py000066400000000000000000000760701461113517100223000ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`3119`-compliant **class detectors** (i.e., callables validating and testing various properties of arbitrary classes standardized by :pep:`3119`). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeDecorHintPep3119Exception from beartype.typing import Callable from beartype._data.hint.datahinttyping import ( TypeException, TypeOrTupleTypes, ) # ....................{ RAISERS ~ instance }.................... def die_unless_object_isinstanceable( # Mandatory parameters. obj: TypeOrTupleTypes, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintPep3119Exception, exception_prefix: str = '', ) -> None: ''' Raise an exception of the passed type unless the passed object is **isinstanceable** (i.e., valid as the second parameter to the :func:`isinstance` builtin). Specifically, this function raises an exception unless this object is either: * An **isinstanceable class** (i.e., class whose metaclass does *not* define an ``__instancecheck__()`` dunder method that raises a :exc:`TypeError` exception). * Tuple of one or more isinstanceable classes. * A :pep:`604`-compliant **new union** (i.e., objects created by expressions of the form ``{type1} | {type2} | ... | {typeN}``) under Python >= 3.10. By definition, *all* new unions are isinstanceable. Parameters ---------- obj : object Object to be validated. exception_cls : TypeException, optional Type of exception to be raised. Defaults to :exc:`.BeartypeDecorHintPep3119Exception`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Raises ------ BeartypeDecorHintPep3119Exception If this object is neither: * An isinstanceable class. * A tuple containing only isinstanceable classes. * A :pep:`604`-compliant new union. ''' # Defer to this lower-level general-purpose raiser. _die_if_object_uncheckable( obj=obj, obj_pith=None, obj_raiser=die_unless_type_isinstanceable, obj_tester=isinstance, # type: ignore[arg-type] exception_cls=exception_cls, exception_prefix=exception_prefix, ) def die_unless_type_isinstanceable( # Mandatory parameters. cls: type, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintPep3119Exception, exception_prefix: str = '', ) -> None: ''' Raise an exception of the passed type unless the passed object is an **isinstanceable class** (i.e., class whose metaclass does *not* define an ``__instancecheck__()`` dunder method that raises a :exc:`TypeError` exception). Classes that are *not* isinstanceable include most PEP-compliant type hints, notably: * **Generic aliases** (i.e., subscriptable classes overriding the ``__class_getitem__()`` class dunder method standardized by :pep:`560` subscripted by an arbitrary object) under Python >= 3.9, whose metaclasses define an ``__instancecheck__()`` dunder method to unconditionally raise an exception. Generic aliases include: * :pep:`484`-compliant **subscripted generics.** * :pep:`585`-compliant type hints. * User-defined classes whose metaclasses define an ``__instancecheck__()`` dunder method to unconditionally raise an exception, including: * :pep:`544`-compliant protocols *not* decorated by the :func:`typing.runtime_checkable` decorator. Motivation ---------- When a class whose metaclass defines an ``__instancecheck__()`` dunder method is passed as the second parameter to the :func:`isinstance` builtin, that builtin defers to that method rather than testing whether the first parameter passed to that builtin is an instance of that class. If that method raises an exception, that builtin raises the same exception, preventing callers from deciding whether arbitrary objects are instances of that class. For brevity, we refer to that class as "non-isinstanceable." Most classes are isinstanceable, because deciding whether arbitrary objects are instances of those classes is a core prerequisite for object-oriented programming. Most classes that are also PEP-compliant type hints, however, are *not* isinstanceable, because they're *never* intended to be instantiated into objects (and typically prohibit instantiation in various ways); they're only intended to be referenced as type hints annotating callables, an arguably crude form of callable markup. :mod:`beartype`-decorated callables typically check the types of arbitrary objects at runtime by passing those objects and types as the first and second parameters to the :func:`isinstance` builtin. If those types are non-isinstanceable, those type-checks will typically raise non-human-readable exceptions (e.g., ``"TypeError: isinstance() argument 2 cannot be a parameterized generic"`` for :pep:`585`-compliant type hints). This is non-ideal both because those exceptions are non-human-readable *and* because those exceptions are raised at call rather than decoration time, where users expect the :func:`beartype.beartype` decorator to raise exceptions for erroneous type hints. Thus the existence of this function, which the :func:`beartype.beartype` decorator calls to validate the usability of type hints that are classes *before* checking objects against those classes at call time. Caveats ------- **This function considers all classes whose metaclasses define ``__instancecheck__()`` dunder methods that raise exceptions other than** :exc:`TypeError` **to be isinstanceable.** This function *only* considers classes whose metaclasses define ``__instancecheck__()`` dunder methods that raise :exc:`TypeError` exceptions to be non-isinstanceable; all other classes are isinstanceable. Ideally, this function would consider any class whose metaclass defines an ``__instancecheck__()`` dunder method that raises any exception (rather than merely a :exc:`TypeError` exception) to be non-isinstanceable. Pragmatically, doing so would raise false positives in common edge cases -- and previously did so, in fact, which is why we no longer do so. In particular, the metaclass of the passed class may *not* necessarily be fully initialized at the early time that this function is called (typically, at :func:`beartype.beartype` decoration time). If this is the case, then eagerly passing that class to :func:`isinstance` is likely to raise an exception. For example, doing so raises an :exc:`AttributeError` when the ``__instancecheck__()`` dunder method defined by that metaclass references an external attribute that has yet to be defined: .. code-block:: python from beartype import beartype class MetaFoo(type): def __instancecheck__(cls, other): return g() class Foo(metaclass=MetaFoo): pass # @beartype transitively calls this function to validate that "Foo" is # isinstanceable. However, since g() has yet to be defined at this time, # doing so raises an "AttributeError" exception despite this logic # otherwise being sound. @beartype def f(x: Foo): pass def g(): return True This function thus constrains itself to merely the :exc:`TypeError` exception, which all non-isinstanceable classes defined by the standard :mod:`typing` module unconditionally raise. This suggests that there is currently an unambiguous one-to-one mapping between non-isinstanceable classes and classes whose metaclass ``__instancecheck__()`` dunder methods raise :exc:`TypeError` exceptions. May this mapping hold true forever! Parameters ---------- cls : object Object to be validated. exception_cls : TypeException, optional Type of exception to be raised. Defaults to :exc:`.BeartypeDecorHintPep3119Exception`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Raises ------ BeartypeDecorHintPep3119Exception If this object is *not* an isinstanceable class. See Also -------- :func:`.die_unless_type_isinstanceable` Further details. ''' # Avoid circular import dependencies. from beartype._util.cls.utilclstest import die_unless_type # If this object is *NOT* a class, raise an exception. die_unless_type( cls=cls, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # Else, this object is a class. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Synchronize with the is_type_isinstanceable() tester. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Attempt to pass this class as the second parameter to isinstance(). try: isinstance(None, cls) # type: ignore[arg-type] # If doing so raised a "TypeError" exception, this class is *NOT* # isinstanceable. In this case, raise a human-readable exception. # # See the docstring for further discussion. except TypeError as exception: assert isinstance(exception_cls, type), ( f'{repr(exception_cls)} not exception class.') assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') #FIXME: Uncomment after we uncover why doing so triggers an #infinite circular exception chain when "hint" is a "GenericAlias". #It's clearly the is_hint_pep544_protocol() call, but why? In any #case, the simplest workaround would just be to inline the logic of #is_hint_pep544_protocol() here directly. Yes, we know. *shrug* # # Human-readable exception message to be raised as either... # exception_message = ( # # If this class is a PEP 544-compliant protocol, a message # # documenting this exact issue and how to resolve it; # ( # f'{exception_prefix}PEP 544 protocol {hint} ' # f'uncheckable at runtime (i.e., ' # f'not decorated by @typing.runtime_checkable).' # ) # if is_hint_pep544_protocol(hint) else # # Else, a fallback message documenting this general issue. # ( # f'{exception_prefix}type {hint} uncheckable at runtime (i.e., ' # f'not passable as second parameter to isinstance() ' # f'due to raising "{exception}" from metaclass ' # f'__instancecheck__() method).' # ) # ) # Exception message to be raised. exception_message = ( f'{exception_prefix}{repr(cls)} uncheckable at runtime ' f'(i.e., not passable as second parameter to isinstance(), ' f'due to raising "{exception.__class__.__name__}: {exception}" ' f'from metaclass __instancecheck__() method).' ) # Raise this exception chained onto this lower-level exception. raise exception_cls(exception_message) from exception # If doing so raised any exception *OTHER* than a "TypeError" exception, # this class may or may not be isinstanceable. Since we have no means of # differentiating the two, we err on the side of caution. Avoid returning a # false negative by quietly ignoring this exception. except Exception: pass # ....................{ RAISERS ~ subclass }.................... def die_unless_object_issubclassable( # Mandatory parameters. obj: TypeOrTupleTypes, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintPep3119Exception, exception_prefix: str = '', ) -> None: ''' Raise an exception of the passed type unless the passed object is **issubclassable** (i.e., valid as the second parameter to the :func:`issubclass` builtin). Specifically, this function raises an exception unless this object is either: * An **issubclassable class** (i.e., class whose metaclass does *not* define an ``__subclasscheck__()`` dunder method that raises a :exc:`TypeError` exception). * Tuple of one or more issubclassable classes. * A :pep:`604`-compliant **new union** (i.e., objects created by expressions of the form ``{type1} | {type2} | ... | {typeN}``) under Python >= 3.10. By definition, *all* new unions are issubclassable. Parameters ---------- obj : object Object to be validated. exception_cls : TypeException, optional Type of exception to be raised. Defaults to :exc:`.BeartypeDecorHintPep3119Exception`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Raises ------ BeartypeDecorHintPep3119Exception If this object is neither: * An issubclassable class. * A tuple containing only issubclassable classes. * A :pep:`604`-compliant new union. ''' # Defer to this lower-level general-purpose raiser. _die_if_object_uncheckable( obj=obj, obj_pith=type, obj_raiser=die_unless_type_issubclassable, obj_tester=issubclass, # type: ignore[arg-type] exception_cls=exception_cls, exception_prefix=exception_prefix, ) def die_unless_type_issubclassable( # Mandatory parameters. cls: type, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintPep3119Exception, exception_prefix: str = '', ) -> None: ''' Raise an exception of the passed type unless the passed object is an **issubclassable class** (i.e., class whose metaclass does *not* define a ``__subclasscheck__()`` dunder method that raise a :exc:`TypeError` exception). Classes that are *not* issubclassable include most PEP-compliant type hints, notably: * **Generic aliases** (i.e., subscriptable classes overriding the ``__class_getitem__()`` class dunder method standardized by :pep:`560` subscripted by an arbitrary object) under Python >= 3.9, whose metaclasses define an ``__subclasscheck__()`` dunder method to unconditionally raise an exception. Generic aliases include: * :pep:`484`-compliant **subscripted generics.** * :pep:`585`-compliant type hints. * User-defined classes whose metaclasses define a ``__subclasscheck__()`` dunder method to unconditionally raise an exception, including: * :pep:`544`-compliant protocols *not* decorated by the :func:`typing.runtime_checkable` decorator. Motivation ---------- See also the "Motivation" and "Caveats" sections of the :func:`die_unless_type_isinstanceable` docstring for further discussion, substituting: * ``__instancecheck__()`` for ``__subclasscheck__()``. * :func:`isinstance` for :func:`issubclass`. Parameters ---------- cls : object Object to be validated. exception_cls : TypeException, optional Type of exception to be raised. Defaults to :exc:`BeartypeDecorHintPep3119Exception`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Raises ------ BeartypeDecorHintPep3119Exception If this object is *not* an issubclassable class. ''' # Avoid circular import dependencies. from beartype._util.cls.utilclstest import die_unless_type # If this hint is *NOT* a class, raise an exception. die_unless_type( cls=cls, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # Else, this hint is a class. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Synchronize with the is_type_issubclassable() tester. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Attempt to pass this class as the second parameter to issubclass(). try: issubclass(type, cls) # type: ignore[arg-type] # If doing so raised a "TypeError" exception, this class is *NOT* # issubclassable. In this case, raise a human-readable exception. # # See the die_unless_type_isinstanceable() docstring for details. except TypeError as exception: assert isinstance(exception_cls, type), ( f'{repr(exception_cls)} not exception class.') assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') # Exception message to be raised. exception_message = ( f'{exception_prefix}{repr(cls)} uncheckable at runtime ' f'(i.e., not passable as second parameter to issubclass(), ' f'due to raising "{exception.__class__.__name__}: {exception}" ' f'from metaclass __subclasscheck__() method).' ) # Raise this exception chained onto this lower-level exception. raise exception_cls(exception_message) from exception # If doing so raised any exception *OTHER* than a "TypeError" exception, # this class may or may not be issubclassable. Since we have no means of # differentiating the two, we err on the side of caution. Avoid returning a # false negative by quietly ignoring this exception. except Exception: pass # ....................{ TESTERS }.................... def is_type_isinstanceable(cls: object) -> bool: ''' :data:`True` only if the passed object is an **isinstanceable class** (i.e., class whose metaclass does *not* define an ``__instancecheck__()`` dunder method that raises a :exc:`TypeError` exception). This tester is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator). Although the implementation does *not* trivially reduce to an efficient one-liner, the inefficient branch of this implementation *only* applies to erroneous edge cases resulting in raised exceptions and is thus largely ignorable. Caveats ------- **This tester may return false positives in unlikely edge cases.** Internally, this tester tests whether this class is isinstanceable by detecting whether passing the :data:`None` singleton and this class to the :func:`isinstance` builtin raises a :exc:`TypeError` exception. If that call raises *no* exception, this class is probably but *not* necessarily isinstanceable. Since the metaclass of this class could define an ``__instancecheck__()`` dunder method to conditionally raise exceptions *except* when passed the :data:`None` singleton, there exists *no* perfect means of deciding whether an arbitrary class is fully isinstanceable in the general sense. Since most classes that are *not* isinstanceable are unconditionally isinstanceable (i.e., the metaclasses of those classes define an ``__instancecheck__()`` dunder method to unconditionally raise exceptions), this distinction is generally meaningless in the real world. This test thus generally suffices. Parameters ---------- cls : object Object to be tested. Returns ------- bool :data:`True` only if this object is an isinstanceable class. See Also -------- :func:`.die_unless_type_isinstanceable` Further details. ''' # If this object is *NOT* a class, immediately return false. if not isinstance(cls, type): return False # Else, this object is a class. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Synchronize with die_unless_type_isinstanceable(). #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Attempt to pass this class as the second parameter to the isinstance() # builtin to decide whether or not this class is safely usable as a # standard class or not. # # Note that this leverages an EAFP (i.e., "It is easier to ask forgiveness # than permission") approach and thus imposes a minor performance penalty, # but that there exists *NO* faster alternative applicable to arbitrary # user-defined classes, whose metaclasses may define an __instancecheck__() # dunder method to raise exceptions and thus prohibit being passed as the # second parameter to the isinstance() builtin, the primary means employed # by @beartype wrapper functions to check arbitrary types. try: isinstance(None, cls) # type: ignore[arg-type] # If the prior function call raised *NO* exception, this class is # probably but *NOT* necessarily isinstanceable. Return true. # If the prior function call raised a "TypeError" exception, this class is # *NOT* isinstanceable. In this case, return false. except TypeError: return False # If the prior function call raised any exception *OTHER* than a "TypeError" # exception, this class may or may not be isinstanceable. Since we have no # means of differentiating the two, we err on the side of caution. Avoid # returning a false negative by safely returning true. except Exception: return True # Look. Just do it. *sigh* return True def is_type_issubclassable(cls: object) -> bool: ''' :data:`True` only if the passed object is either an **issubclassable class** (i.e., class whose metaclass does *not* define a ``__subclasscheck__()`` dunder method that raises a :exc:`TypeError` exception) *or* tuple containing only issubclassable classes. This tester is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator). Although the implementation does *not* trivially reduce to an efficient one-liner, the inefficient branch of this implementation *only* applies to erroneous edge cases resulting in raised exceptions and is thus largely ignorable. Caveats ------- See also the "Caveats" sections of the :func:`.is_type_isinstanceable` docstring for further discussion, substituting: * ``__instancecheck__()`` for ``__subclasscheck__()``. * :func:`isinstance` for :func:`issubclass`. Parameters ---------- cls : object Object to be tested. Returns ------- bool :data:`True` only if this object is either: * An issubclassable class. * A tuple containing only issubclassable classes. See Also -------- :func:`.die_unless_type_issubclassable` Further details. ''' # If this object is *NOT* a class, immediately return false. if not isinstance(cls, type): return False # Else, this object is a class. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Synchronize with die_unless_type_issubclassable(). #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Attempt to pass this class as the second parameter to the issubclass() # builtin to decide whether or not this class is safely usable as a # standard class or not. # # Note that this leverages an EAFP (i.e., "It is easier to ask forgiveness # than permission") approach and thus imposes a minor performance penalty, # but that there exists *NO* faster alternative applicable to arbitrary # user-defined classes, whose metaclasses may define a __subclasscheck__() # dunder method to raise exceptions and thus prohibit being passed as the # second parameter to the issubclass() builtin, the primary means employed # by @beartype wrapper functions to check arbitrary types. try: issubclass(type, cls) # type: ignore[arg-type] # If the prior function call raised *NO* exception, this class is # probably but *NOT* necessarily issubclassable. Return true. # If the prior function call raised a "TypeError" exception, this class is # *NOT* issubclassable. In this case, return false. except TypeError: return False # If the prior function call raised any exception *OTHER* than a "TypeError" # exception, this class may or may not be issubclassable. Since we have no # means of differentiating the two, we err on the side of caution. Avoid # returning a false negative by safely returning true. except Exception: pass # Look. Just do it. *sigh* return True # ....................{ PRIVATE ~ raisers }.................... def _die_if_object_uncheckable( obj: TypeOrTupleTypes, obj_pith: object, obj_raiser: Callable, obj_tester: Callable[[object, TypeOrTupleTypes], bool], exception_cls: TypeException, exception_prefix: str, ) -> None: ''' Raise an exception of the passed type unless the passed object is **runtime-checkable** (i.e., valid as the second parameter to either the :func:`isinstance` or :func:`issubclass` builtins) according to the passed object tester and raiser. Parameters ---------- obj : object Object to be validated. obj_pith : object Object guaranteed to satisfy the ``obj_tester`` callable when ``obj`` is runtime-typecheckable (i.e., when ``obj_tester`` is called as ``obj_tester(obj_pith, obj)``). obj_raiser : Callable Callable raising an exception unless this object is runtime-checkable according to this predicate, which should be either: * :func:`.die_unless_type_isinstanceable`. * :func:`.die_unless_type_issubclassable`. obj_tester : Callable[[object, TypeOrTupleTypes], bool] Callable returning :data:`True` only if this object is runtime-checkable according to this predicate, which should be either: * :func:`isinstance`. * :func:`issubclass`. exception_cls : TypeException Type of exception to be raised. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Raises ------ BeartypeDecorHintPep3119Exception If this object is *not* runtime-checkable according to the passed object tester and raiser. ''' assert callable(obj_raiser), f'{repr(obj_raiser)} uncallable.' assert callable(obj_tester), f'{repr(obj_tester)} uncallable.' # Avoid circular import dependencies. from beartype._util.cls.utilclstest import die_unless_type_or_types from beartype._util.hint.pep.proposal.utilpep604 import is_hint_pep604 from beartype._util.hint.pep.utilpepget import get_hint_pep_args # If this object is *NOT* a PEP 604-compliant new union... if not is_hint_pep604(obj): # If this object is neither a class nor tuple of classes, raise an # exception. die_unless_type_or_types( type_or_types=obj, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # Else, this object is either a class or tuple of classes. # Else, this object is a PEP 604-compliant new union. In either case, this # object now *COULD* be runtime-checkable. To decide whether this object is # actually instanceable, further introspection is needed. # If this object is a class... if isinstance(obj, type): # If this class is *NOT* runtime-checkable, raise an exception. obj_raiser( cls=obj, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # Else, this class is runtime-checkable. # Else, this object *MUST* (by process of elimination and validation # above) be either a tuple of classes *OR* new union. In either case... else: # Attempt to pass this object as the second parameter to isinstance(). try: obj_tester(obj_pith, obj) # type: ignore[arg-type] # If doing so raises a "TypeError" exception, this object is *NOT* # runtime-checkable. In this case, raise a human-readable exception. # # See the die_unless_type_runtime-checkable() docstring for details. except TypeError as exception: assert isinstance(exception_cls, type), ( f'{repr(exception_cls)} not exception class.') assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') # Tuple of all items of this iterable object. obj_items: tuple = None # type: ignore[assignment] # Human-readable label describing this object in this exception # message. obj_label: str = None # type: ignore[assignment] # Human-readable label describing the first non-runtime-checkable # item of this object in this exception message. obj_item_label: str = None # type: ignore[assignment] # If this object is a tuple, define these locals accordingly. if isinstance(obj, tuple): obj_items = obj obj_label = 'tuple union' obj_item_label = 'tuple union item' # Else, this object is a new union. Define these locals accordingly. else: obj_items = get_hint_pep_args(obj) obj_label = 'PEP 604 new union' obj_item_label = 'new union child type' # Exception message to be raised. exception_message = ( f'{exception_prefix} {obj_label} {repr(obj)} ' f'uncheckable at runtime' ) # For the 0-based index of each tuple class and that class... for cls_index, cls in enumerate(obj_items): # If this class is *NOT* runtime-checkable, raise an exception. obj_raiser( cls=cls, exception_cls=exception_cls, exception_prefix=( f'{exception_message}, as ' f'{obj_item_label} {cls_index} ' ), ) # Else, this class is runtime-checkable. Continue to the next. # Raise this exception chained onto this lower-level exception. # Although this should *NEVER* happen (as we should have already # raised an exception above), we nonetheless do so for safety. raise exception_cls(f'{exception_message}.') from exception # If doing so raised any exception *OTHER* than a "TypeError" exception, # this class may or may not be runtime-checkable. Since we have no means # of differentiating the two, we err on the side of caution. Avoid # returning a false negative by quietly ignoring this exception. except Exception: pass beartype-0.18.5/beartype/_util/cls/pep/utilpep557.py000066400000000000000000000036411461113517100222150ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`557`-compliant **testers** (i.e., low-level callables testing various properties of dataclasses standardized by :pep:`557`). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from dataclasses import is_dataclass # ....................{ TESTERS }.................... def is_type_pep557(cls: type) -> bool: ''' :data:`True` only if the passed class is a **dataclass** (i.e., :pep:`557`-compliant class decorated by the standard :func:`dataclasses.dataclass` decorator). This tester is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- cls : type Class to be inspected. Returns ---------- bool :data:`True` only if this class is a dataclass. Raises ---------- _BeartypeUtilTypeException If this object is *not* a class. ''' # Avoid circular import dependencies. from beartype._util.cls.utilclstest import die_unless_type # If this object is *NOT* a type, raise an exception. die_unless_type(cls) # Else, this object is a type. # Return true only if this type is a dataclass. # # Note that the is_dataclass() tester was intentionally implemented # ambiguously to return true for both actual dataclasses *AND* # instances of dataclasses. Since the prior validation omits the # latter, this call unambiguously returns true *ONLY* if this object is # an actual dataclass. (Dodged a misfired bullet there, folks.) return is_dataclass(cls) beartype-0.18.5/beartype/_util/cls/utilclsget.py000066400000000000000000000143731461113517100216710ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **class getters** (i.e., low-level callables obtaining various properties of arbitrary classes). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilTypeException from beartype.typing import ( Optional, ) from beartype._data.hint.datahinttyping import ( LexicalScope, TypeException, ) from beartype._util.cache.utilcachecall import callable_cached # ....................{ GETTERS }.................... #FIXME: Unit test us up. def get_type_filename_or_none(cls: type) -> Optional[str]: ''' Absolute filename of the file on the local filesystem containing the pure-Python source code for the script or module defining the passed class if that class is defined on-disk *or* :data:`None` otherwise (i.e., if that class is dynamically defined in-memory by a prior call to the :func:`exec` or :func:`eval` builtins). Parameters ---------- cls : type Class to be inspected. Returns ------- Optional[str] Either: * If this class was physically declared by a file, the absolute filename of that file. * If this class was dynamically declared in-memory, :data:`None`. ''' # Avoid circular import dependencies. from beartype._util.module.utilmodget import ( get_module_filename_or_none, get_object_module_name_or_none, ) from beartype._util.module.utilmodget import get_module_imported_or_none # Fully-qualified name of the module declaring this type if any *OR* "None". # # Note that *ALL* types should be declared by *SOME* modules. Nonetheless, # this is Python. It's best to assume the worst. type_module_name = get_object_module_name_or_none(cls) # If a module declares this type... if type_module_name: # This module if previously imported *OR* "None". # # Note that this module *SHOULD* necessarily already have been imported, # as this type obviously exists. Nonetheless, this module will be # unimportable for types dynamically declared in-memory rather than # on-disk, in which case the name of this module will have been a lie. type_module = get_module_imported_or_none(type_module_name) # If this module was previously imported... if type_module: # Return the filename defining this module if any *OR* "None". return get_module_filename_or_none(type_module) # Else, *NO* modules defines this type. # If all else fails, this type was probably declared in-memory rather than # on-disk. In this case, fallback to merely returning "None". return None #FIXME: Unit test us up, please. def get_type_locals( # Mandatory parameters. cls: type, # Optional parameters. exception_cls: TypeException = _BeartypeUtilTypeException, ) -> LexicalScope: ''' **Local scope** (i.e., dictionary mapping from the name to value of each attribute directly declared by that class) for the passed class. Caveats ------- **This getter returns an immutable rather than mutable mapping.** Callers requiring the latter are encouraged to manually coerce the immutable mapping returned by this getter into a mutable mapping (e.g., by passing the former to the :class:`dict` constructor as is). Design ------ This getter currently reduces to a trivial one-liner returning ``cls.__dict__`` and has thus been defined mostly just for orthogonality with the comparable :func:`beartype._util.func.utilfuncscope.get_func_locals` getter. That said, :pep:`563` suggests this non-trivial heuristic for computing the local scope of a given class: For classes, localns can be composed by chaining vars of the given class and its base classes (in the method resolution order). Since slots can only be filled after the class was defined, we don’t need to consult them for this purpose. We fail to grok that suggestion, because we lack a galactic brain. A minimal-length example (MLE) refutes all of the above by demonstrating that superclass attributes are *not* local to subclasses: .. code-block:: python >>> class Superclass(object): ... my_int = int >>> class Subclass(Superclass): ... def get_str(self) -> my_int: ... return 'Oh, Gods.' NameError: name 'my_int' is not defined We are almost certainly confused about what :pep:`563` is talking about, but we are almost certain that :pep:`536` is also confused about what :pep:`563` is talking about. That said, the standard :func:`typing.get_type_hints` getter implements that suggestion with iteration over the method-resolution order (MRO) of the passed class resembling: .. code-block:: python for base in reversed(obj.__mro__): ... base_locals = dict(vars(base)) if localns is None else localns The standard :func:`typing.get_type_hints` getter appears to recursively retrieve all type hints annotating both the passed class and all superclasses of that class. Why? We have no idea, frankly. We're unconvinced that is useful in practice. We prefer a trivial one-liner, which behaves exactly as advertised and efficiently at decoration-time. Parameters ---------- cls : type Class to be inspected. exception_cls : Type[Exception] Type of exception to be raised. Defaults to :exc:`_BeartypeUtilTypeException`. Returns ------- LexicalScope Local scope for this class. Raises ------ exception_cls If the next non-ignored frame following the last ignored frame is *not* the parent callable or module directly declaring the passed callable. ''' assert isinstance(cls, type), f'{repr(cls)} not type.' # Return the dictionary of class attributes bundled with this class. return cls.__dict__ # type: ignore[return-value] beartype-0.18.5/beartype/_util/cls/utilclsmake.py000066400000000000000000000124171461113517100220240ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **callable factories** (i.e., low-level functions dynamically creating and returning new in-memory callables). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilTypeException from beartype.typing import Optional from beartype._cave._cavemap import NoneTypeOr from beartype._data.hint.datahinttyping import ( LexicalScope, TupleTypes, TypeException, ) from beartype._data.kind.datakinddict import DICT_EMPTY from beartype._util.text.utiltextidentifier import die_unless_identifier # ....................{ MAKERS }.................... def make_type( # Mandatory arguments. type_name: str, # Optional arguments. type_module_name: Optional[str] = None, type_bases: Optional[TupleTypes] = None, type_scope: Optional[LexicalScope] = None, type_doc: Optional[str] = None, exception_cls: TypeException = _BeartypeUtilTypeException, exception_prefix: str = '', ) -> type: ''' Dynamically create and return a new class with the passed name subclassing all passed base classes and defined by the passed class scope. Parameters ---------- type_name : str Name of the class to be created. type_module_name : Optional[str] Fully-qualified name of the module declaring this class. Defaults to :data:`None`, in which case this class remains undeclared by any module. type_bases : Optional[Tuple[type, ...]] Tuple of all base classes to be inherited by this class. Defaults to the empty tuple, equivalent to the 1-tuple ``(object,)`` inheriting this class from only the root base class :class:`object` of all classes. type_scope : Optional[Dict[str, Any]] Dictionary mapping from the name to value of each **class-scoped attribute** (i.e., method, variable) to be defined by this class. Defaults to the empty dictionary, equivalent to declaring a class with the trivial body ``pass``. type_doc : Optional[str] Human-readable docstring documenting this class. Defaults to :data:`None`, in which case this class remains undocumented. exception_cls : Type[Exception], optional Type of exception to raise in the event of a fatal error. Defaults to :exc:`._BeartypeUtilTypeException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- type Class with this name subclassing these base classes and defined by this class scope. Raises ------ exception_cls If either: * The passed classname is empty. * The passed classname is non-empty but *not* a valid unqualified Python identifier. ''' assert isinstance(type_name, str), f'{repr(type_name)} not string.' assert isinstance(type_module_name, NoneTypeOr[str]), ( f'{repr(type_module_name)} neither string nor "None".') assert isinstance(type_doc, NoneTypeOr[str]), ( f'{repr(type_doc)} neither string nor "None".') # If this classname is *NOT* a valid unqualified Python identifier, raise an # exception. Insanely, the builtin type.__init__() constructor silently # allows this classname to be invalid -- despite the resulting class # violating sanity and normative standards. Note that invalid names include: # * The empty string. # * An invalid Python identifier. # * A valid fully-qualified Python identifier. if not type_name.isidentifier(): raise exception_cls( f'{exception_prefix}class name {repr(type_name)} invalid.') # Else, this classname is a valid unqualified Python identifier. # Default all unpassed parameters. if type_bases is None: type_bases = () # type: ignore[assignment] if type_scope is None: type_scope = DICT_EMPTY # type: ignore[assignment] assert isinstance(type_bases, tuple), ( f'{repr(type_bases)} neither tuple nor "None".') assert isinstance(type_scope, dict), ( f'{repr(type_scope)} neither dictionary nor "None".') # Thank you, bizarre 3-parameter variant of the type.__init__() constructor. cls = type(type_name, type_bases, type_scope) # If this class has a module name... if type_module_name is not None: # If this module name is *NOT* a valid Python identifier, raise an # exception. die_unless_identifier( text=type_module_name, exception_cls=exception_cls, exception_prefix='Class module name ', ) # Else, this module name is a valid Python identifier. # Set the module name of this class. cls.__module__ = type_module_name # Else, this class has *NO* module name. # If documenting this class, do so. if type_doc is not None: cls.__doc__ = type_doc # Else, this class is undocumented. # Return this class. return cls beartype-0.18.5/beartype/_util/cls/utilclsset.py000066400000000000000000000071741461113517100217060ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **class setters** (i.e., low-level callables modifying various properties of arbitrary classes). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_10 # ....................{ SETTERS }.................... #FIXME: Unit test us up. def set_type_attr(cls: type, attr_name: str, attr_value: object) -> None: ''' Dynamically set the **class variable** (i.e., attribute of the passed class) with the passed name to the passed value. Parameters ---------- cls : type Class to set this attribute on. attr_name : str Name of the class attribute to be set. attr_value : object Value to set this class attribute to. Caveats ------- **This function is unavoidably slow.** Class attributes are *only* settable by calling the tragically slow :func:`setattr` builtin. Attempting to directly set an attribute on the class dictionary raises an exception. Why? Because class dictionaries are actually low-level :class:`mappingproxy` objects that intentionally override the ``__setattr__()`` dunder method to unconditionally raise an exception. Why? Because that constraint enables the :meth:`type.__setattr__` dunder method to enforce critical efficiency constraints on class attributes -- including that class attribute keys are *not* only strings but also valid Python identifiers: .. code-block:: pycon >>> class OhGodHelpUs(object): pass >>> OhGodHelpUs.__dict__['even_god_cannot_help'] = 2 TypeError: 'mappingproxy' object does not support item assignment See also this `relevant StackOverflow answer by Python luminary Raymond Hettinger `__. .. _answer: https://stackoverflow.com/a/32720603/2809027 ''' # Attempt to set the class attribute with this name to this value. try: setattr(cls, attr_name, attr_value) # If doing so raises a builtin "TypeError"... except TypeError as exception: # Message raised with this "TypeError". exception_message = str(exception) # If this message satisfies a well-known pattern unique to the current # Python version, then this exception signifies this attribute to be # inherited from an immutable builtin type (e.g., "str") subclassed by # this user-defined subclass. In this case, silently skip past this # uncheckable attribute to the next. if ( # The active Python interpreter targets Python >= 3.10, match a # message of the form "cannot set '{attr_name}' attribute of # immutable type '{cls_name}'". IS_PYTHON_AT_LEAST_3_10 and ( exception_message.startswith("cannot set '") and "' attribute of immutable type " in exception_message # Else, the active Python interpreter targets Python <= 3.9. In this # case, match a message of the form "can't set attributes of # built-in/extension type '{cls_name}'". ) or exception_message.startswith( "can't set attributes of built-in/extension type '") ): return # Else, this message does *NOT* satisfy that pattern. # Preserve this exception by re-raising this exception. raise beartype-0.18.5/beartype/_util/cls/utilclstest.py000066400000000000000000000317141461113517100220670ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **class testers** (i.e., low-level callables testing and validating various properties of arbitrary classes). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilTypeException from beartype._cave._cavefast import TestableTypes as TestableTypesTuple from beartype._data.cls.datacls import TYPES_BUILTIN from beartype._data.hint.datahinttyping import ( TypeException, TypeOrTupleTypes, ) from beartype._data.module.datamodpy import BUILTINS_MODULE_NAME # ....................{ RAISERS }.................... def die_unless_type( # Mandatory parameters. cls: object, # Optional parameters. exception_cls: TypeException = _BeartypeUtilTypeException, exception_prefix: str = '', ) -> None: ''' Raise an exception of the passed type unless the passed object is a class. Parameters ---------- cls : object Object to be validated. exception_cls : Type[Exception] Type of exception to be raised in the event of a fatal error. Defaults to :exc:`._BeartypeUtilTypeException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Raises ------ exception_cls If this object is *not* a class. ''' # If this object is *NOT* a class, raise an exception. if not isinstance(cls, type): assert isinstance(exception_cls, type), ( 'f{repr(exception_cls)} not exception class.') assert isinstance(exception_prefix, str), ( 'f{repr(exception_prefix)} not string.') raise exception_cls(f'{exception_prefix}{repr(cls)} not class.') # Else, this object is a class. #FIXME: Unit test us up. def die_unless_type_or_types( # Mandatory parameters. type_or_types: object, # Optional parameters. exception_cls: TypeException = _BeartypeUtilTypeException, exception_prefix: str = '', ) -> None: ''' Raise an exception of the passed type unless the passed object is either a class *or* tuple of one or more classes. Parameters ---------- type_or_types : object Object to be validated. exception_cls : Type[Exception] Type of exception to be raised in the event of a fatal error. Defaults to :exc:`._BeartypeUtilTypeException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Raises ------ exception_cls If this object is neither a class *nor* tuple of one or more classes. ''' # If this object is neither a class *NOR* tuple of one or more classes, # raise an exception. if not is_type_or_types(type_or_types): assert isinstance(exception_cls, type), ( 'f{repr(exception_cls)} not exception class.') assert issubclass(exception_cls, Exception), ( f'{repr(exception_cls)} not exception subclass.') assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') # Exception message to be raised below. exception_message = ( f'{exception_prefix}{repr(type_or_types)} neither ' f'class nor tuple of one or more classes' ) # If this object is a tuple... if isinstance(type_or_types, tuple): # If this tuple is empty, note that. if not type_or_types: exception_message += ' (i.e., is empty tuple)' # Else, this tuple is non-empty. In this case... else: # For the 0-based index of each tuple item and that item... for cls_index, cls in enumerate(type_or_types): # If this object is *NOT* a class... if not isinstance(cls, type): # Note this. exception_message += ( f' (i.e., tuple item {cls_index} ' f'{repr(cls)} not class)' ) # Halt iteration. break # Else, this object is a class. Continue to the next item. # Else, this object is a non-tuple. In this case, the general-purpose # exception message suffices. # Raise this exception. raise exception_cls(f'{exception_message}.') # Else, this object is either a class *OR* tuple of one or more classes. # ....................{ TESTERS }.................... def is_type_or_types(type_or_types: object) -> bool: ''' :data:`True` only if the passed object is either a class *or* tuple of one or more classes. Parameters ---------- type_or_types : object Object to be inspected. Returns ------- bool :data:`True` only if this object is either a class *or* tuple of one or more classes. ''' # Return true only if either... return ( # This object is a class *OR*... isinstance(type_or_types, type) or ( # This object is a tuple *AND*... isinstance(type_or_types, tuple) and # This tuple is non-empty *AND*... bool(type_or_types) and # This tuple contains only classes. all(isinstance(cls, type) for cls in type_or_types) ) ) # ....................{ TESTERS ~ builtin }.................... def is_type_builtin(cls: type) -> bool: ''' :data:`True` only if the passed object is a **builtin type** (i.e., globally accessible C-based type implicitly accessible from all scopes and thus requiring *no* explicit importation). Caveats ------- This tester intentionally ignores **fake builtin types** (i.e., types that are *not* builtin but nonetheless erroneously masquerade as being builtin, including the type of the :data:`None` singleton) by returning :data:`False` when passed a fake builtin type. If this is undesirable, consider calling the lower-level :func:`.is_type_builtin_or_fake` tester. Parameters ---------- cls : type Class to be inspected. Returns ------- bool :data:`True` only if this class is builtin. ''' # Return true only if this is a builtin type. return cls in TYPES_BUILTIN def is_type_builtin_or_fake(cls: type) -> bool: ''' :data:`True` only if the passed object is a **possibly fake builtin type** (i.e., type declared by the standard :mod:`builtins` module). This tester is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Caveats ------- This tester intentionally accepts **fake builtin types** (i.e., types that are *not* builtin but nonetheless erroneously masquerade as being builtin, including the type of the :data:`None` singleton) by returning :data:`True` when passed a fake builtin type. If this is undesirable, consider calling the higher-level :func:`.is_type_builtin` tester. Like all non-builtin types, fake builtin types are globally inaccessible until explicitly imported into the current lexical variable scope. Unlike all non-builtin types, however, fake builtin types declare themselves to be builtin. The standard example is the type of the :data:`None` singleton: .. code-block:: python >>> f'{type(None).__module__}.{type(None).__name__}' 'builtins.NoneType' >>> NoneType NameError: name 'NoneType' is not defined # <---- this is balls These inconsistencies almost certainly constitute bugs in the CPython interpreter itself, but it seems doubtful CPython developers would see it that way and almost certain everyone else would defend these edge cases. We're *not* dying on that lonely hill. We obey the Iron Law of Guido. Parameters ---------- cls : type Class to be inspected. Returns ------- bool :data:`True` only if this object is a possibly fake builtin type. ''' # Avoid circular import dependencies. from beartype._util.module.utilmodget import ( get_object_type_module_name_or_none) # If this object is *NOT* a type, return false. if not isinstance(cls, type): return False # Else, this object is a type. # Fully-qualified name of the module defining this type if this type is # defined by a module *OR* "None" otherwise (i.e., if this type is # dynamically defined in-memory). cls_module_name = get_object_type_module_name_or_none(cls) # This return true only if this name is that of the "builtins" module # declaring all builtin types. return cls_module_name == BUILTINS_MODULE_NAME # ....................{ TESTERS ~ subclass }.................... def is_type_subclass( cls: object, base_classes: TypeOrTupleTypes) -> bool: ''' :data:`True` only if the passed object is an inclusive subclass of the passed superclass(es). Specifically, this tester returns :data:`True` only if either: * If ``base_classes`` is a single superclass, the passed class is either: * That superclass itself *or*... * A subclass of that superclass. * Else, ``base_classes`` is a tuple of one or more superclasses. In this case, the passed class is either: * One of those superclasses themselves *or*... * A subclass of one of those superclasses. Caveats ------- **This higher-level tester should always be called in lieu of the lower-level** :func:`issubclass` **builtin,** which raises an undescriptive exception when the first passed parameter is *not* a class: e.g., .. code-block:: python >>> issubclass(object(), type) TypeError: issubclass() arg 1 must be a class This tester suffers no such deficits, instead safely returning ``False`` when the first passed parameter is *not* a class. Parameters ---------- obj : object Object to be inspected. base_classes : TestableTypes Superclass(es) to test whether this object is a subclass of defined as either: * A single class. * A tuple of one or more classes. Returns ------- bool :data:`True` only if this object is an inclusive subclass of these superclass(es). ''' assert isinstance(base_classes, TestableTypesTuple), ( f'{repr(base_classes)} neither class nor tuple of classes.') # Return true only if... return ( # This object is a class *AND*... isinstance(cls, type) and # This class either is this superclass(es) or a subclass of this # superclass(es). issubclass(cls, base_classes) ) #FIXME: Unit test us up, please. def is_type_subclass_proper( cls: object, base_classes: TypeOrTupleTypes) -> bool: ''' ``True`` only if the passed object is a proper subclass of the passed superclass(es). Specifically, this tester returns ``True`` only if either: * If ``base_classes`` is a single superclass, the passed class is a subclass of that superclass (but *not* that superclass itself). * Else, ``base_classes`` is a tuple of one or more superclasses. In this case, the passed class is a subclass of one of those superclasses (but *not* one of those superclasses themselves). Parameters ---------- obj : object Object to be inspected. base_classes : TestableTypes Superclass(es) to test whether this object is a subclass of defined as either: * A single class. * A tuple of one or more classes. Returns ------- bool ``True`` only if this object is a proper subclass of these superclass(es). ''' assert isinstance(base_classes, TestableTypesTuple), ( f'{repr(base_classes)} neither class nor tuple of classes.') # Return true only if... return ( # This object is a class *AND*... isinstance(cls, type) and # This class either is this superclass(es) or a subclass of this # superclass(es) *AND*... issubclass(cls, base_classes) and # It is *NOT* the case that... not ( # If the caller passed a tuple of one or more superclasses, this # class is one of these superclasses themselves; cls in base_classes if isinstance(base_classes, tuple) else # Else, the caller passed a single superclass. In this case, this # class is this superclass itself. cls is base_classes ) ) beartype-0.18.5/beartype/_util/error/000077500000000000000000000000001461113517100175005ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/error/__init__.py000066400000000000000000000000001461113517100215770ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/error/utilerrget.py000066400000000000000000000134751461113517100222520ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **exception getters** (i.e., low-level callables retrieving metadata associated with various types of exceptions). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilExceptionException from beartype._util.text.utiltextlabel import label_exception # ....................{ GETTERS }.................... def get_name_error_attr_name(name_error: NameError) -> str: ''' Unqualified basename of the non-existent attribute referenced by the passed :class:`NameError` exception. Parameters ---------- name_error : NameError :class:`NameError` exception to be inspected. Returns ------- str Unqualified basename of this non-existent attribute. Raises ------ _BeartypeUtilExceptionException If this is *not* a well-known :class:`NameError` exception raised by the standard Python interpreter. Examples -------- .. code-block:: pycon >>> from beartype._util.error.utilerrget import ( ... get_name_error_attr_name) >>> try: ... undefined_attr ... except NameError as name_error: ... print(get_name_error_attr_name(name_error)) ... print(name_error) undefined_attr name 'undefined_attr' is not defined ''' assert isinstance(name_error, NameError), ( f'{repr(name_error)} not "NameError" exception.') # Message associated with this name error. error_message = str(name_error) # Unqualified basename of the non-existent attribute referenced by this # message, initialized to the empty string. attr_name = '' # 0-based index of the first single quote in this message if this message # contains a single quote or "-1" otherwise. # # Note that: # * *ALL* well-recognized name errors contain one or more single-quoted # substrings of the form "'{attr_name}'". The first such single-quoted # substring in each name error provides the desired unqualified basename # of the undefined attribute described by this name error. # * There exist at least three such kinds of name errors, including: # * Most "NameError" exception messages assume the common form: # NameError: name '{attr_name}' is not defined # * Some "NameError" exception messages assume the less common form: # UnboundLocalError: cannot access local variable '{attr_name}' # where it is not associated with a value # * A few "NameError" exception messages assume the uncommon form: # UnboundLocalError: cannot access free variable '{attr_name}' where # it is not associated with a value in enclosing scope. # * The str.find()-based approach performed below is more efficient than: # * A brute-force iterative approach attempting to manually match this # message against an iterable of well-known message prefixes. # * A Python-compatible regular expression (PCRE)-based approach. In # general, PCRE-based matching is only faster than str.find()-based # matching as the number of str.find() calls increases past a certain # threshold. Certainly, two str.find() calls is below that threshold. ERROR_MESSAGE_QUOTE_FIRST_INDEX = error_message.find("'") # If this name error contains at least one single quote... if ERROR_MESSAGE_QUOTE_FIRST_INDEX != -1: # Truncate the prefix of this message preceding this first single quote # (e.g., from "name '{attr_name}' is not defined" to "{attr_name}' is # not defined"). error_message = error_message[ERROR_MESSAGE_QUOTE_FIRST_INDEX + 1:] # print(f'error_message truncated: {error_message}') # 0-based index of the next single quote in this message if this message # contains a second single quote or "-1" otherwise. ERROR_MESSAGE_QUOTE_NEXT_INDEX = error_message.find("'") # If this name error contains at least two single quotes... if ERROR_MESSAGE_QUOTE_NEXT_INDEX != -1: # Define the unqualified basename of the non-existent attribute # referenced by this message as the substring of this message # bounded by the first and second single quotes in this message # (e.g., from "{attr_name}' is not defined" to "{attr_name}"). attr_name = error_message[:ERROR_MESSAGE_QUOTE_NEXT_INDEX] # Else, this name error contains only one single quote. # Else, this name error contains *NO* single quotes. # If this name error is unrecognized, raise an exception. # # Note that this should *NEVER* occur. Of course, this will occur. if not attr_name: raise _BeartypeUtilExceptionException( # pragma: no cover f'Non-standard "{label_exception(name_error)}" unrecognized ' f"(i.e., single-quoted substring '{{attr_name}}' not found)." ) from name_error # Else, this name error is recognized. # # If this basename is *NOT* a Python identifier, raise an exception. # # Note that this should *NEVER* occur. Of course, this will occur. elif not attr_name.isidentifier(): raise _BeartypeUtilExceptionException( # pragma: no cover f'Non-standard "{label_exception(name_error)}" unrecognized ' f"(i.e., single-quoted substring '{attr_name}' found but not a " f'valid Python identifier).' ) from name_error # Else, this name error is a Python identifier. # Return this basename. return attr_name beartype-0.18.5/beartype/_util/error/utilerrraise.py000066400000000000000000000144171461113517100225730ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **exception handlers** (i.e., low-level callables manipulating fatal exceptions in a human-readable, general-purpose manner). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._data.error.dataerrmagic import EXCEPTION_PLACEHOLDER from beartype._util.error.utilerrtest import is_exception_message_str from beartype._util.text.utiltextmunge import uppercase_str_char_first # ....................{ RAISERS }.................... def reraise_exception_placeholder( # Mandatory parameters. exception: Exception, target_str: str, # Optional parameters. source_str: str = EXCEPTION_PLACEHOLDER, ) -> None: ''' Reraise the passed exception in a safe manner preserving both this exception object *and* the original traceback associated with this exception object, but globally replacing all instances of the passed source substring hard-coded into this exception's message with the passed target substring. Parameters ---------- exception : Exception Exception to be reraised. target_str : str Target human-readable format substring to replace the passed source substring previously hard-coded into this exception's message. source_str : Optional[str] Source non-human-readable substring previously hard-coded into this exception's message to be replaced by the passed target substring. Defaults to :data:`.EXCEPTION_PLACEHOLDER`. Raises ------ exception The passed exception, globally replacing all instances of this source substring in this exception's message with this target substring. See Also -------- :data:`.EXCEPTION_PLACEHOLDER` Further commentary on usage and motivation. https://stackoverflow.com/a/62662138/2809027 StackOverflow answer mildly inspiring this implementation. Examples -------- .. code-block:: pycon >>> from beartype.roar import BeartypeDecorHintPepException >>> from beartype._util.cache.utilcachecall import callable_cached >>> from beartype._util.error.utilerrraise import ( ... reraise_exception_placeholder, EXCEPTION_PLACEHOLDER) >>> from random import getrandbits >>> @callable_cached ... def portend_low_level_winter(is_winter_coming: bool) -> str: ... if is_winter_coming: ... raise BeartypeDecorHintPepException( ... '{} intimates that winter is coming.'.format( ... EXCEPTION_PLACEHOLDER)) ... else: ... return 'PRAISE THE SUN' >>> def portend_high_level_winter() -> None: ... try: ... print(portend_low_level_winter(is_winter_coming=False)) ... print(portend_low_level_winter(is_winter_coming=True)) ... except BeartypeDecorHintPepException as exception: ... reraise_exception_placeholder( ... exception=exception, ... target_str=( ... 'Random "Song of Fire and Ice" spoiler' if getrandbits(1) else ... 'Random "Dark Souls" plaintext meme' ... )) >>> portend_high_level_winter() PRAISE THE SUN Traceback (most recent call last): File "", line 30, in portend_high_level_winter() File "", line 27, in portend_high_level_winter 'Random "Dark Souls" plaintext meme' File "/home/leycec/py/beartype/beartype._util.error.utilerrraise.py", line 225, in reraise_exception_placeholder raise exception.with_traceback(exception.__traceback__) File "", line 20, in portend_high_level_winter print(portend_low_level_winter(is_winter_coming=True)) File "/home/leycec/py/beartype/beartype/_util/cache/utilcachecall.py", line 296, in _callable_cached raise exception File "/home/leycec/py/beartype/beartype/_util/cache/utilcachecall.py", line 289, in _callable_cached *args, **kwargs) File "", line 13, in portend_low_level_winter EXCEPTION_PLACEHOLDER)) beartype.roar.BeartypeDecorHintPepException: Random "Song of Fire and Ice" spoiler intimates that winter is coming. ''' assert isinstance(exception, Exception), ( f'{repr(exception)} not exception.') assert isinstance(source_str, str), f'{repr(source_str)} not string.' assert isinstance(target_str, str), f'{repr(target_str)} not string.' # If this is a conventional exception... if is_exception_message_str(exception): # Munged exception message globally replacing all instances of this # source substring with this target substring. # # Note that we intentionally call the lower-level str.replace() method # rather than the higher-level # beartype._util.text.utiltextmunge.replace_str_substrs() function here, # as the latter unnecessarily requires this exception message to contain # one or more instances of this source substring. exception_message = exception.args[0].replace(source_str, target_str) # If doing so actually changed this message... if exception_message != exception.args[0]: # Uppercase the first character of this message if needed. exception_message = uppercase_str_char_first(exception_message) # Reconstitute this exception argument tuple from this message. # # Note that if this tuple contains only this message, this slice # "exception.args[1:]" safely yields the empty tuple. Go, Python! exception.args = (exception_message,) + exception.args[1:] # Else, this message remains preserved as is. # Else, this is an unconventional exception. In this case, preserve this # exception as is. # Re-raise this exception while preserving its original traceback. raise exception.with_traceback(exception.__traceback__) beartype-0.18.5/beartype/_util/error/utilerrtest.py000066400000000000000000000031401461113517100224360ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **exception testers** (i.e., low-level callables introspecting metadata associated with various types of exceptions). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... # ....................{ TESTERS }.................... #FIXME: Unit test us up, please. def is_exception_message_str(exception: Exception) -> bool: ''' :data:`True` only if the message encapsulated by the passed exception is a simple string (as is typically but *not* necessarily the case). Parameters ---------- exception : Exception Exception to be inspected. Returns ------- bool :data:`True` only if this exception's message is a simple string. ''' assert isinstance(exception, Exception), f'{repr(exception)} not exception.' # Return true only if... return bool( # Exception arguments are a tuple (as is typically but not necessarily # the case) *AND*... isinstance(exception.args, tuple) and # This tuple is non-empty (as is typically but not necessarily the # case) *AND*... exception.args and # The first item of this tuple is a string providing this exception's # message (as is typically but *NOT* necessarily the case)... isinstance(exception.args[0], str) ) beartype-0.18.5/beartype/_util/error/utilerrwarn.py000066400000000000000000000221521461113517100224320ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **warning handlers** (i.e., low-level callables manipulating non-fatal warnings -- which, technically, are also exceptions -- in a human-readable, general-purpose manner). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import ( Any, Iterable, ) from beartype._data.error.dataerrmagic import EXCEPTION_PLACEHOLDER from beartype._data.hint.datahinttyping import TypeWarning from beartype._util.error.utilerrtest import is_exception_message_str from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_12 from beartype._util.text.utiltextmunge import uppercase_str_char_first from collections.abc import Iterable as IterableABC from warnings import ( WarningMessage, warn, warn_explicit, ) # ....................{ WARNERS }.................... # If the active Python interpreter targets Python >= 3.12, the standard # warnings.warn() function supports the optional "skip_file_prefixes" parameter # critical for emitting more useful warnings. In this case, define the # issue_warning() warner to pass that parameter. if IS_PYTHON_AT_LEAST_3_12: # ....................{ IMPORTS }.................... # Defer version-specific imports. import beartype from os.path import dirname # ....................{ WARNERS }.................... def issue_warning(cls: TypeWarning, message: str) -> None: # The warning you gave us is surely our last! warn(message, cls, skip_file_prefixes=_ISSUE_WARNING_IGNORE_DIRNAMES) # type: ignore[call-overload] # warn(message, cls) # type: ignore[call-overload] # ....................{ PRIVATE ~ globals }.................... _ISSUE_WARNING_IGNORE_DIRNAMES = (dirname(beartype.__file__),) ''' Tuple of one or more **ignorable warning dirnames** (i.e., absolute directory names of all Python modules to be ignored by the :func:`.issue_warning` warner when deciding which source module to associate with the issued warning, enabling this warner to associate this warning with the original externally defined module to which this warning applies). This tuple includes the dirname of the top-level directory providing the :mod:`beartype` package, enabling this warner to ignore all stack frames produced by internal calls to submodules of this package. Doing so emits substantially more useful and readable warnings for external callers. ''' # Else, the active Python interpreter targets Python < 3.12. In this case, # define the issue_warning() warner to avoid passing that parameter. else: def issue_warning(cls: TypeWarning, message: str) -> None: # Time to cry your tears! Now cry! warn(message, cls) issue_warning.__doc__ = ( ''' Issue (i.e., emit) a non-fatal warning of the passed type with the passed message. Caveats ------- **This high-level warner should always be called in lieu of the low-level** :func:`warnings.warn` **warner.** Whereas the latter issues warnings that obfuscate the external user-defined modules to which those warnings apply, this warner associates this warning with the applicable user-defined module when the active Python interpreter targets Python >= 3.12. Parameters ---------- cls: Type[Warning] Type of warning to be issued. message: str Human-readable warning message to be issued. ''' ) # ....................{ REWARNERS }.................... def reissue_warnings_placeholder( # Mandatory parameters. warnings: Iterable[WarningMessage], target_str: str, # Optional parameters. source_str: str = EXCEPTION_PLACEHOLDER, ) -> None: ''' Reissue (i.e., re-emit) the passed warning in a safe manner preserving both this warning object *and* **associated context** (e.g., filename, line number) associated with this warning object, but globally replacing all instances of the passed source substring hard-coded into this warning's message with the passed target substring. Parameters ---------- warnings : Iterable[WarningMessage] Iterable of zero or more warnings to be reissued, typically produced by an external call to the standard ``warnings.catch_warnings(record=True)`` context manager. target_str : str Target human-readable format substring to replace the passed source substring previously hard-coded into this warning's message. source_str : Optional[str] Source non-human-readable substring previously hard-coded into this warning's message to be replaced by the passed target substring. Defaults to :data:`.EXCEPTION_PLACEHOLDER`. Warns ----- warning The passed warning, globally replacing all instances of this source substring in this warning's message with this target substring. See Also -------- :data:`.EXCEPTION_PLACEHOLDER` Further commentary on usage and motivation. https://stackoverflow.com/a/77516994/2809027 StackOverflow answer strongly inspiring this implementation. ''' assert isinstance(warnings, IterableABC), ( f'{repr(warnings)} not iterable.') assert isinstance(source_str, str), f'{repr(source_str)} not string.' assert isinstance(target_str, str), f'{repr(target_str)} not string.' # For each warning descriptor in this iterable of zero or more warning # descriptors... for warning_info in warnings: assert isinstance(warning_info, WarningMessage), ( # <-- terrible name! f'{repr(warning_info)} not "WarningMessage" instance.') # Original warning wrapped by this warning descriptor, localized both # for readability *AND* negligible speed. *sigh* warning = warning_info.message # Munged warning message to be issued below. warning_message_new: Any = None # If either... if ( # This warning is... *ALREADY A STRING!?* What is going on here? # Look. Not even we know. But mypy claims that warnings recorded by # calls to the standard "warnings.catch_warnings(record=True)" # function satisfy the union "Warning | str". Technically, that # makes no sense. Pragmatically, that makes no sense. But mypy says # it's true. We are too tired to argue with static type-checkers at # 4:11AM in the morning. isinstance(warning, str) or # This warning is conventional... is_exception_message_str(warning) # Then this warning is or has a standard message. In this case... ): # Original warning message, coerced from the original warning. # # Note that the poorly named "message" attribute is the original # warning rather warning message. Just as with exceptions, coercing # this warning into a string reliably retrieves its message. warning_message_old = str(warning) # Munged warning message globally replacing all instances of this # source substring with this target substring. # # Note that we intentionally call the lower-level str.replace() # method rather than the higher-level # beartype._util.text.utiltextmunge.replace_str_substrs() function # here, as the latter unnecessarily requires this warning message to # contain one or more instances of this source substring. warning_message_new = warning_message_old.replace( source_str, target_str) # If doing so actually changed this message... if warning_message_new != warning_message_old: # Uppercase the first character of this message if needed. warning_message_new = uppercase_str_char_first( warning_message_new) # Else, this message remains preserved as is. # Else, this is an unconventional warning. In this case... else: # Tuple of the zero or more arguments with which this warning was # originally issued. warning_args = warning.args # Assert that this warning was issued with exactly one argument. # Since the warnings.warn() signature accepts only a single # "message" parameter, this assertion *SHOULD* always hold. *sigh* assert len(warning_args) == 1 # Preserve this warning as is. warning_message_new = warning_args[0] # Reissue this warning with a possibly modified message. warn_explicit( message=warning_message_new, category=warning_info.category, filename=warning_info.filename, lineno=warning_info.lineno, source=warning_info.source, ) beartype-0.18.5/beartype/_util/func/000077500000000000000000000000001461113517100173025ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/func/__init__.py000066400000000000000000000000001461113517100214010ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/func/arg/000077500000000000000000000000001461113517100200535ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/func/arg/__init__.py000066400000000000000000000000001461113517100221520ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/func/arg/utilfuncargget.py000066400000000000000000000321011461113517100234450ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **callable parameter getter utilities** (i.e., callables introspectively querying metadata on parameters accepted by arbitrary callables). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilCallableException from beartype.typing import Optional from beartype._cave._cavefast import MethodBoundInstanceOrClassType from beartype._data.hint.datahinttyping import ( Codeobjable, TypeException, ) from beartype._util.func.arg.utilfuncargiter import ( ARG_META_INDEX_NAME, iter_func_args, ) from beartype._util.func.utilfunccodeobj import ( get_func_codeobj_or_none, get_func_codeobj, ) from collections.abc import Callable # ....................{ GETTERS ~ arg }.................... #FIXME: Unit test us up, please. def get_func_arg_first_name_or_none( # Mandatory parameters. func: Callable, # Optional parameters. is_unwrap: bool = True, exception_cls: TypeException = _BeartypeUtilCallableException, ) -> Optional[str]: ''' Name of the first parameter listed in the signature of the passed pure-Python callable if any *or* :data:`None` otherwise (i.e., if that callable accepts *no* parameters and is thus parameter-less). Parameters ---------- func : Codeobjable Pure-Python callable, frame, or code object to be inspected. is_unwrap: bool, optional :data:`True` only if this getter implicitly calls the :func:`beartype._util.func.utilfuncwrap.unwrap_func_all` function. Defaults to :data:`True` for safety. See :func:`.iter_func_args` for further commentary. exception_cls : type, optional Type of exception to be raised in the event of a fatal error. Defaults to :class:`._BeartypeUtilCallableException`. Returns ------- Optional[str] Either: * If that callable accepts one or more parameters, the name of the first parameter listed in the signature of that callable. * Else, :data:`None`. Raises ------ exception_cls If that callable is *not* pure-Python. ''' # For metadata describing each parameter accepted by this callable... for arg_meta in iter_func_args( func=func, is_unwrap=is_unwrap, exception_cls=exception_cls, ): # Return the name of this parameter. return arg_meta[ARG_META_INDEX_NAME] # type: ignore[return-value] # Else, the above "return" statement was *NOT* performed. In this case, this # callable accepts *NO* parameters. # Return "None". return None # ....................{ GETTERS ~ args }.................... def get_func_args_flexible_len( # Mandatory parameters. func: Codeobjable, # Optional parameters. is_unwrap: bool = True, exception_cls: TypeException = _BeartypeUtilCallableException, exception_prefix: str = '', ) -> int: ''' Number of **flexible parameters** (i.e., parameters passable as either positional or keyword arguments but *not* positional-only, keyword-only, variadic, or other more constrained kinds of parameters) accepted by the passed pure-Python callable. This getter transparently handles all of the following: * Conventional pure-Python callables. * If ``is_unwrap`` is :data:`True`: * Pure-Python **partials** (i.e., pure-Python callable :class:`functools.partial` objects directly wrapping pure-Python callables). If a partial is passed, this getter transparently returns the total number of flexible parameters accepted by the lower-level callable wrapped by this partial minus the number of flexible parameters partialized away by this partial. Parameters ---------- func : Codeobjable Pure-Python callable, frame, or code object to be inspected. is_unwrap: bool, optional :data:`True` only if this getter implicitly calls the :func:`beartype._util.func.utilfuncwrap.unwrap_func_all` function. Defaults to :data:`True` for safety. See :func:`.get_func_codeobj` for further commentary. exception_cls : type, optional Type of exception to be raised in the event of a fatal error. Defaults to :class:`._BeartypeUtilCallableException`. exception_prefix : str, optional Human-readable label prefixing the message of any exception raised in the event of a fatal error. Defaults to the empty string. Returns ------- int Number of flexible parameters accepted by this callable. Raises ------ exception_cls If that callable is *not* pure-Python. ''' # Avoid circular import dependencies. from beartype._util.api.utilapifunctools import ( get_func_functools_partial_args_flexible_len, is_func_functools_partial, ) # Code object underlying the passed pure-Python callable unwrapped if any # *OR* "None" otherwise (i.e., that callable has *NO* code object). func_codeobj = get_func_codeobj_or_none(func=func, is_unwrap=is_unwrap) # If that callable has a code object, return the number of flexible # parameters accepted by this callable exposed by this code object. if func_codeobj: return func_codeobj.co_argcount # Else, that callable has *NO* code object. # If that callable is *NOT* actually callable, raise an exception. if not callable(func): raise exception_cls(f'{exception_prefix}{repr(func)} uncallable.') # Else, that callable is callable. # If unwrapping that callable *AND* that callable is a partial (i.e., # "functools.partial" object wrapping a lower-level callable), return the # total number of flexible parameters accepted by the pure-Python wrappee # callable wrapped by this partial minus the number of flexible parameters # passed by this partial to this wrappee. if is_unwrap and is_func_functools_partial(func): return get_func_functools_partial_args_flexible_len( func=func, is_unwrap=is_unwrap, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # Else, that callable is *NOT* a partial. # # By process of elimination, that callable *MUST* be an otherwise uncallable # object whose class has intentionally made that object callable by defining # the __call__() dunder method. Fallback to introspecting that method. # "__call__" attribute of that callable if any *OR* "None" otherwise (i.e., # if that callable is actually uncallable). func_call_attr = getattr(func, '__call__', None) # If that callable fails to define the "__call__" attribute, that callable # is actually uncallable. But the callable() builtin claimed that callable # to be callable above. In this case, raise an exception. # # Note that this should *NEVER* happen. Nonetheless, this just happened. if func_call_attr is None: # pragma: no cover raise exception_cls( f'{exception_prefix}{repr(func)} uncallable ' f'(i.e., defines no __call__() dunder method).' ) # Else, that callable defines the __call__() dunder method. # Return the total number of flexible parameters accepted by the pure-Python # wrappee callable wrapped by this bound method descriptor minus one to # account for the first "self" parameter implicitly # passed by this descriptor to that callable. return _get_func_boundmethod_args_flexible_len( func=func_call_attr, is_unwrap=is_unwrap, exception_cls=exception_cls, exception_prefix=exception_prefix, ) #FIXME: Unit test us up, please. def get_func_args_nonvariadic_len( # Mandatory parameters. func: Codeobjable, # Optional parameters. is_unwrap: bool = True, exception_cls: TypeException = _BeartypeUtilCallableException, ) -> int: ''' Number of **non-variadic parameters** (i.e., parameters passable as either positional, positional-only, keyword, or keyword-only arguments) accepted by the passed pure-Python callable. Parameters ---------- func : Codeobjable Pure-Python callable, frame, or code object to be inspected. is_unwrap: bool, optional :data:`True` only if this getter implicitly calls the :func:`beartype._util.func.utilfuncwrap.unwrap_func_all` function. Defaults to :data:`True` for safety. See :func:`.get_func_codeobj` for further commentary. exception_cls : type, optional Type of exception to be raised in the event of a fatal error. Defaults to :class:`._BeartypeUtilCallableException`. Returns ------- int Number of flexible parameters accepted by this callable. Raises ------ exception_cls If that callable is *not* pure-Python. ''' # Code object underlying the passed pure-Python callable unwrapped. func_codeobj = get_func_codeobj( func=func, is_unwrap=is_unwrap, exception_cls=exception_cls, ) # Return the number of non-variadic parameters accepted by this callable. return func_codeobj.co_argcount + func_codeobj.co_kwonlyargcount # ....................{ PRIVATE ~ getters : args }.................... def _get_func_boundmethod_args_flexible_len( # Mandatory parameters. func: MethodBoundInstanceOrClassType, # Optional parameters. is_unwrap: bool = True, exception_cls: TypeException = _BeartypeUtilCallableException, exception_prefix: str = '', ) -> int: ''' Number of **flexible parameters** (i.e., parameters passable as either positional or keyword arguments but *not* positional-only, keyword-only, variadic, or other more constrained kinds of parameters) accepted by the passed **C-based bound instance method descriptor** (i.e., callable implicitly instantiated and assigned on the instantiation of an object whose class declares an instance function (whose first parameter is typically named ``self``)). Specifically, this getter transparently returns one less than the total number of flexible parameters accepted by the lower-level callable wrapped by this descriptor to account for the first ``self`` parameter implicitly passed by this descriptor to that callable. Parameters ---------- func : MethodBoundInstanceOrClassType Bound method descriptor to be inspected. is_unwrap: bool, optional :data:`True` only if this getter implicitly calls the :func:`beartype._util.func.utilfuncwrap.unwrap_func_all` function. Defaults to :data:`True` for safety. See :func:`.get_func_codeobj` for further commentary. exception_cls : type, optional Type of exception to be raised in the event of a fatal error. Defaults to :class:`._BeartypeUtilCallableException`. exception_prefix : str, optional Human-readable label prefixing the message of any exception raised in the event of a fatal error. Defaults to the empty string. Returns ------- int Number of flexible parameters accepted by this callable. Raises ------ exception_cls If that callable is *not* pure-Python. ''' # Avoid circular import dependencies. from beartype._util.func.utilfuncwrap import unwrap_func_boundmethod_once # Unbound pure-Python function encapsulated by this C-based bound method # descriptor bound to some callable object. wrappee = unwrap_func_boundmethod_once( func=func, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # Number of flexible parameters accepted by that function. # # Note that this recursive function call is guaranteed to immediately bottom # out and thus be safe for similar reasons as given above. wrappee_args_flexible_len = get_func_args_flexible_len( func=wrappee, is_unwrap=is_unwrap, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # If this number is zero, the caller maliciously defined a non-static # function accepting *NO* parameters. Since this paradoxically includes the # mandatory first "self" parameter for a bound method descriptor, it is # infeasible for this edge case to occur. Nonetheless, raise an exception. if not wrappee_args_flexible_len: # pragma: no cover raise exception_cls( f'{exception_prefix}{repr(func)} accepts no ' f'parameters despite being a bound instance method descriptor.' ) # Else, this number is positive. # Return this number minus one to account for the fact that this bound # method descriptor implicitly passes the instance object to which this # method descriptor is bound as the first parameter to all calls of this # method descriptor. return wrappee_args_flexible_len - 1 beartype-0.18.5/beartype/_util/func/arg/utilfuncargiter.py000066400000000000000000000641751461113517100236510ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **callable parameter iterator utilities** (i.e., low-level callables introspectively iterating over parameters accepted by arbitrary callables). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilCallableException from beartype.typing import ( Dict, Iterable, Optional, Tuple, ) from beartype._data.hint.datahinttyping import ( # Codeobjable, TypeException, ) from beartype._data.kind.datakinddict import DICT_EMPTY from beartype._util.func.utilfunccodeobj import get_func_codeobj from beartype._util.func.utilfuncwrap import unwrap_func_all_isomorphic from collections.abc import Callable from enum import ( Enum, auto as next_enum_member_value, unique as die_unless_enum_member_values_unique, ) from inspect import CO_VARARGS, CO_VARKEYWORDS from itertools import count from types import CodeType # ....................{ ENUMERATIONS }.................... @die_unless_enum_member_values_unique class ArgKind(Enum): ''' Enumeration of all kinds of **callable parameters** (i.e., arguments passed to pure-Python callables). This enumeration intentionally declares members of the same name as those declared by the standard :class:`inspect.Parameter` class. Whereas the former are unconditionally declared below and thus portable across Python versions, the latter are only conditionally declared depending on Python version and thus non-portable across Python versions. Notably, the :attr:`inspect.Parameter.POSITIONAL_ONLY` attribute is only defined under Python >= 3.8. Attributes ---------- POSITIONAL_ONLY : EnumMemberType Kind of all **positional-only parameters** (i.e., parameters required to be passed positionally, syntactically followed in the signatures of their callables by the :pep:`570`-compliant ``/,`` pseudo-parameter). POSITIONAL_OR_KEYWORD : EnumMemberType Kind of all **flexible parameters** (i.e., parameters permitted to be passed either positionally or by keyword). VAR_POSITIONAL : EnumMemberType Kind of all **variadic positional parameters** (i.e., tuple of zero or more positional parameters *not* explicitly named by preceding positional-only or flexible parameters, syntactically preceded by the ``*`` prefix and typically named ``*args``). KEYWORD_ONLY : EnumMemberType Kind of all **keyword-only parameters** (i.e., parameters required to be passed by keyword, syntactically preceded in the signatures of their callables by the :pep:`3102`-compliant ``*,`` pseudo-parameter). VAR_KEYWORD : EnumMemberType Kind of all **variadic keyword parameters** (i.e., tuple of zero or more keyword parameters *not* explicitly named by preceding keyword-only or flexible parameters, syntactically preceded by the ``**`` prefix and typically named ``**kwargs``). ''' POSITIONAL_ONLY = next_enum_member_value() POSITIONAL_OR_KEYWORD = next_enum_member_value() VAR_POSITIONAL = next_enum_member_value() KEYWORD_ONLY = next_enum_member_value() VAR_KEYWORD = next_enum_member_value() # ....................{ SINGLETONS }.................... ArgMandatory = object() ''' Arbitrary sentinel singleton assigned by the :func:`.iter_func_args` generator to the :data:`.ARG_META_INDEX_DEFAULT` fields of all :class:`.ArgMeta` instances describing **mandatory parameters** (i.e., parameters that *must* be explicitly passed to their callables). ''' # ....................{ HINTS }.................... ArgMeta = Tuple[ArgKind, str, object] ''' PEP-compliant type hint matching each 3-tuple ``(arg_kind, arg_name, default_value_or_mandatory)`` iteratively yielded by the :func:`.iter_func_args` generator for each parameter accepted by the passed pure-Python callable, where: * ``arg_kind`` is this parameter's **kind** (i.e., ".ArgKind" enumeration member conveying the syntactic class of this parameter, constraining how the callable declaring this parameter requires this parameter to be passed). * ``name`` is this parameter's **name** (i.e., syntactically valid Python identifier uniquely identifying this parameter in its parameter list). * ``default_value_or_mandatory`` is either: * If this parameter is mandatory, the magic constant :data:`.ArgMandatory`. * Else, this parameter is optional and thus defaults to a default value when unpassed. In this case, this is that default value. ''' # ....................{ CONSTANTS ~ index }.................... # Iterator yielding the next integer incrementation starting at 0, to be safely # deleted *AFTER* defining the following 0-based indices via this iterator. __arg_meta_index_counter = count(start=0, step=1) ARG_META_INDEX_KIND = next(__arg_meta_index_counter) ''' 0-based index into each 4-tuple iteratively yielded by the generator returned by the :func:`.iter_func_args` generator function of the currently iterated parameter's **kind** (i.e., :class:`ArgKind` enumeration member conveying this parameter's syntactic class, constraining how the callable declaring this parameter requires this parameter to be passed). ''' ARG_META_INDEX_NAME = next(__arg_meta_index_counter) ''' 0-based index into each 4-tuple iteratively yielded by the generator returned by the :func:`.iter_func_args` generator function of the currently iterated parameter's **name** (i.e., syntactically valid Python identifier uniquely identifying this parameter in its parameter list). ''' ARG_META_INDEX_DEFAULT = next(__arg_meta_index_counter) ''' 0-based index into each 4-tuple iteratively yielded by the generator returned by the :func:`.iter_func_args` generator function of the currently iterated parameter's **default value** specified as either: * If this parameter is mandatory, the magic constant :data:`.ArgMandatory`. * Else, this parameter is optional and thus defaults to a default value when unpassed. In this case, this is that default value. ''' # Delete the above counter for safety and sanity in equal measure. del __arg_meta_index_counter # ....................{ GENERATORS }.................... def iter_func_args( # Mandatory parameters. func: Callable, # Optional parameters. func_codeobj: Optional[CodeType] = None, is_unwrap: bool = True, exception_cls: TypeException = _BeartypeUtilCallableException, # Note this generator is intentionally annotated as returning a high-level # "Iterable[...]" rather than a low-level "Generator[..., ..., ...]", as the # syntax governing the latter is overly verbose and largely unhelpful. ) -> Iterable[ArgMeta]: ''' Generator yielding one **parameter metadata tuple** (i.e., tuple whose items describe a single parameter) for each parameter accepted by the passed pure-Python callable. For consistency with the official grammar for callable signatures standardized by :pep:`570`, this generator is guaranteed to yield parameter metadata in the same order as required by Python syntax and semantics. In order, this is: * **Mandatory positional-only parameters** (i.e., parameter metadata whose kind is :attr:`ArgKind.POSITIONAL_ONLY` and whose default value is :data:`ArgMandatory`). * **Optional positional-only parameters** (i.e., parameter metadata whose kind is :attr:`ArgKind.POSITIONAL_ONLY` and whose default value is *not* :data:`ArgMandatory`). * **Mandatory flexible parameters** (i.e., parameter metadata whose kind is :attr:`ArgKind.POSITIONAL_OR_KEYWORD` and whose default value is :data:`ArgMandatory`). * **Optional flexible parameters** (i.e., parameter metadata whose kind is :attr:`ArgKind.POSITIONAL_OR_KEYWORD` and whose default value is *not* :data:`ArgMandatory`). * **Variadic positional parameters** (i.e., parameter metadata whose kind is :attr:`ArgKind.VAR_POSITIONAL` and whose default value is :data:`ArgMandatory`). * **Mandatory and optional keyword-only parameters** (i.e., parameter metadata whose kind is :attr:`ArgKind.KEYWORD_ONLY`). Unlike all other parameter kinds, keyword-only parameters are (by definition) unordered; ergo, Python explicitly permits mandatory and optional keyword-only parameters to be heterogeneously intermingled rather than clustered. * **Variadic keyword parameters** (i.e., parameter metadata whose kind is :attr:`ArgKind.VAR_KEYWORD` and whose default value is :data:`ArgMandatory`). Caveats ------- **This highly optimized generator function should always be called in lieu of the highly unoptimized** :func:`inspect.signature` **function,** which implements a similar introspection as this generator with significantly worse space and time consumption. Seriously. *Never* call that anywhere. Parameters ---------- func : Callable Pure-Python callable to be inspected. func_codeobj: CodeType, optional Code object underlying that callable unwrapped. Defaults to :data:`None`, in which case this iterator internally defers to the comparatively slower :func:`get_func_codeobj` function. is_unwrap: bool, optional :data:`True` only if this generator implicitly calls the :func:`unwrap_func_all_isomorphic` function to unwrap this possibly higher-level wrapper into its possibly lowest-level wrappee *before* returning the code object of that wrappee. Note that doing so incurs worst-case time complexity ``O(n)`` for ``n`` the number of lower-level wrappees wrapped by this wrapper. Defaults to :data:`True` for robustness. Why? Because this generator *must* always introspect lowest-level wrappees rather than higher-level wrappers. The latter typically do *not* wrap the default values of the former, since this is the default behaviour of the :func:`functools.update_wrapper` function underlying the :func:`functools.wrap` decorator underlying all sane decorators. If this boolean is set to :data:`False` while that callable is actually a wrapper, this generator will erroneously misidentify optional as mandatory parameters and fail to yield their default values. Only set this boolean to :data:`False` if you pretend to know what you're doing. exception_cls : type, optional Type of exception to be raised in the event of a fatal error. Defaults to :class:`._BeartypeUtilCallableException`. Yields ------ ArgMeta Parameter metadata tuple describing the currently yielded parameter. Raises ------ exception_cls If that callable is *not* pure-Python. ''' # ..................{ LOCALS ~ noop }.................. # If unwrapping that callable, do so *BEFORE* obtaining the code object of # that callable for safety (to avoid desynchronization between the two). if is_unwrap: func = unwrap_func_all_isomorphic(func) # Else, that callable is assumed to have already been unwrapped by the # caller. We should probably assert that, but doing so requires an # expensive call to hasattr(). What you gonna do? # If passed *NO* code object, query that callable for its code object. if func_codeobj is None: func_codeobj = get_func_codeobj(func=func, exception_cls=exception_cls) # In any case, that code object is now defined. # Bit field of OR-ed binary flags describing this callable. func_codeobj_flags = func_codeobj.co_flags # Number of both optional and mandatory non-keyword-only parameters (i.e., # positional-only *AND* flexible (i.e., positional or keyword) parameters) # accepted by that callable. args_len_posonly_or_flex = func_codeobj.co_argcount # Number of both optional and mandatory keyword-only parameters accepted by # that callable. args_len_kwonly = func_codeobj.co_kwonlyargcount #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Synchronize with the is_func_arg_variadic_positional() and # is_func_arg_variadic_keyword() testers. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # True only if that callable accepts variadic positional or keyword # parameters. For efficiency, these tests are inlined from the # is_func_arg_variadic_positional() and is_func_arg_variadic_keyword() # testers. Yes, this optimization has been profiled to yield joy. is_arg_var_pos = bool(func_codeobj_flags & CO_VARARGS) is_arg_var_kw = bool(func_codeobj_flags & CO_VARKEYWORDS) # print(f'func.__name__ = {func.__name__}\nis_arg_var_pos = {is_arg_var_pos}\nis_arg_var_kw = {is_arg_var_kw}') # If that callable accepts *NO* parameters, silently reduce to the empty # generator (i.e., noop) for both space and time efficiency. Just. Do. It. # # Note that this is a critical optimization when @beartype is # unconditionally applied with import hook automation to *ALL* physical # callables declared by a package, many of which will be argumentless. if ( args_len_posonly_or_flex + args_len_kwonly + is_arg_var_pos + is_arg_var_kw ) == 0: yield from () return # Else, that callable accepts one or more parameters. # ..................{ LOCALS ~ names }.................. # Tuple of the names of all variables localized to that callable. # # Note that this tuple contains the names of both: # * All parameters accepted by that callable. # * All local variables internally declared in that callable's body. # # Ergo, this tuple *CANNOT* be searched in full. Only the subset of this # tuple containing argument names is relevant and may be safely searched. # # Lastly, note the "func_codeobj.co_names" attribute is incorrectly # documented in the "inspect" module as the "tuple of names of local # variables." That's a lie. That attribute is instead a mostly useless # tuple of the names of both globals and object attributes accessed in the # body of that callable. *shrug* args_name = func_codeobj.co_varnames # ..................{ LOCALS ~ defaults }.................. # Tuple of the default values assigned to all optional non-keyword-only # parameters (i.e., all optional positional-only *AND* optional flexible # (i.e., positional or keyword) parameters) accepted by that callable if any # *OR* the empty tuple otherwise. args_defaults_posonly_or_flex = func.__defaults__ or () # type: ignore[attr-defined] # print(f'args_defaults_posonly_or_flex: {args_defaults_posonly_or_flex}') # Dictionary mapping from the name of each optional keyword-only parameter # accepted by that callable to the default value assigned to that parameter # if any *OR* the empty dictionary otherwise. # # For both space and time efficiency, the empty dictionary is intentionally # *NOT* accessed here as "{}". Whereas each instantiation of the empty tuple # efficiently reduces to the same empty tuple, each instantiation of the # empty dictionary inefficiently creates a new empty dictionary: e.g., # >>> () is () # True # >>> {} is {} # False args_defaults_kwonly = func.__kwdefaults__ or DICT_EMPTY # type: ignore[attr-defined] # ..................{ LOCALS ~ len }.................. # Number of both optional and mandatory positional-only parameters accepted # by that callable, standardized under Python >= 3.8 by PEP 570. args_len_posonly = func_codeobj.co_posonlyargcount # type: ignore[attr-defined] assert args_len_posonly_or_flex >= args_len_posonly, ( f'Positional-only and flexible argument count {args_len_posonly_or_flex} < ' f'positional-only argument count {args_len_posonly}.') # Number of both optional and mandatory flexible parameters accepted by # that callable. args_len_flex = args_len_posonly_or_flex - args_len_posonly # Number of optional non-keyword-only parameters accepted by that callable. args_len_posonly_or_flex_optional = len(args_defaults_posonly_or_flex) # Number of optional flexible parameters accepted by that callable, defined # as the number of optional non-keyword-only parameters capped to the total # number of flexible parameters. Why? Because optional flexible parameters # preferentially consume non-keyword-only default values first; optional # positional-only parameters consume all remaining non-keyword-only default # values. Why? Because: # * Default values are *ALWAYS* assigned to positional parameters from # right-to-left. # * Flexible parameters reside to the right of positional-only parameters. # # Specifically, this number is defined as... args_len_flex_optional = min( # If the number of optional non-keyword-only parameters exceeds the # total number of flexible parameters, the total number of flexible # parameters. For obvious reasons, the number of optional flexible # parameters *CANNOT* exceed the total number of flexible parameters; args_len_flex, # Else, the total number of flexible parameters is strictly greater # than the number of optional non-keyword-only parameters, implying # optional flexible parameters consume all non-keyword-only default # values. In this case, the number of optional flexible parameters is # the number of optional non-keyword-only parameters. args_len_posonly_or_flex_optional, ) # Number of optional positional-only parameters accepted by that callable, # defined as all remaining optional non-keyword-only parameters *NOT* # already consumed by positional parameters. Note that this number is # guaranteed to be non-negative. Why? Because, it is the case that either: # * "args_len_posonly_or_flex_optional >= args_len_flex", in which case # "args_len_flex_optional == args_len_flex", in which case # "args_len_posonly_or_flex_optional >= args_len_flex_optional". # * "args_len_posonly_or_flex_optional < args_len_flex", in which case # "args_len_flex_optional == args_len_posonly_or_flex_optional", in which # case "args_len_posonly_or_flex_optional == args_len_flex_optional". # # Just roll with it, folks. It's best not to question the unfathomable. args_len_posonly_optional = ( args_len_posonly_or_flex_optional - args_len_flex_optional) # Number of mandatory positional-only parameters accepted by that callable. args_len_posonly_mandatory = args_len_posonly - args_len_posonly_optional # Number of mandatory flexible parameters accepted by that callable. args_len_flex_mandatory = args_len_flex - args_len_flex_optional # ..................{ INTROSPECTION }.................. # 0-based index of the first parameter of the currently iterated kind # accepted by that callable in the "args_name" tuple. args_index_kind_first = 0 # If that callable accepts at least one mandatory positional-only # parameter... if args_len_posonly_mandatory: # For each mandatory positional-only parameter accepted by that # callable, yield a tuple describing this parameter. for arg_name in args_name[ args_index_kind_first:args_len_posonly_mandatory]: yield (ArgKind.POSITIONAL_ONLY, arg_name, ArgMandatory,) # 0-based index of the first parameter of the next iterated kind. args_index_kind_first = args_len_posonly_mandatory # If that callable accepts at least one optional positional-only # parameter... if args_len_posonly_optional: # 0-based index of the parameter following the last optional # positional-only parameter in the "args_name" tuple. args_index_kind_last_after = ( args_index_kind_first + args_len_posonly_optional) # For the 0-based index of each optional positional-only parameter # accepted by that callable and that parameter, yield a tuple # describing this parameter. for arg_index, arg_name in enumerate(args_name[ args_index_kind_first:args_index_kind_last_after]): # assert arg_posonly_optional_index < args_len_posonly_optional, ( # f'Optional positional-only parameter index {arg_posonly_optional_index} >= ' # f'optional positional-only parameter count {args_len_posonly_optional}.') yield ( ArgKind.POSITIONAL_ONLY, arg_name, args_defaults_posonly_or_flex[arg_index], ) # 0-based index of the first parameter of the next iterated kind. args_index_kind_first = args_index_kind_last_after # If that callable accepts at least one mandatory flexible parameter... if args_len_flex_mandatory: # 0-based index of the parameter following the last mandatory # flexible parameter in the "args_name" tuple. args_index_kind_last_after = ( args_index_kind_first + args_len_flex_mandatory) # For each mandatory flexible parameter accepted by that callable, # yield a tuple describing this parameter. for arg_name in args_name[ args_index_kind_first:args_index_kind_last_after]: yield (ArgKind.POSITIONAL_OR_KEYWORD, arg_name, ArgMandatory,) # 0-based index of the first parameter of the next iterated kind. args_index_kind_first = args_index_kind_last_after # If that callable accepts at least one optional flexible parameter... if args_len_flex_optional: # 0-based index of the parameter following the last optional # flexible parameter in the "args_name" tuple. args_index_kind_last_after = ( args_index_kind_first + args_len_flex_optional) # For the 0-based index of each optional flexible parameter accepted by # this callable and that parameter, yield a 3-tuple describing this # parameter. for arg_index, arg_name in enumerate(args_name[ args_index_kind_first:args_index_kind_last_after]): # assert arg_flex_optional_index < args_len_flex_optional, ( # f'Optional flexible parameter index {arg_flex_optional_index} >= ' # f'optional flexible parameter count {args_len_flex_optional}.') yield ( ArgKind.POSITIONAL_OR_KEYWORD, arg_name, args_defaults_posonly_or_flex[ args_len_posonly_optional + arg_index], ) # 0-based index of the first parameter of the next iterated kind. args_index_kind_first = args_index_kind_last_after # 0-based index of the parameter following the last keyword-only # parameter in the "args_name" tuple. This index is required by multiple # branches below (rather than merely one branch) and thus unconditionally # computed for all these branches. args_index_kind_last_after = args_index_kind_first + args_len_kwonly # If that callable accepts a variadic positional parameter, yield a tuple # describing this parameter. # # Note that: # * This parameter is intentionally yielded *BEFORE* keyword-only # parameters to conform with syntactic standards. A variadic positional # parameter necessarily appears before any keyword-only parameters in the # signature of that callable. # * The 0-based index of this parameter in the "args_name" tuple is exactly # one *AFTER* the last keyword-only parameter in that tuple if any and # one *BEFORE* the variadic keyword parameter in that tuple if any. This # idiosyncrasy is entirely the fault of CPython, which grouped the # two variadic positional and keyword parameters at the end of this list # despite syntactic constraints on their lexical position. if is_arg_var_pos: yield ( ArgKind.VAR_POSITIONAL, args_name[args_index_kind_last_after], ArgMandatory, ) # If that callable accepts at least one keyword-only parameter... if args_len_kwonly: # dict.get() method repeatedly called below and thus localized for # negligible efficiency. Look. Just do this. We needs godspeed. args_defaults_kwonly_get = args_defaults_kwonly.get # For each keyword-only parameter accepted by that callable, yield a # tuple describing this parameter. for arg_name in args_name[ args_index_kind_first:args_index_kind_last_after]: yield ( ArgKind.KEYWORD_ONLY, arg_name, # Either: # * If this is an optional keyword-only parameter, the default # value of this parameter. # * If this is a mandatory keyword-only parameter, the # placeholder "ArgMandatory" singleton. args_defaults_kwonly_get(arg_name, ArgMandatory), ) # If that callable accepts a variadic keyword parameter... if is_arg_var_kw: # 0-based index of the variadic keyword parameter accepted by that # callable in the "args_name" tuple, optimized by noting that Python # booleans are literally integers that can be computed with. Notably: # * If that callable accepts *NO* variadic positional parameter, then: # is_arg_var_pos == 0 # args_index_var_kw == args_index_var_pos # * If that callable accepts a variadic positional parameter, then: # is_arg_var_kw == 1 # args_index_var_pos == args_index_var_pos + 1 args_index_kind_last_after += is_arg_var_pos # Yield a tuple describing this parameter. yield ( ArgKind.VAR_KEYWORD, args_name[args_index_kind_last_after], ArgMandatory, ) beartype-0.18.5/beartype/_util/func/arg/utilfuncargtest.py000066400000000000000000000333461461113517100236610ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **callable parameter tester utilities** (i.e., callables introspectively validating and testing parameters accepted by arbitrary callables). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilCallableException from beartype._util.func.arg.utilfuncargiter import ( ARG_META_INDEX_NAME, iter_func_args, ) from beartype._util.func.utilfunccodeobj import get_func_codeobj from beartype._data.hint.datahinttyping import ( Codeobjable, TypeException, ) from collections.abc import Callable from inspect import ( CO_VARARGS, CO_VARKEYWORDS, ) # ....................{ VALIDATORS }.................... def die_unless_func_args_len_flexible_equal( # Mandatory parameters. func: Codeobjable, func_args_len_flexible: int, # Optional parameters. is_unwrap: bool = True, exception_cls: TypeException = _BeartypeUtilCallableException, exception_prefix: str = '', ) -> None: ''' Raise an exception unless the passed pure-Python callable accepts the passed number of **flexible parameters** (i.e., parameters passable as either positional or keyword arguments). Parameters ---------- func : Codeobjable Pure-Python callable, frame, or code object to be inspected. func_args_len_flexible : int Number of flexible parameters to validate this callable as accepting. is_unwrap: bool, optional :data:`True` only if this validator implicitly calls the :func:`beartype._util.func.utilfuncwrap.unwrap_func_all` function to unwrap this possibly higher-level wrapper into its possibly lowest-level wrappee *before* returning the code object of that wrappee. Note that doing so incurs worst-case time complexity :math:`O(n)` for :math:`n` the number of lower-level wrappees wrapped by this wrapper. Defaults to :data:`True` for robustness. Why? Because this validator *must* always introspect lowest-level wrappees rather than higher-level wrappers. The latter typically do *not* accurately replicate the signatures of the former. In particular, decorator wrappers typically wrap decorated callables with variadic positional and keyword parameters (e.g., ``def _decorator_wrapper(*args, **kwargs)``). Since neither constitutes a flexible parameter, this validator raises an exception when passed such a wrapper with this boolean set to :data:`False`. Only set this boolean to :data:`False` if you pretend to know what you're doing. exception_cls : type, optional Type of exception to be raised. Defaults to :class:`._BeartypeUtilCallableException`. exception_prefix : str, optional Human-readable label prefixing this exception message. Defaults to the empty string. Raises ------ exception_cls If this callable either: * Is *not* callable. * Is callable but is *not* pure-Python. * Is a pure-Python callable accepting either more or less than this Number of flexible parameters. ''' assert isinstance(func_args_len_flexible, int) # Avoid circular import dependencies. from beartype._util.func.arg.utilfuncargget import ( get_func_args_flexible_len) # Number of flexible parameters accepted by this callable. func_args_len_flexible_actual = get_func_args_flexible_len( func=func, is_unwrap=is_unwrap, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # If this callable accepts more or less than this number of flexible # parameters, raise an exception. if func_args_len_flexible_actual != func_args_len_flexible: assert isinstance(exception_cls, type), ( f'{repr(exception_cls)} not class.') assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') raise exception_cls( f'{exception_prefix}callable {repr(func)} flexible argument count ' f'{func_args_len_flexible_actual} != {func_args_len_flexible} ' f'(i.e., {repr(func)} accepts {func_args_len_flexible_actual} ' f'rather than {func_args_len_flexible} positional and/or keyword ' f'parameters).' ) # Else, this callable accepts exactly this number of flexible parameters. #FIXME: Uncomment as needed. # def die_unless_func_argless( # # Mandatory parameters. # func: Codeobjable, # # # Optional parameters. # func_label: str = 'Callable', # exception_cls: Type[Exception] = _BeartypeUtilCallableException, # ) -> None: # ''' # Raise an exception unless the passed pure-Python callable is # **argumentless** (i.e., accepts *no* arguments). # # Parameters # ---------- # func : Codeobjable # Pure-Python callable, frame, or code object to be inspected. # func_label : str, optional # Human-readable label describing this callable in exception messages # raised by this validator. Defaults to ``'Callable'``. # exception_cls : type, optional # Type of exception to be raised if this callable is neither a # pure-Python function nor method. Defaults to # :class:`_BeartypeUtilCallableException`. # # Raises # ---------- # exception_cls # If this callable either: # # * Is *not* callable. # * Is callable but is *not* pure-Python. # * Is a pure-Python callable accepting one or more parameters. # ''' # # # If this callable accepts one or more arguments, raise an exception. # if is_func_argless( # func=func, func_label=func_label, exception_cls=exception_cls): # assert isinstance(func_label, str), f'{repr(func_label)} not string.' # assert isinstance(exception_cls, type), ( # f'{repr(exception_cls)} not class.') # # raise exception_cls( # f'{func_label} {repr(func)} not argumentless ' # f'(i.e., accepts one or more arguments).' # ) # ....................{ TESTERS ~ kind }.................... def is_func_argless( # Mandatory parameters. func: Codeobjable, # Optional parameters. exception_cls: TypeException = _BeartypeUtilCallableException, ) -> bool: ''' :data:`True` only if the passed pure-Python callable is **argumentless** (i.e., accepts *no* arguments). Parameters ---------- func : Codeobjable Pure-Python callable, frame, or code object to be inspected. exception_cls : type, optional Type of exception to be raised in the event of fatal error. Defaults to :class:`._BeartypeUtilCallableException`. Returns ------- bool :data:`True` only if the passed callable accepts *no* arguments. Raises ------ exception_cls If the passed callable is *not* pure-Python. ''' # Code object underlying the passed pure-Python callable unwrapped. func_codeobj = get_func_codeobj( func=func, is_unwrap=False, exception_cls=exception_cls) # Return true only if this callable accepts neither... return not ( # One or more non-variadic arguments *NOR*... is_func_arg_nonvariadic(func_codeobj) or # One or more variadic arguments. is_func_arg_variadic(func_codeobj) ) # ....................{ TESTERS ~ kind : non-variadic }.................... #FIXME: Unit test us up, please. def is_func_arg_nonvariadic(func: Codeobjable) -> bool: ''' :data:`True` only if the passed pure-Python callable accepts any **non-variadic parameters** (i.e., one or more positional, positional-only, keyword, or keyword-only arguments). Parameters ---------- func : Union[Callable, CodeType, FrameType] Pure-Python callable, frame, or code object to be inspected. Returns ------- bool :data:`True` only if that callable accepts any non-variadic parameters. Raises ------ _BeartypeUtilCallableException If that callable is *not* pure-Python. ''' # Avoid circular import dependencies. from beartype._util.func.arg.utilfuncargget import ( get_func_args_nonvariadic_len) # Return true only if this callable accepts any non-variadic parameters. return bool(get_func_args_nonvariadic_len(func)) # ....................{ TESTERS ~ kind : variadic }.................... def is_func_arg_variadic(func: Codeobjable) -> bool: ''' :data:`True` only if the passed pure-Python callable accepts any **variadic parameters** (i.e., either a variadic positional argument (e.g., ``*args``) *or* a variadic keyword argument (e.g., ``**kwargs``)). Parameters ---------- func : Union[Callable, CodeType, FrameType] Pure-Python callable, frame, or code object to be inspected. Returns ------- bool :data:`True` only if that callable accepts either: * Variadic positional arguments (e.g., ``*args``). * Variadic keyword arguments (e.g., ``**kwargs``). Raises ------ _BeartypeUtilCallableException If that callable is *not* pure-Python. ''' # Return true only if this callable declares either... # # We can't believe it's this simple, either. But it is. return ( # Variadic positional arguments *OR*... is_func_arg_variadic_positional(func) or # Variadic keyword arguments. is_func_arg_variadic_keyword(func) ) def is_func_arg_variadic_positional(func: Codeobjable) -> bool: ''' :data:`True` only if the passed pure-Python callable accepts a variadic positional argument (e.g., ``*args``). Parameters ---------- func : Union[Callable, CodeType, FrameType] Pure-Python callable, frame, or code object to be inspected. Returns ------- bool :data:`True` only if the passed callable accepts a variadic positional argument. Raises ------ _BeartypeUtilCallableException If the passed callable is *not* pure-Python. ''' #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Synchronize with the iter_func_args() iterator. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Code object underlying the passed pure-Python callable unwrapped. func_codeobj = get_func_codeobj(func=func, is_unwrap=False) # Return true only if this callable declares variadic positional arguments. return func_codeobj.co_flags & CO_VARARGS != 0 def is_func_arg_variadic_keyword(func: Codeobjable) -> bool: ''' :data:`True` only if the passed pure-Python callable accepts a variadic keyword argument (e.g., ``**kwargs``). Parameters ---------- func : Codeobjable Pure-Python callable, frame, or code object to be inspected. Returns ------- bool :data:`True` only if the passed callable accepts a variadic keyword argument. Raises ------ _BeartypeUtilCallableException If the passed callable is *not* pure-Python. ''' #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Synchronize with the iter_func_args() iterator. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Code object underlying the passed pure-Python callable unwrapped. func_codeobj = get_func_codeobj(func=func, is_unwrap=False) # Return true only if this callable declares variadic keyword arguments. return func_codeobj.co_flags & CO_VARKEYWORDS != 0 # ....................{ TESTERS ~ name }.................... #FIXME: *THIS TESTER IS HORRIFYINGLY SLOW*, thanks to a naive implementation #deferring to the slow iter_func_args() iterator. A substantially faster #get_func_arg_names() getter should be implemented instead and this tester #refactored to call that getter. How? Simple: # def get_func_arg_names(func: Callable) -> Tuple[str]: # # A trivial algorithm for deciding the number of arguments can be # # found at the head of the iter_func_args() iterator. # args_len = ... # # # One-liners for great glory. # return func.__code__.co_varnames[:args_len] # <-- BOOM def is_func_arg_name(func: Callable, arg_name: str) -> bool: ''' :data:`True` only if the passed pure-Python callable accepts an argument with the passed name. Caveats ------- **This tester exhibits worst-case time complexity** ``O(n)`` **for** ``n`` **the total number of arguments accepted by this callable,** due to unavoidably performing a linear search for an argument with this name is this callable's argument list. This tester should thus be called sparingly and certainly *not* repeatedly for the same callable. Parameters ---------- func : Callable Pure-Python callable to be inspected. arg_name : str Name of the argument to be searched for. Returns ------- bool :data:`True` only if that callable accepts an argument with this name. Raises ------ _BeartypeUtilCallableException If the passed callable is *not* pure-Python. ''' assert isinstance(arg_name, str), f'{arg_name} not string.' # Return true only if... return any( # This is the passed name... arg_meta[ARG_META_INDEX_NAME] == arg_name # For the name of any parameter accepted by this callable. for arg_meta in iter_func_args(func) ) beartype-0.18.5/beartype/_util/func/pep/000077500000000000000000000000001461113517100200665ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/func/pep/__init__.py000066400000000000000000000000001461113517100221650ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/func/pep/utilpep484func.py000066400000000000000000000027021461113517100232370ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`484`-compliant **callable utilities** (i.e., callables specifically applicable to :pep:`484`-compliant decorators used to decorate user-defined callables). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from collections.abc import Callable # ....................{ TESTERS }.................... def is_func_pep484_notypechecked(func: Callable) -> bool: ''' ``True`` only if the passed callable was decorated by the :pep:`484`-compliant :func:`typing.no_type_check` decorator instructing both static and runtime type checkers to ignore that callable with respect to type-checking (and thus preserve that callable as is). Parameters ---------- func : Callable Callable to be inspected. Returns ---------- bool ``True`` only if that callable was decorated by the :pep:`484`-compliant :func:`typing.no_type_check` decorator. ''' # Return true only if that callable declares a dunder attribute hopefully # *ONLY* declared on that callable by the @typing.no_type_check decorator. return getattr(func, '__no_type_check__', False) is True beartype-0.18.5/beartype/_util/func/utilfunccode.py000066400000000000000000000776451461113517100223630ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **callable source code** (i.e., file providing the uncompiled pure-Python source code from which a compiled callable originated) utilities. This private submodule implements supplementary callable-specific utility functions required by various :mod:`beartype` facilities, including callables generated by the :func:`beartype.beartype` decorator. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... #FIXME: *FILE UPSTREAM CPYTHON ISSUES.* Unfortunately, this submodule exposed a #number of significant issues in the CPython stdlib -- all concerning parsing #of lambda functions. These include: # #1. The inspect.getsourcelines() function raises unexpected # "tokenize.TokenError" exceptions when passed lambda functions preceded by # one or more triple-quoted strings: e.g., # >>> import inspect # >>> built_to_fail = ( # ... ('''Uh-oh. # ... ''', lambda obj: 'Oh, Gods above and/or below!' # ... ) # ... ) # >>> inspect.getsourcelines(built_to_fail[1])} # tokenize.TokenError: ('EOF in multi-line string', (323, 8)) #2. The "func.__code__.co_firstlineno" attribute is incorrect for syntactic # constructs resembling: # assert is_hint_pep593_beartype(Annotated[ # <--- line 1 # str, Is[lambda text: bool(text)]]) is True # <--- line 2 # Given such a construct, the nested lambda function should have a # "func.__code__.co_firstlineno" attribute whose value is "2". Instead, that # attribute's value is "1". This then complicates detection of lambda # functions, which are already non-trivial enough to detect. Specifically, # this causes the inspect.findsource() function to either raise an unexpected # "OSError" *OR* return incorrect source when passed a file containing the # above snippet. In either case, that is bad. *sigh* #3. Introspecting the source code for two or more lambdas defined on the same # line is infeasible, because code objects only record line numbers rather # than both line and column numbers. Well, that's unfortunate. # ^--- Actually, we're *PRETTY* sure that Python 3.11 has finally resolved # this by now recording column numbers with code objects. So, let's # improve the logic below to handle this edge case under Python >= 3.11. #FIXME: Contribute get_func_code_or_none() back to this StackOverflow question #as a new answer, as this is highly non-trivial, frankly: # https://stackoverflow.com/questions/59498679/how-can-i-get-exactly-the-code-of-a-lambda-function-in-python/64421174#64421174 # ....................{ IMPORTS }.................... from ast import ( NodeVisitor, parse as ast_parse, ) from beartype.roar._roarwarn import _BeartypeUtilCallableWarning from beartype.typing import ( List, Optional, ) from beartype._data.hint.datahinttyping import TypeWarning from beartype._util.error.utilerrwarn import issue_warning from beartype._util.func.utilfunccodeobj import get_func_codeobj from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_9 from collections.abc import Callable from inspect import ( findsource, getsource, ) from traceback import format_exc # ....................{ GETTERS ~ code : lines }.................... #FIXME: Unit test us up. def get_func_code_lines_or_none( # Mandatory parameters. func: Callable, # Optional parameters. warning_cls: TypeWarning = _BeartypeUtilCallableWarning, ) -> Optional[str]: ''' **Line-oriented callable source code** (i.e., string concatenating the subset of all lines of the on-disk Python script or module declaring the passed pure-Python callable) if that callable was declared on-disk *or* ``None`` otherwise (i.e., if that callable was dynamically declared in-memory). Caveats ---------- **The higher-level** :func:`get_func_code_or_none` **getter should typically be called instead.** Why? Because this lower-level getter inexactly returns all lines embedding the declaration of the passed callable rather than the exact substring of those lines declaring that callable. Although typically identical for non-lambda functions, those two strings typically differ for lambda functions. Lambda functions are expressions embedded in larger statements rather than full statements. **This getter is excruciatingly slow.** See the :func:`get_func_code_or_none` getter for further commentary. Parameters ---------- func : Callable Callable to be inspected. warning_cls : TypeWarning, optional Type of warning to be emitted in the event of a non-fatal error. Defaults to :class:`_BeartypeUtilCallableWarning`. Returns ---------- Optional[str] Either: * If the passed callable was physically declared by a file, a string concatenating the subset of lines of that file declaring that callable. * If the passed callable was dynamically declared in-memory, ``None``. Warns ---------- :class:`warning_cls` If the passed callable is defined by a pure-Python source code file but is *not* parsable from that file. While we could allow any parser exception to percolate up the call stack and halt the active Python process when left unhandled, doing so would render :mod:`beartype` fragile -- gaining us little and costing us much. ''' # Avoid circular import dependencies. from beartype._util.func.utilfuncfile import is_func_file from beartype._util.text.utiltextlabel import label_callable # If the passed callable exists on-disk and is thus pure-Python... if is_func_file(func): # Attempt to defer to the standard inspect.getsource() function. try: return getsource(func) # If that function raised *ANY* exception, reduce that exception to a # non-fatal warning. except Exception: # Reduce this fatal error to a non-fatal warning embedding a full # exception traceback as a formatted string. issue_warning( cls=warning_cls, message=f'{label_callable(func)} not parsable:\n{format_exc()}', ) # Else, the passed callable only exists in-memory. # Return "None" as a fallback. return None #FIXME: Unit test us up. def get_func_file_code_lines_or_none( # Mandatory parameters. func: Callable, # Optional parameters. warning_cls: TypeWarning = _BeartypeUtilCallableWarning, ) -> Optional[str]: ''' **Line-oriented callable source file code** (i.e., string concatenating *all* lines of the on-disk Python script or module declaring the passed pure-Python callable) if that callable was declared on-disk *or* ``None`` otherwise (i.e., if that callable was dynamically declared in-memory). Caveats ---------- **This getter is excruciatingly slow.** See the :func:`get_func_code_or_none` getter for further commentary. Parameters ---------- func : Callable Callable to be inspected. warning_cls : TypeWarning, optional Type of warning to be emitted in the event of a non-fatal error. Defaults to :class:`_BeartypeUtilCallableWarning`. Returns ---------- Optional[str] Either: * If the passed callable was physically declared by an file, a string concatenating *all* lines of that file. * If the passed callable was dynamically declared in-memory, ``None``. Warns ---------- :class:`warning_cls` If the passed callable is defined by a pure-Python source code file but is *not* parsable from that file. While we could allow any parser exception to percolate up the call stack and halt the active Python process when left unhandled, doing so would render :mod:`beartype` fragile -- gaining us little and costing us much. ''' # Avoid circular import dependencies. from beartype._util.func.utilfuncfile import is_func_file from beartype._util.text.utiltextlabel import label_callable # If the passed callable exists on-disk and is thus pure-Python... if is_func_file(func): # Attempt to defer to the standard inspect.findsource() function, which # returns a 2-tuple "(file_code_lines, file_code_lineno_start)", where: # * "file_code_lines" is a list of all lines of the script or module # declaring the passed callable. # * "file_code_lineno_start" is the line number of the first such line # declaring the passed callable. Since this line number is already # provided by the "co_firstlineno" instance variable of this # callable's code object, however, there is *NO* reason whatsoever to # return this line number. Indeed, it's unclear why that function # returns this uselessly redundant metadata in the first place. try: # List of all lines of the file declaring the passed callable. func_file_code_lines, _ = findsource(func) # Return this list concatenated into a string. return ''.join(func_file_code_lines) # If that function raised *ANY* exception, reduce that exception to a # non-fatal warning. While we could permit this exception to percolate # up the call stack and inevitably halt the active Python process when # left unhandled, doing so would render @beartype fragile -- gaining us # little and costing us much. # # Notably, the lower-level inspect.getblock() function internally # called by the higher-level findsource() function prematurely halted # due to an unexpected bug in the pure-Python tokenizer leveraged by # inspect.getblock(). Notably, this occurs when passing lambda # functions preceded by triple-quoted strings: e.g., # >>> import inspect # >>> built_to_fail = ( # ... ('''Uh-oh. # ... ''', lambda obj: 'Oh, Gods above and/or below!' # ... ) # ... ) # >>> inspect.findsource(built_to_fail[1])} # tokenize.TokenError: ('EOF in multi-line string', (323, 8)) except Exception: # Reduce this fatal error to a non-fatal warning embedding a full # exception traceback as a formatted string. issue_warning( cls=warning_cls, message=f'{label_callable(func)} not parsable:\n{format_exc()}', ) # Else, the passed callable only exists in-memory. # Return "None" as a fallback. return None # ....................{ GETTERS ~ code : lambda }.................... # If the active Python interpreter targets Python >= 3.9 and thus defines the # ast.unparse() function required to decompile AST nodes into source code, # define the get_func_code_or_none() getter to get only the exact source code # substring defining a passed lambda function rather than the inexact # concatenation of all source code lines embedding that definition. if IS_PYTHON_AT_LEAST_3_9: # Defer version-specific imports. from ast import unparse as ast_unparse # type: ignore[attr-defined] _LAMBDA_CODE_FILESIZE_MAX = 1000000 ''' Maximum size (in bytes) of files to be safely parsed for lambda function declarations by the :func:`get_func_code_or_none` getter. ''' def get_func_code_or_none( # Mandatory parameters. func: Callable, # Optional parameters. warning_cls: TypeWarning = _BeartypeUtilCallableWarning, ) -> Optional[str]: # Avoid circular import dependencies. from beartype._util.func.utilfunctest import is_func_lambda from beartype._util.text.utiltextlabel import label_callable # If the passed callable is a pure-Python lambda function... if is_func_lambda(func): # Attempt to parse the substring of the source code defining this # lambda from the file providing that code. # # For safety, this function reduces *ALL* exceptions raised by this # introspection to non-fatal warnings and returns "None". Why? # Because the standard "ast" module in general and our # "_LambdaNodeUnparser" class in specific are sufficiently fragile # as to warrant extreme caution. AST parsing and unparsing is # notoriously unreliable across different versions of different # Python interpreters and compilers. # # Moreover, we *NEVER* call this function in a critical code path; # we *ONLY* call this function to construct human-readable # exception messages. Clearly, raising low-level non-human-readable # exceptions when attempting to raise high-level human-readable # exceptions rather defeats the entire purpose of the latter. # # Lastly, note that "pytest" will still fail any tests emitting # unexpected warnings. In short, raising exceptions here would gain # @beartype little and cost @beartype much. try: # String concatenating all lines of the file defining that # lambda if that lambda is defined by a file *OR* "None". lambda_file_code = get_func_file_code_lines_or_none( func=func, warning_cls=warning_cls) # If that lambda is defined by a file... if lambda_file_code: # Code object underlying this lambda. func_codeobj = get_func_codeobj(func) # If this file exceeds a sane maximum file size, emit a # non-fatal warning and safely ignore this file. if len(lambda_file_code) >= _LAMBDA_CODE_FILESIZE_MAX: issue_warning( cls=warning_cls, message=( f'{label_callable(func)} not parsable, ' f'as file size exceeds safe maximum ' f'{_LAMBDA_CODE_FILESIZE_MAX}MB.' ), ) # Else, this file *SHOULD* be safely parsable by the # standard "ast" module without inducing a fatal # segmentation fault. else: # Abstract syntax tree (AST) parsed from this file. ast_tree = ast_parse(lambda_file_code) # Lambda node unparser decompiling all AST lambda nodes # encapsulating lambda functions starting at the same # line number as the passed lambda in this file. lambda_node_unparser = _LambdaNodeUnparser( lambda_lineno=func_codeobj.co_firstlineno) # Perform this decompilation. lambda_node_unparser.visit(ast_tree) # List of each code substring exactly covering each # lambda function starting at that line number. lambdas_code = lambda_node_unparser.lambdas_code # If one or more lambda functions start at that line # number... if lambdas_code: # If two or more lambda functions start at that # line number, emit a non-fatal warning. Since # lambda functions only provide a starting line # number rather than both starting line number # *AND* column, we have *NO* means of # disambiguating between these lambda functions and # thus *CANNOT* raise an exception. if len(lambdas_code) >= 2: # Human-readable concatenation of the # definitions of all lambda functions defined # on that line. lambdas_code_str = '\n '.join(lambdas_code) # Emit this warning. issue_warning( cls=warning_cls, message=( f'{label_callable(func)} ambiguous, ' f'as that line defines ' f'{len(lambdas_code)} lambdas; ' f'arbitrarily selecting first ' f'lambda:\n{lambdas_code_str}' ), ) # Else, that line number defines one lambda. # Return the substring covering that lambda. return lambdas_code[0] # Else, *NO* lambda functions start at that line # number. In this case, emit a non-fatal warning. # # Ideally, we would instead raise a fatal exception. # Why? Because this edge case violates expectations. # Since the passed lambda function claims it originates # from some line number of some file *AND* since that # file both exists and is parsable as valid Python, we # expect that line number to define one or more lambda # functions. If it does not, raising an exception seems # superficially reasonable. Yet, we don't. See above. else: issue_warning( cls=warning_cls, message=f'{label_callable(func)} not found.', ) # Else, that lambda is dynamically defined in-memory. # If *ANY* of the dodgy stdlib callables (e.g., ast.parse(), # inspect.findsource()) called above raise *ANY* other unexpected # exception, reduce this fatal error to a non-fatal warning with an # exception traceback as a formatted string. # # Note that the likeliest (but certainly *NOT* only) type of # exception to be raised is a "RecursionError", as described by the # official documentation for the ast.unparse() function: # Warning: Trying to unparse a highly complex expression would # result with RecursionError. except Exception: issue_warning( cls=warning_cls, message=( f'{label_callable(func)} not parsable:\n' f'{format_exc()}' ), ) # Else, the passed callable is *NOT* a pure-Python lambda function. # In any case, the above logic failed to introspect code for the passed # callable. Defer to the get_func_code_lines_or_none() function. return get_func_code_lines_or_none(func=func, warning_cls=warning_cls) # Helper class instantiated above to decompile AST lambda nodes. class _LambdaNodeUnparser(NodeVisitor): ''' **Lambda node unparser** (i.e., object decompiling the abstract syntax tree (AST) nodes of *all* pure-Python lambda functions defined in a caller-specified block of source code into the exact substrings of that block defining those lambda functions by applying the visitor design pattern to an AST parsed from that block). Attributes ---------- lambdas_code : List[str] List of one or more **source code substrings** (i.e., of one or more lines of code) defining each of the one or more lambda functions starting at line :attr:`_lambda_lineno` of the code from which the AST visited by this visitor was parsed. _lambda_lineno : int Caller-requested line number (of the code from which the AST visited by this object was parsed) starting the definition of the lambda functions to be unparsed by this visitor. ''' # ................{ INITIALIZERS }................ def __init__(self, lambda_lineno: int) -> None: ''' Initialize this visitor. Parameters ---------- lambda_lineno : int Caller-specific line number (of the code from which the AST visited by this object was parsed) starting the definition of the lambda functions to be unparsed by this visitor. ''' assert isinstance(lambda_lineno, int), ( f'{repr(lambda_lineno)} not integer.') assert lambda_lineno >= 0, f'{lambda_lineno} < 0.' # Initialize our superclass. super().__init__() # Classify all passed parameters. self._lambda_lineno = lambda_lineno # Initialize all remaining instance variables. self.lambdas_code: List[str] = [] def visit_Lambda(self, node): ''' Visit (i.e., handle, process) the passed AST node encapsulating the definition of a lambda function (parsed from the code from which the AST visited by this visitor was parsed) *and*, if that lambda starts on the caller-requested line number, decompile this node back into the substring of this line defining that lambda. Parameters ---------- node : LambdaNode AST node encapsulating the definition of a lambda function. ''' # If the desired lambda starts on the current line number... if node.lineno == self._lambda_lineno: # Decompile this node into the substring of this line defining # this lambda. self.lambdas_code.append(ast_unparse(node)) # Recursively visit all child nodes of this lambda node. While # doing so is largely useless, a sufficient number of dragons # are skulking to warrant an abundance of caution and magic. self.generic_visit(node) # Else if the desired lambda starts on a later line number than # the current line number, recursively visit all child nodes of # the current lambda node. elif node.lineno < self._lambda_lineno: self.generic_visit(node) #FIXME: Consider raising an exception here instead like #"StopException" to force a premature halt to this recursion. Of #course, handling exceptions also incurs a performance cost, so... # Else, the desired lambda starts on an earlier line number than # the current line number, the current lambda *CANNOT* be the # desired lambda and is thus ignorable. In this case, avoid # recursively visiting *ANY* child nodes of the current lambda node # to induce a premature halt to this recursive visitation. # Else, the active Python interpreter targets only Python < 3.9 and thus does # *NOT* define the ast.unparse() function required to decompile AST nodes into # source code. In this case... else: def get_func_code_or_none( # Mandatory parameters. func: Callable, # Optional parameters. warning_cls: TypeWarning = _BeartypeUtilCallableWarning, ) -> Optional[str]: # Defer to the get_func_code_lines_or_none() function as is. return get_func_code_lines_or_none(func=func, warning_cls=warning_cls) get_func_code_or_none.__doc__ = ''' **Callable source code** (i.e., substring of all lines of the on-disk Python script or module declaring the passed pure-Python callable) if that callable was declared on-disk *or* ``None`` otherwise (i.e., if that callable was dynamically declared in-memory). Specifically, this getter returns: * If the passed callable is a lambda function *and* active Python interpreter targets Python >= 3.9 (and thus defines the ast.unparse() function required to decompile AST lambda nodes into original source code), the exact substring of that code declaring that lambda function. * Else, the concatenation of all lines of that code declaring that callable. Caveats ---------- **This getter is excruciatingly slow.** This getter should *only* be called when unavoidable and ideally *only* in performance-agnostic code paths. Notably, this getter finds relevant lines by parsing the script or module declaring the passed callable starting at the first line of that declaration and continuing until a rudimentary tokenizer implemented in pure-Python (with *no* concern for optimization and thus slow beyond all understanding of slow) detects the last line of that declaration. In theory, we could significantly optimize that routine; in practice, anyone who cares will preferably compile or JIT :mod:`beartype` instead. Parameters ---------- func : Callable Callable to be inspected. warning_cls : TypeWarning, optional Type of warning to be emitted in the event of a non-fatal error. Defaults to :class:`_BeartypeUtilCallableWarning`. Returns ---------- Optional[str] Either: * If the passed callable was physically declared by a file, the exact substring of all lines of that file declaring that callable. * If the passed callable was dynamically declared in-memory, ``None``. Warns ---------- :class:`warning_cls` If the passed callable is a pure-Python lambda function that was physically declared by either: * A large file exceeding a sane maximum file size (e.g., 1MB). Note that: * If this is *not* explicitly guarded against, passing absurdly long strings to the :func:`ast.parse` function can actually induce a segmentation fault in the active Python process. This is a longstanding and unresolved issue in the :mod:`ast` module. See also: https://bugs.python.org/issue32758 * Generously assuming each line of that file contains between 40 to 80 characters, this maximum supports files of between 12,500 to 25,000 lines, which at even the lower end of that range covers most real-world files of interest. * A complex (but *not* necessarily large) file causing the recursive :func:`ast.parse` or :func:`ast.unparse` function or any other :mod:`ast` callable to exceed Python's recursion limit by exhausting the stack. * A line of source code declaring two or more lambda functions (e.g., ``lambdas = lambda: 'and black,', lambda: 'and pale,'``). In this case, the substring of code declaring the first such function is ambiguously returned; all subsequent such functions are unavoidably ignored. See Also ---------- https://stackoverflow.com/questions/59498679/how-can-i-get-exactly-the-code-of-a-lambda-function-in-python/64421174#64421174 StackOverflow answer strongly inspiring this implementation. ''' # ....................{ GETTERS ~ label }.................... #FIXME: This getter no longer has a sane reason to exist. Consider excising. # from beartype.roar._roarexc import _BeartypeUtilCallableException # from beartype._cave._cavefast import CallableTypes # from sys import modules # # def get_func_code_label(func: Callable) -> str: # ''' # Human-readable label describing the **origin** (i.e., uncompiled source) of # the passed callable. # # Specifically, this getter returns either: # # * If that callable is pure-Python *and* physically declared on-disk, the # absolute filename of the uncompiled on-disk Python script or module # physically declaring that callable. # * If that callable is pure-Python *and* dynamically declared in-memory, # the placeholder string ``""``. # * If that callable is C-based, the placeholder string ``""``. # # Caveats # ---------- # **This getter is intentionally implemented for speed rather than robustness # against unlikely edge cases.** The string returned by this getter is *only* # intended to be embedded in human-readable labels, warnings, and exceptions. # Avoid using this string for *any* mission-critical purpose. # # Parameters # ---------- # func : Callable # Callable to be inspected. # # Returns # ---------- # str # Either: # # * If that callable is physically declared by an uncompiled Python # script or module, the absolute filename of this script or module. # * Else, the placeholder string ``""`` implying that callable to # have been dynamically declared in-memory. # # Raises # ------ # _BeartypeUtilCallableException # If that callable is *not* callable. # # See Also # ---------- # :func:`inspect.getsourcefile` # Inefficient stdlib function strongly inspiring this implementation, # which has been highly optimized for use by the performance-sensitive # :func:`beartype.beartype` decorator. # ''' # # # If this callable is uncallable, raise an exception. # if not callable(func): # raise _BeartypeUtilCallableException(f'{repr(func)} not callable.') # # Else, this callable is callable. # # # Human-readable label describing the origin of the passed callable. # func_origin_label = '' # # # If this callable is a standard callable rather than arbitrary class or # # object overriding the __call__() dunder method... # if isinstance(func, CallableTypes): # # Avoid circular import dependencies. # from beartype._util.func.utilfuncfile import get_func_filename_or_none # from beartype._util.func.utilfuncwrap import unwrap_func_all_isomorphic # # # Code object underlying the passed pure-Python callable unwrapped if # # this callable is pure-Python *OR* "None" otherwise. # func_filename = get_func_filename_or_none(unwrap_func_all_isomorphic(func)) # # # If this callable has a code object, set this label to either the # # absolute filename of the physical Python module or script declaring # # this callable if this code object provides that metadata *OR* a # # placeholder string specific to C-based callables otherwise. # func_origin_label = func_filename if func_filename else '' # # Else, this callable is *NOT* a standard callable. In this case... # else: # # If this callable is *NOT* a class (i.e., is an object defining the # # __call__() method), reduce this callable to the class of this object. # if not isinstance(func, type): # func = type(func) # # In either case, this callable is now a class. # # # Fully-qualified name of the module declaring this class if this class # # was physically declared by an on-disk module *OR* "None" otherwise. # func_module_name = func.__module__ # # # If this class was physically declared by an on-disk module, defer to # # the absolute filename of that module. # # # # Note that arbitrary modules need *NOT* declare the "__file__" dunder # # attribute. Unlike most other core Python objects, modules are simply # # arbitrary objects that reside in the "sys.modules" dictionary. # if func_module_name: # func_origin_label = getattr( # modules[func_module_name], '__file__', func_origin_label) # # # Return this label. # return func_origin_label beartype-0.18.5/beartype/_util/func/utilfunccodeobj.py000066400000000000000000000335121461113517100230370ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **callable code object utilities** (i.e., callables introspecting **code objects** (i.e., instances of the :class:`CodeType` type) underlying all pure-Python callables). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilCallableException from beartype.typing import ( Any, Optional, ) from beartype._data.hint.datahinttyping import ( Codeobjable, TypeException, ) from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_11 from types import ( CodeType, FrameType, FunctionType, GeneratorType, MethodType, ) # ....................{ GETTERS }.................... def get_func_codeobj( # Mandatory parameters. func: Codeobjable, # Optional parameters. is_unwrap: bool = False, exception_cls: TypeException = _BeartypeUtilCallableException, exception_prefix: str = '', ) -> CodeType: ''' **Code object** (i.e., instance of the :class:`CodeType` type) underlying the passed **codeobjable** (i.e., pure-Python object directly associated with a code object) if this object is codeobjable *or* raise an exception otherwise (e.g., if this object is *not* codeobjable). For convenience, this getter also accepts a code object, in which case that code object is simply returned as is. Code objects have a docstring under CPython resembling: .. code-block:: python Code objects provide these attributes: co_argcount number of arguments (not including *, ** args or keyword only arguments) co_code string of raw compiled bytecode co_cellvars tuple of names of cell variables co_consts tuple of constants used in the bytecode co_filename name of file in which this code object was created co_firstlineno number of first line in Python source code co_flags bitmap: 1=optimized | 2=newlocals | 4=*arg | 8=**arg | 16=nested | 32=generator | 64=nofree | 128=coroutine | 256=iterable_coroutine | 512=async_generator co_freevars tuple of names of free variables co_posonlyargcount number of positional only arguments co_kwonlyargcount number of keyword only arguments (not including ** arg) co_lnotab encoded mapping of line numbers to bytecode indices co_name name with which this code object was defined co_names tuple of names of local variables co_nlocals number of local variables co_qualname fully-qualified name with which this code object was defined (Python >= 3.11 only) co_stacksize virtual machine stack space required co_varnames tuple of names of arguments and local variables Parameters ---------- func : Codeobjable Codeobjable to be inspected. is_unwrap: bool, optional :data:`True` only if this getter implicitly calls the :func:`beartype._util.func.utilfuncwrap.unwrap_func_all` function to unwrap this possibly higher-level wrapper into a possibly lower-level wrappee *before* returning the code object of that wrappee. Note that doing so incurs worst-case time complexity :math:`O(n)` for :math:`n` the number of lower-level wrappees wrapped by this wrapper. Defaults to :data:`False` for efficiency. exception_cls : TypeException, optional Type of exception to be raised in the event of a fatal error. Defaults to :class:`._BeartypeUtilCallableException`. exception_prefix : str, optional Human-readable label prefixing the message of any exception raised in the event of a fatal error. Defaults to the empty string. Returns ------- CodeType Code object underlying this codeobjable. Raises ------ exception_cls If this codeobjable has *no* code object and is thus *not* pure-Python. ''' # Code object underlying this callable if this callable is pure-Python *OR* # "None" otherwise. func_codeobj = get_func_codeobj_or_none(func=func, is_unwrap=is_unwrap) # If this callable is *NOT* pure-Python... if func_codeobj is None: # Avoid circular import dependencies. from beartype._util.func.utilfunctest import die_unless_func_python # Raise an exception. die_unless_func_python( func=func, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # Else, this callable is pure-Python and this code object exists. # Return this code object. return func_codeobj # type: ignore[return-value] def get_func_codeobj_or_none( # Mandatory parameters. # # Note that the "func" parameter is intentionally annotated as "Any" rather # than "Codeobjable", as this tester transparently supports *ALL* objects. func: Any, # Optional parameters. is_unwrap: bool = False, ) -> Optional[CodeType]: ''' **Code object** (i.e., instance of the :class:`CodeType` type) underlying the passed **codeobjable** (i.e., pure-Python object directly associated with a code object) if this object is codeobjable *or* :data:`None` otherwise (e.g., if this object is *not* codeobjable). Specifically, if the passed object is a: * Pure-Python function, this getter returns the code object of that function (i.e., ``func.__code__``). * Pure-Python bound method wrapping a pure-Python unbound function, this getter returns the code object of the latter (i.e., ``func.__func__.__code__``). * Pure-Python call stack frame, this getter returns the code object of the pure-Python callable encapsulated by that frame (i.e., ``func.f_code``). * Pure-Python generator, this getter returns the code object of that generator (i.e., ``func.gi_code``). * Code object, this getter returns that code object as is. * Any other object, this getter raises an exception. Caveats ------- If ``is_unwrap``, **this callable has worst-case time complexity** :math:`O(n)` **for** :math:`n` **the number of lower-level wrappees wrapped by this higher-level wrapper.** That parameter should thus be disabled in time-critical code paths; instead, the lowest-level wrappee returned by the :func:``beartype._util.func.utilfuncwrap.unwrap_func_all` function should be temporarily stored and then repeatedly passed. Parameters ---------- func : Any Codeobjable to be inspected. is_unwrap: bool, optional :data:`True` only if this getter implicitly calls the :func:`beartype._util.func.utilfuncwrap.unwrap_func_all` function to unwrap this possibly higher-level wrapper into a possibly lower-level wrappee *before* returning the code object of that wrappee. Note that doing so incurs worst-case time complexity :math:`O(n)` for :math:`n` the number of lower-level wrappees wrapped by this wrapper. Defaults to :data:`False` for both efficiency and disambiguity. Returns ------- Optional[CodeType] Either: * If this codeobjable is pure-Python, the code object underlying this codeobjable. * Else, :data:`None`. See Also -------- :func:`.get_func_codeobj` Further details. ''' assert is_unwrap.__class__ is bool, f'{is_unwrap} not boolean.' # Avoid circular import dependencies. from beartype._util.func.utilfuncwrap import unwrap_func_all # Note that: # * For efficiency, tests are intentionally ordered in decreasing likelihood # of a successful match. # * An equivalent algorithm could also technically be written as a chain of # "getattr(func, '__code__', None)" calls, but that doing so would both be # less efficient *AND* render this getter less robust. Why? Because the # getattr() builtin internally calls the __getattr__() and # __getattribute__() dunder methods (either of which could raise # arbitrary exceptions) and is thus considerably less safe. # # If this object is already a code object, return this object as is. if isinstance(func, CodeType): return func # Else, this object is *NOT* already a code object. # # If this object is a pure-Python function... # # Note that this test intentionally leverages the standard # "types.FunctionType" class rather than our equivalent # "beartype.cave.FunctionType" class to avoid circular import issues. elif isinstance(func, FunctionType): #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Synchronize this with the same test below (for methods). #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Return the code object of either: # * If unwrapping this function, the lowest-level wrappee wrapped by # this function. # * Else, this function as is. return (unwrap_func_all(func) if is_unwrap else func).__code__ # type: ignore[attr-defined] # Else, this object is *NOT* a pure-Python function. # # If this callable is a bound method, return this method's code object. # # Note this test intentionally tests the standard "types.MethodType" class # rather than our equivalent "beartype.cave.MethodBoundInstanceOrClassType" # class to avoid circular import issues. elif isinstance(func, MethodType): # Unbound function underlying this bound method. func = func.__func__ #FIXME: Can "MethodType" objects actually bind lower-level C-based #rather than pure-Python functions? We kinda doubt it -- but maybe they #can. If they can't, then this test is superfluous and should be #removed with all haste. # If this unbound function is pure-Python... if isinstance(func, FunctionType): #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Synchronize this with the same test above. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Return the code object of either: # * If unwrapping this function, the lowest-level wrappee wrapped # by this function. # * Else, this function as is. return (unwrap_func_all(func) if is_unwrap else func).__code__ # type: ignore[attr-defined] # Else, this callable is *NOT* a pure-Python bound method. # # If this object is a pure-Python generator, return this generator's code # object. elif isinstance(func, GeneratorType): return func.gi_code # Else, this object is *NOT* a pure-Python generator. # # If this object is a call stack frame, return this frame's code object. elif isinstance(func, FrameType): #FIXME: *SUS AF.* This is likely to behave as expected *ONLY* for frames #encapsulating pure-Python callables. For frames encapsulating C-based #callables, this is likely to fail with an "AttributeError" exception. #That said, we have *NO* idea how to test this short of defining our own #C-based callable accepting a pure-Python callable as a callback #parameter and calling that callback. Are there even C-based callables #like that in the wild? return func.f_code # Else, this object is *NOT* a call stack frame. Since none of the above # tests matched, this object *MUST* be a C-based callable. # Fallback to returning "None". return None # ....................{ GETTERS }.................... #FIXME: Unit test us up, please. def get_func_codeobj_basename(func: Codeobjable, **kwargs) -> str: ''' Unqualified basename (contextually depending on the version of the active Python interpreter) of the passed **codeobjable** (i.e., pure-Python object directly associated with a code object) if this object is codeobjable *or* raise an exception otherwise (e.g., if this object is *not* codeobjable). Specifically, this getter returns: * If the active Python interpreter targets Python >= 3.11, the value of the the ``co_qualname`` attribute on this code object. * Else, the value of the ``co_name`` attribute on this code object. Parameters ---------- func : Codeobjable Codeobjable to be inspected. All remaining keyword parameters are passed as is to the :func:`.get_func_codeobj` getter. Raises ------ exception_cls If this codeobjable has *no* code object and is thus *not* pure-Python. ''' # Code object underlying this codeobjable if pure-Python *OR* raise an # exception otherwise (i.e., if this codeobjable is C-based). func_codeobj = get_func_codeobj(func, **kwargs) # Return either... return ( # If the active Python interpreter targets Python >= 3.11 and thus # defines the "co_qualname" attribute on code objects, that attribute; func_codeobj.co_qualname # type: ignore[attr-defined] if IS_PYTHON_AT_LEAST_3_11 else # Else, the active Python interpreter targets Python < 3.11 and thus # does *NOT* defines the "co_qualname" attribute on code objects. In # this case, the "co_name" attribute instead. func_codeobj.co_name ) beartype-0.18.5/beartype/_util/func/utilfuncfile.py000066400000000000000000000173011461113517100223470ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **callable origin** (i.e., uncompiled source from which a compiled callable originated) utilities. This private submodule implements supplementary callable-specific utility functions required by various :mod:`beartype` facilities, including callables generated by the :func:`beartype.beartype` decorator. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... #FIXME: *FILE UPSTREAM CPYTHON ISSUES.* Unfortunately, this submodule exposed a #number of significant issues in the CPython stdlib -- all concerning parsing #of lambda functions. These include: # #1. The inspect.getsourcelines() function raises unexpected # "tokenize.TokenError" exceptions when passed lambda functions preceded by # one or more triple-quoted strings: e.g., # >>> import inspect # >>> built_to_fail = ( # ... ('''Uh-oh. # ... ''', lambda obj: 'Oh, Gods above and/or below!' # ... ) # ... ) # >>> inspect.getsourcelines(built_to_fail[1])} # tokenize.TokenError: ('EOF in multi-line string', (323, 8)) #FIXME: Contribute get_func_code_or_none() back to this StackOverflow question #as a new answer, as this is highly non-trivial, frankly: # https://stackoverflow.com/questions/59498679/how-can-i-get-exactly-the-code-of-a-lambda-function-in-python/64421174#64421174 # ....................{ IMPORTS }.................... from beartype.typing import Optional from beartype._data.hint.datahinttyping import Codeobjable from beartype._util.func.utilfunccodeobj import get_func_codeobj_or_none from linecache import cache as linecache_cache # ....................{ TESTERS }.................... def is_func_file(func: Codeobjable) -> bool: ''' :data:`True` only if the passed callable is defined **on-disk** (e.g., by a script or module whose pure-Python source code is accessible to the active Python interpreter as a file on the local filesystem). Equivalently, this tester returns :data:`False` if that callable is dynamically defined in-memory (e.g., by a prior call to the :func:`exec` or :func:`eval` builtins). Parameters ---------- func : Codeobjable Codeobjable to be inspected. Returns ------- bool :data:`True` only if the passed callable is defined on-disk. ''' # One-liners for abstruse abstraction. return get_func_filename_or_none(func) is not None # ....................{ GETTERS }.................... def get_func_filename_or_none(func: Codeobjable, **kwargs) -> Optional[str]: ''' Absolute filename of the file on the local filesystem containing the pure-Python source code for the script or module defining the passed callable if that callable is defined on-disk *or* :data:`None` otherwise (i.e., if that callable is dynamically defined in-memory by a prior call to the :func:`exec` or :func:`eval` builtins). Parameters ---------- func : Codeobjable Codeobjable to be inspected. All remaining keyword parameters are passed as is to the :func:`beartype._util.func.utilfunccodeobj.get_func_codeobj` getter. Returns ------- Optional[str] Either: * If that callable was physically declared by a file, the absolute filename of that file. * If that callable was dynamically declared in-memory, :data:`None`. ''' # Code object underlying the passed callable if that callable is pure-Python # *OR* "None" otherwise (i.e., if that callable is C-based). # # Note that we intentionally do *NOT* test whether this callable is # explicitly pure-Python or C-based: e.g., # # If this callable is implemented in C, this callable has no code # # object with which to inspect the filename declaring this callable. # # In this case, defer to a C-specific placeholder string. # if isinstance(func, CallableCTypes): # func_origin_label = '' # # Else, this callable is implemented in Python. In this case... # else: # # If this callable is a bound method wrapping an unbound function, # # unwrap this method into the function it wraps. Why? Because only # # the latter provides the code object for this callable. # if isinstance(func, MethodBoundInstanceOrClassType): # func = func.__func__ # # # Defer to the absolute filename of the Python file declaring this # # callable, dynamically retrieved from this callable's code object. # func_origin_label = func.__code__.co_filename # # Why? Because PyPy. The logic above succeeds for CPython but fails for # PyPy, because *ALL CALLABLES ARE C-BASED IN PYPY.* Adopting the above # approach would unconditionally return the C-specific placeholder string # for all callables -- including those originally declared as pure-Python in # a Python module. So it goes. func_codeobj = get_func_codeobj_or_none(func, **kwargs) # If the passed callable has *NO* code object and is thus *NOT* pure-Python, # that callable was *NOT* defined by a pure-Python source code file. In this # case, return "None". if not func_codeobj: return None # Else, that callable is pure-Python. # Absolute filename of the pure-Python source code file defining that # callable if this code object offers that metadata *OR* "None" otherwise. # # Note that we intentionally do *NOT* assume all code objects to offer this # metadata (e.g., by unconditionally returning "func_codeobj.co_filename"). # Why? Because PyPy yet again. For inexplicable reasons, PyPy provides # *ALL* C-based builtins (e.g., len()) with code objects failing to provide # this metadata. Yes, this is awful. Yes, this is the Python ecosystem. func_filename = getattr(func_codeobj, 'co_filename', None) # If either this code object does not provide this filename *OR*... if not func_filename or ( # This filename is a "<"- and ">"-bracketed placeholder string, this # filename is a placeholder signifying this callable to be dynamically # declared in-memory rather than by an on-disk module. Examples include: # * "", signifying a callable dynamically declared in-memory. # * "<@beartype(...) at 0x...}>', signifying a callable dynamically # declared in-memory by the # beartype._util.func.utilfuncmake.make_func() function, possibly # cached with the standard "linecache" module. func_filename[ 0] == '<' and func_filename[-1] == '>' and # This in-memory callable's source code was *NOT* cached with the # "linecache" module and has thus effectively been destroyed. func_filename not in linecache_cache # Then return "None", as this filename is useless for almost all purposes. ): return None # Else, this filename is either: # * That of an on-disk module, which is good. # * That of an in-memory callable whose source code was cached with the # "linecache" module. Although less good, this filename *CAN* technically # be used to recover this code by querying the "linecache" module. # Return this filename as is, regardless of whether this file exists. # Callers are responsible for performing further validation if desired. # print(f'func_filename: {func_filename}') return func_filename beartype-0.18.5/beartype/_util/func/utilfuncframe.py000066400000000000000000000356311461113517100225300ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **call stack frame utilities** (i.e., callables introspecting the current stack of frame objects, encapsulating the linear chain of calls to external callables underlying the call to the current callable). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... import sys from beartype.roar._roarexc import _BeartypeUtilCallFrameException from beartype.typing import ( Callable, Iterable, Optional, ) from beartype._cave._cavefast import CallableFrameType from beartype._data.hint.datahinttyping import TypeException # ....................{ GETTERS }.................... #FIXME: Mypy insists this getter can return "None" in certain edge cases. But... #what are those? Official documentation is seemingly silent on the issue. *sigh* get_frame: Optional[Callable[[int], Optional[CallableFrameType]]] = getattr( sys, '_getframe', None) ''' Private low-level :func:`sys._getframe` getter if the active Python interpreter declares this getter *or* :data:`None` otherwise (i.e., if this interpreter does *not* declare this getter). All standard Python interpreters supported by this package including both CPython *and* PyPy declare this getter. Ergo, this attribute should *always* be a valid callable rather than :data:`None`. If this getter is *not* :data:`None`, this getter's signature and docstring under CPython resembles: :: _getframe([depth]) -> frameobject Return a frame object from the call stack. If optional integer depth is given, return the frame object that many calls below the top of the stack. If that is deeper than the call stack, ValueError is raised. The default for depth is zero, returning the frame at the top of the call stack. Frame objects provide these attributes: f_back next outer frame object (this frame's caller) f_builtins built-in namespace seen by this frame f_code code object being executed in this frame f_globals global namespace seen by this frame f_lasti index of last attempted instruction in bytecode f_lineno current line number in Python source code f_locals local namespace seen by this frame f_trace tracing function for this frame, or None Parameters ---------- depth : int 0-based index of the stack frame on the current call stack to be returned. Defaults to 0, signifying the stack frame encapsulating the lexical scope directly calling this getter. Returns ------- CallableFrameType Stack frame with the passed index on the current call stack. Raises ------ ValueError If this index exceeds the **height** (i.e., total number of stack frames) of the current call stack. ''' #FIXME: Preserve until we inevitably require this getter, please. #def get_frame_or_none(ignore_frames: int) -> Optional[FrameType]: # try: # return get_frame(ignore_frames + 1) # except ValueError: # return None #def get_frame_caller_or_none() -> Optional[FrameType]: # return get_frame_or_none(ignore_frames=2) # ....................{ GETTERS ~ name }.................... #FIXME: Unit test us up, please. def get_frame_package_name( # Mandatory parameters. frame: CallableFrameType, # Optional parameters. exception_cls: TypeException = _BeartypeUtilCallFrameException, ) -> Optional[str]: ''' Fully-qualified name of the parent package of the child module declaring the callable whose code object is that of the passed **stack frame** (i.e., :class:`types.CallableFrameType` instance encapsulating all metadata describing a single call on the current call stack) if module has a parent package *or* the empty string otherwise (e.g., if that module is either a top-level module or script residing outside any parent package structure). Parameters ---------- frame : CallableFrameType Stack frame to be inspected. exception_cls : TypeException, optional Type of exception to be raised in the event of a fatal error. Defaults to :class:`._BeartypeUtilCallFrameException`. Returns ------- Optional[str] Either: * If the callable described by this frame resides in a package, the fully-qualified name of that package. * Else, :data:`None`. Raises ------ exception_cls If that callable has *no* code object and is thus *not* pure-Python. See Also -------- :func:`beartype._util.func.utilfunccodeobj.get_func_codeobj_basename` Related getter getting the unqualified basename of that callable. ''' assert isinstance(frame, CallableFrameType), ( f'{repr(frame)} not stack frame.') # Fully-qualified name of the parent package of the child module declaring # the callable whose code object is that of this stack frame's if that # module declares its name *OR* the empty string otherwise (e.g., if that # module is either a top-level module or script residing outside any parent # package structure). frame_package_name = frame.f_globals.get('__package__') # Return the name of this parent package. return frame_package_name #FIXME: Unit test us up, please. def get_frame_module_name( # Mandatory parameters. frame: CallableFrameType, # Optional parameters. exception_cls: TypeException = _BeartypeUtilCallFrameException, ) -> Optional[str]: ''' Fully-qualified name of the module declaring the callable whose code object is that of the passed **stack frame** (i.e., :class:`types.CallableFrameType` instance encapsulating all metadata describing a single call on the current call stack). Parameters ---------- frame : CallableFrameType Stack frame to be inspected. Returns ------- Optional[str] Either: * If the callable described by this frame resides in a module, the fully-qualified name of that module. * Else, :data:`None`. See Also -------- :func:`beartype._util.func.utilfunccodeobj.get_func_codeobj_basename` Related getter getting the unqualified basename of that callable. ''' assert isinstance(frame, CallableFrameType), ( f'{repr(frame)} not stack frame.') # Fully-qualified name of the module declaring the callable described by # this frame. frame_module_name = frame.f_globals.get('__name__') # Return this name. return frame_module_name #FIXME: Preserved for posterity. Currently unused, but potentially useful. # from beartype._data.func.datafunccodeobj import FUNC_CODEOBJ_NAME_MODULE # #FIXME: Unit test us up, please. # def get_frame_name( # # Mandatory parameters. # frame: CallableFrameType, # # # Optional parameters. # exception_cls: TypeException = _BeartypeUtilCallFrameException, # ) -> str: # ''' # Fully-qualified name of the callable whose code object is that of the passed # **stack frame** (i.e., :class:`types.CallableFrameType` instance # encapsulating all metadata describing a single call on the current call # stack). # # Parameters # ---------- # frame : CallableFrameType # Stack frame to be inspected. # # Returns # ------- # str # Fully-qualified name of the callable described by this frame. # ''' # # # Avoid circular import dependencies. # from beartype._util.func.utilfunccodeobj import get_func_codeobj_basename # # # Fully-qualified name of the module declaring the callable described by # # this frame. # frame_module_name = get_frame_module_name( # frame=frame, exception_cls=exception_cls) # # # Unqualified basename of that callable. # frame_basename = get_func_codeobj_basename( # func=frame, exception_cls=exception_cls) # # # Possibly fully-qualified name of that callable, defined as either... # frame_name = ( # # If that callable is *NOT* actually a callable but instead the # # top-level lexical scope of a module, omit the prefixing module name # # (which is, in any case, the meaningless magic string ""); # frame_basename # if frame_module_name == FUNC_CODEOBJ_NAME_MODULE else # # Else, that callable is actually a callable. In this case, prefix the # # unqualified basename of that callable by the fully-qualified name of # # the module declaring that callable. # f'{frame_module_name}.{frame_basename}' # ) # # # Return this name. # return frame_name # ....................{ ITERATORS }.................... def iter_frames( # Optional parameters. func_stack_frames_ignore: int = 0, ) -> Iterable[CallableFrameType]: ''' Generator yielding one **frame** (i.e., :class:`types.CallableFrameType` instance) for each call on the current **call stack** (i.e., stack of frame objects, encapsulating the linear chain of calls to external callables underlying the current call to this callable). Notably, for each: * **C-based callable call** (i.e., call of a C-based rather than pure-Python callable), this generator yields one frame encapsulating *no* code object. Only pure-Python frame objects have code objects. * **Class-scoped callable call** (i.e., call of an arbitrary callable occurring at class scope rather than from within the body of a callable or class, typically due to a method being decorated), this generator yields one frame ``func_frame`` for that class ``cls`` such that ``func_frame.f_code.co_name == cls.__name__`` (i.e., the name of the code object encapsulated by that frame is the unqualified name of the class encapsulating the lexical scope of this call). Actually, we just made all of that up. That is *probably* (but *not* necessarily) the case. Research is warranted. * **Module-scoped callable call** (i.e., call of an arbitrary callable occurring at module scope rather than from within the body of a callable or class, typically due to a function or class being decorated), this generator yields one frame ``func_frame`` such that ``func_frame.f_code.co_name == ''`` (i.e., the name of the code object encapsulated by that frame is the placeholder string assigned by the active Python interpreter to all scopes encapsulating the top-most lexical scope of a module in the current call stack). The above constraints imply that frames yielded by this generator *cannot* be assumed to encapsulate code objects. See the "Examples" subsection for standard logic handling this edge case. Caveats ------- **This high-level iterator requires the private low-level** :func:`sys._getframe` **getter.** If that getter is undefined, this iterator reduces to the empty generator yielding nothing rather than raising an exception. Since all standard Python implementations (e.g., CPython, PyPy) define that getter, this should typically *not* be a real-world concern. Parameters ---------- func_stack_frames_ignore : int, optional Number of frames on the call stack to be ignored (i.e., silently incremented past). Defaults to 0. Returns ------- Iterable[CallableFrameType] Generator yielding one frame for each call on the current call stack. See Also -------- :func:`.get_frame` Further details on stack frame objects. Examples -------- >>> from beartype._util.func.utilfunccodeobj import ( ... get_func_codeobj_or_none) >>> from beartype._util.func.utilfuncframe import iter_frames # For each stack frame on the call stack... >>> for func_frame in iter_frames(): ... # Code object underlying this frame's scope if this scope is ... # pure-Python *OR* "None" otherwise. ... func_frame_codeobj = get_func_codeobj_or_none(func_frame) ... ... # If this code object does *NOT* exist, this scope is C-based. ... # In this case, silently ignore this scope and proceed to the ... # next frame in the call stack. ... if func_frame_codeobj is None: ... continue ... # Else, this code object exists, implying this scope to be ... # pure-Python. ... ... # Fully-qualified name of this scope's module. ... func_frame_module_name = func_frame.f_globals['__name__'] ... ... # Unqualified name of this scope. ... func_frame_name = func_frame_codeobj.co_name ... ... # Print the fully-qualified name of this scope. ... print(f'On {func_frame_module_name}.{func_frame_name}()!') ''' assert isinstance(func_stack_frames_ignore, int), ( f'{func_stack_frames_ignore} not integer.') assert func_stack_frames_ignore >= 0, ( f'{func_stack_frames_ignore} negative.') # If the active Python interpreter fails to declare the private # sys._getframe() getter, reduce to the empty generator (i.e., noop). if get_frame is None: # pragma: no cover yield from () return # Else, the active Python interpreter declares the sys._getframe() getter. # Attempt to obtain the... try: # Next non-ignored frame following the last ignored frame, ignoring an # additional frame embodying the current call to this iterator. func_frame = get_frame(func_stack_frames_ignore + 1) # type: ignore[misc] # If doing so raises a "ValueError" exception... except ValueError as value_error: # Whose message matches this standard boilerplate, the caller requested # that this generator ignore more stack frames than currently exist on # the call stack. Permitting this exception to unwind the call stack # would only needlessly increase the fragility of this already fragile # mission-critical generator. Instead, swallow this exception and # silently reduce to the empty generator (i.e., noop). if str(value_error) == 'call stack is not deep enough': yield from () return # Whose message does *NOT* match this standard boilerplate, an # unexpected exception occurred. In this case, re-raise this exception. raise # Else, doing so raised *NO* "ValueError" exception. # print(f'start frame: {repr(func_frame)}') # While at least one frame remains on the call stack... while func_frame: # print(f'current frame: {repr(func_frame)}') # Yield this frame to the caller. yield func_frame # Iterate to the next frame on the call stack. func_frame = func_frame.f_back beartype-0.18.5/beartype/_util/func/utilfuncget.py000066400000000000000000000155111461113517100222100ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **callable getters** (i.e., utility functions dynamically querying and retrieving various properties of passed callables). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilCallableException from beartype.typing import ( Callable, Optional, ) from beartype._cave._cavefast import MethodBoundInstanceOrClassType from beartype._data.hint.datahinttyping import ( HintAnnotations, TypeException, ) # ....................{ GETTERS ~ descriptors }.................... #FIXME: Unit test us up, please. #FIXME: Docstring us up, please. def get_func_boundmethod_self( # Mandatory parameters. func: MethodBoundInstanceOrClassType, # Optional parameters. exception_cls: TypeException = _BeartypeUtilCallableException, exception_prefix: str = '', ) -> object: ''' Instance object to which the passed **C-based bound instance method descriptor** (i.e., callable implicitly instantiated and assigned on the instantiation of an object whose class declares an instance function (whose first parameter is typically named ``self``) as an instance variable of that object such that that callable unconditionally passes that object as the value of that first parameter on all calls to that callable) was bound as an instance attribute at instantiation time. Parameters ---------- func : object Bound method descriptor to be inspected. exception_cls : TypeException, optional Type of exception to be raised in the event of a fatal error. Defaults to :exc:`._BeartypeUtilCallableWrapperException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- object Instance object to which this bound method descriptor is bound to. Raises ------ exception_cls If the passed object is *not* a bound method descriptor. See Also -------- :func:`beartype._util.func.utilfunctest.is_func_boundmethod` Further details. ''' # Avoid circular import dependencies. from beartype._util.func.utilfunctest import die_unless_func_boundmethod # If this object is *NOT* a class method descriptor, raise an exception. die_unless_func_boundmethod( func=func, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # Else, this object is a class method descriptor. # Return the pure-Python function wrapped by this descriptor. Just do it! return func.__self__ # ....................{ GETTERS ~ hints }.................... #FIXME: Refactor all unsafe access of the low-level "__annotations__" dunder #attribute to instead call this high-level getter, please. #FIXME: Unit test us up, please. def get_func_annotations( # Mandatory parameters. func: Callable, # Optional parameters. exception_cls: TypeException = _BeartypeUtilCallableException, exception_prefix: str = '', ) -> HintAnnotations: ''' **Annotations** (i.e., dictionary mapping from the name of each annotated parameter or return of the passed pure-Python callable to the type hint annotating that parameter or return) of that callable. Parameters ---------- func : object Object to be inspected. exception_cls : TypeException, optional Type of exception to be raised in the event of a fatal error. Defaults to :exc:`._BeartypeUtilCallableException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- HintAnnotations Annotations of that callable. Raises ------ exception_cls If that callable is *not* actually a pure-Python callable. See Also -------- :func:`.get_func_annotations_or_none` Further details. ''' # Annotations of that callable if that callable is actually a pure-Python # callable *OR* "None" otherwise. hint_annotations = get_func_annotations_or_none(func) # If that callable is *NOT* pure-Python, raise an exception. if hint_annotations is None: assert isinstance(exception_cls, type), ( f'{repr(exception_cls)} not class.') assert issubclass(exception_cls, Exception), ( f'{repr(exception_cls)} not exception subclass.') assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') # If that callable is uncallable, raise an appropriate exception. if not callable(func): raise exception_cls(f'{exception_prefix}{repr(func)} not callable.') # Else, that callable is callable. # Raise a human-readable exception. raise exception_cls( f'{exception_prefix}{repr(func)} not pure-Python function.') # Else, that callable is pure-Python. # Return these annotations. return hint_annotations #FIXME: Refactor all unsafe access of the low-level "__annotations__" dunder #attribute to instead call this high-level getter, please. #FIXME: Unit test us up, please. def get_func_annotations_or_none(func: Callable) -> Optional[HintAnnotations]: ''' **Annotations** (i.e., dictionary mapping from the name of each annotated parameter or return of the passed pure-Python callable to the type hint annotating that parameter or return) of that callable if that callable is actually a pure-Python callable *or* :data:`None` otherwise (i.e., if that callable is *not* a pure-Python callable). Parameters ---------- func : object Object to be inspected. Returns ------- Optional[HintAnnotations] Either: * If that callable is actually a pure-Python callable, the annotations of that callable. * Else, :data:`None`. ''' # Demonstrable monstrosity demons! # # Note that the "__annotations__" dunder attribute is guaranteed to exist # *ONLY* for standard pure-Python callables. Various other callables of # interest (e.g., functions exported by the standard "operator" module) do # *NOT* necessarily declare that attribute. Since this tester is commonly # called in general-purpose contexts where this guarantee does *NOT* # necessarily hold, we intentionally access that attribute safely albeit # somewhat more slowly via getattr(). return getattr(func, '__annotations__', None) beartype-0.18.5/beartype/_util/func/utilfuncmake.py000066400000000000000000000455041461113517100223530ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **callable factories** (i.e., low-level functions dynamically creating and returning new in-memory callables). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilCallableException from beartype.typing import Optional from beartype._data.hint.datahinttyping import ( LexicalScope, TypeException, ) from beartype._util.text.utiltextlabel import label_exception from beartype._util.text.utiltextmunge import number_str_lines from beartype._util.utilobject import get_object_name from collections.abc import Callable from functools import update_wrapper from linecache import cache as linecache_cache # type: ignore[attr-defined] from weakref import finalize # ....................{ MAKERS }.................... def make_func( # Mandatory arguments. func_name: str, func_code: str, # Optional arguments. func_globals: Optional[LexicalScope] = None, func_locals: Optional[LexicalScope] = None, func_doc: Optional[str] = None, func_label: Optional[str] = None, func_wrapped: Optional[Callable] = None, is_debug: bool = False, exception_cls: TypeException = _BeartypeUtilCallableException, ) -> Callable: ''' Dynamically create and return a new function with the passed name declared by the passed code snippet and internally accessing the passed dictionaries of globally and locally scoped variables. Parameters ---------- func_name : str Name of the function to be created. func_code : str Code snippet defining this function, including both this function's signature prefixed by zero or more decorations *and* body. **This snippet must be unindented.** If this snippet is indented, this factory raises a syntax error. func_globals : dict[str, Any] | None Dictionary mapping from the name to value of each **globally scoped attribute** (i.e., internally referenced in the body of the function declared by this code snippet). Defaults to the empty dictionary. func_locals : dict[str, Any] | None Dictionary mapping from the name to value of each **locally scoped attribute** (i.e., internally referenced either in the signature of the function declared by this code snippet *or* as decorators decorating that function). **Note that this factory necessarily modifies the contents of this dictionary.** Defaults to the empty dictionary. func_doc : str | None Human-readable docstring documenting this function. Defaults to :data:`None`, in which case this function remains undocumented. func_label : str | None Human-readable label describing this function for error-handling purposes. Defaults to :data:`None`, in which case this label effectively defaults to ``"{func_name}()"``. func_wrapped : Callable | None Callable wrapped by the function to be created. If non-:data:`None`, special dunder attributes will be propagated (i.e., copied) from this wrapped callable into this created function; these include: * ``__name__``, this function's unqualified name. * ``__doc__``, this function's docstring. * ``__module__``, the fully-qualified name of this function's module. Defaults to :data:`None`. is_debug : bool, optional :data:`True` only if this function is being debugged. If :data:`True`, then the definition (including signature and body) of this function is: * Printed to standard output. * Cached with the standard :mod:`linecache` module under a fake filename uniquely synthesized by this factory for this function. External callers may then subsequently access the definition of this function from that module as: .. code-block:: python from linecache import cache as linecache_cache func_source = linecache_cache[func.__code__.co_filename] Defaults to :data:`False`. exception_cls : Type[Exception], optional Type of exception to raise in the event of a fatal error. Defaults to :exc:`._BeartypeUtilCallableException`. Returns ------- Callable Function with this name declared by this snippet. Raises ------ exception_cls If either: * ``func_locals`` contains a key whose value is that of ``func_name``, implying the caller already declared a local attribute whose name collides with that of this function. * This code snippet is syntactically invalid. * This code snippet is syntactically valid but fails to declare a function with this name. ''' # ..................{ VALIDATION ~ pre }.................. assert isinstance(func_name, str), f'{repr(func_name)} not string.' assert isinstance(func_code, str), f'{repr(func_code)} not string.' assert isinstance(is_debug, bool), f'{repr(is_debug)} not bool.' assert func_name, 'Parameter "func_name" empty.' assert func_code, 'Parameter "func_code" empty.' # Default all unpassed parameters. if func_globals is None: func_globals = {} if func_locals is None: func_locals = {} if func_label is None: func_label = f'{func_name}()' assert isinstance(func_globals, dict), ( f'{repr(func_globals)} not dictionary.') assert isinstance(func_locals, dict), ( f'{repr(func_locals)} not dictionary.') assert isinstance(func_label, str), f'{repr(func_label)} not string.' # If that function's name is already in this local scope, the caller # already declared a local attribute whose name collides with that # function's. In this case, raise an exception for safety. if func_name in func_locals: raise exception_cls( f'{func_label} already defined by caller locals:\n' f'{repr(func_locals)}' ) # Else, that function's name is *NOT* already in this local scope. # ..................{ STARTUP ~ filename }.................. # Note that this logic is intentionally performed *BEFORE* munging the # "func_code" string in-place below, as this logic depends upon the unique # ID of that string. Reassignment obliterates that uniqueness. # Arbitrary object uniquely associated with this function. func_filename_object: object = None # Possibly fully-qualified name of an arbitrary object uniquely associated # with this function. func_filename_name: str = None # type: ignore[assignment] # If this function is a high-level wrapper wrapping a lower-level # wrappee, uniquify the subsequent filename against this wrappee. This # wrappee's fully-qualified name guarantees the uniqueness of this # filename. Ergo, this is the ideal case. if func_wrapped: func_filename_name = get_object_name(func_wrapped) func_filename_object = func_wrapped # Else, this function is *NOT* such a wrapper. In this less ideal case, # fallback to a poor man's uniquification against the unqualified name and # code string underlying this function. else: func_filename_name = func_name func_filename_object = func_code # Fake in-memory filename hopefully unique to this function. # Optimistically, a fully-qualified object name and ID *SHOULD* be unique # for the lifetime of the active Python process. # # Specifically, this filename guarantees the uniqueness of the 3-tuple # ``({func_filename}, {func_file_line_number}, {func_name})`` commonly # leveraged by profilers (e.g., "cProfile") to identify arbitrary callables, # where: # * `{func_filename}` is this filename (e.g., # `""`). # * `{func_file_line_number}`, is *ALWAYS* 0 and thus *NEVER* unique. # * `{func_name}`, is identical to that of the decorated callable and also # thus *NEVER* unique. # # Ergo, uniquifying this filename is the *ONLY* means of uniquifying # metadata identifying this wrapper function via runtime inspection. Failure # to do so reduces tracebacks induced by exceptions raised by this wrapper # to non-human-readability, which is less than ideal: e.g., # # Traceback (most recent call last): # File "/home/leycec/py/betsee/betsee/gui/simconf/stack/widget/mixin/guisimconfwdgeditscalar.py", line 313, in _set_alias_to_widget_value_if_sim_conf_open # widget=self, value_old=self._widget_value_last) # File "", line 25, in func_beartyped # File "/home/leycec/py/betsee/betsee/gui/simconf/stack/widget/mixin/guisimconfwdgeditscalar.py", line 409, in __init__ # *args, widget=widget, synopsis=widget.undo_synopsis, **kwargs) # File "", line 13, in func_beartyped # # See the final traceback line, which is effectively useless. func_filename = ( f'<@beartype({func_filename_name}) at {id(func_filename_object):#x}>') # ..................{ STARTUP ~ code }.................. # Code snippet defining this function, stripped of all leading and trailing # whitespace to improve both readability and disambiguity. Since this # whitespace is safely ignorable, the original snippet is safely # replaceable by this stripped snippet. func_code = func_code.strip() # If debugging this function, print the definition of this function. if is_debug: print(f'{number_str_lines(func_code)}') # else: # print('!!!!!!!!!PRINTING NOTHING!!!!!!!!!!!') # Else, leave that definition obscured by the voracious bitbuckets of time. # ..................{ CREATION }.................. # Attempt to... try: # Call the more verbose and obfuscatory compile() builtin instead of # simply calling "exec(func_code, func_globals, func_locals)". Why? # Because the exec() builtin does *NOT* provide a means to set this # function's "__code__.co_filename" read-only attribute. # # Note that we could pass "single" instead of "exec" here if we were # willing to constrain the passed "func_code" to a single statement. In # casual testing, there is very little performance difference between # the two (with an imperceptibly slight edge going to "single"). func_code_compiled = compile(func_code, func_filename, 'exec') assert func_name not in func_locals # Define that function. For obscure and likely uninteresting reasons, # Python fails to capture that function (i.e., expose that function to # this factory) when the locals() dictionary is passed; instead, a # unique local dictionary *MUST* be passed. exec(func_code_compiled, func_globals, func_locals) # If doing so fails for *ANY* reason whatsoever, wrap that low-level # exception with a higher-level exception exhibiting the exact issue. Doing # so enables users to submit meaningful issues to our tracker. except Exception as exception: # Raise an exception suffixed by that function's declaration such that # each line of that declaration is prefixed by that line's number. This # renders "SyntaxError" exceptions referencing arbitrary line numbers # human-readable: e.g., # File "", line 56 # if not ( # ^ # SyntaxError: invalid syntax raise exception_cls( f'{func_label} unparseable, as @beartype generated ' f'invalid code raising "{label_exception(exception)}":\n\n' f'{number_str_lines(func_code)}' ) from exception # ..................{ VALIDATION ~ post }.................. # If that function's name is *NOT* in this local scope, this code snippet # failed to declare that function. In this case, raise an exception. if func_name not in func_locals: raise exception_cls( f'{func_label} undefined by code snippet:\n\n' f'{number_str_lines(func_code)}' ) # Else, that function's name is in this local scope. # Function declared by this code snippet. func: Callable = func_locals[func_name] # type: ignore[assignment] # If that function is uncallable, raise an exception. if not callable(func): raise exception_cls( f'{func_label} defined by code snippet uncallable:\n\n' f'{number_str_lines(func_code)}' ) # Else, that function is callable. # # If that function is a wrapper wrapping a wrappee callable, propagate # dunder attributes from that wrappee onto this wrapper. elif func_wrapped is not None: assert callable(func_wrapped), f'{repr(func_wrapped)} uncallable.' update_wrapper(wrapper=func, wrapped=func_wrapped) # Else, that function is *NOT* such a wrapper. # ..................{ CLEANUP }.................. # If that function is documented... # # Note that function is intentionally documented *AFTER* propagating dunder # attributes to enable callers to explicitly overwrite documentation # propagated from that wrappee onto this wrapper. if func_doc is not None: assert isinstance(func_doc, str), f'{repr(func_doc)} not string.' assert func_doc, '"func_doc" empty.' # Document that function. func.__doc__ = func_doc # Else, that function is undocumented. # If debugging this function... if is_debug: # Render this function debuggable (e.g., via the standard "pdb" module) # by exposing this function's definition to the standard "linecache" # module under the fake filename synthesized above. # # Technically, we *COULD* slightly improve the uniquification of this # filename for the uncommon edge case when this function does *NOT* # wrap a lower-level wrappee (e.g., by embedding the ID of this # function that now exists rather than an arbitrary string object). # Pragmatically, doing so would prevent external callers from trivially # retrieving this function's definition from "linecache". Why? Because # reusing the "func_filename" string embedded in this function as # "func.__code__.co_filename" trivializes this lookup for callers. # Ultimately, sane lookup >>> slightly uniquer filename. linecache_cache[func_filename] = ( # type: ignore[assignment] len(func_code), # type: ignore[assignment] # Y u gotta b diff'rnt Python 3.7? WHY?! None, # usually mtime for determining when to discard files, but # providing None instructs linecache to no-op (never discard) func_code.splitlines(keepends=True), func_filename, ) # Define and register a cleanup callback removing that function's # linecache entry called if and when that function is # garbage-collected. def _remove_func_linecache_entry(): linecache_cache.pop(func_filename, None) finalize(func, _remove_func_linecache_entry) # Else, this function is *NOT* being debugged. # Return that function. return func # ....................{ COPIERS }.................... #FIXME: Consider excising. Although awesome, this is no longer needed. # from beartype._util.func.utilfunctest import die_unless_func_python # from types import FunctionType # def copy_func_shallow( # # Mandatory arguments. # func: Callable, # # # Optional arguments. # exception_cls: Type[Exception] = _BeartypeUtilCallableException, # ) -> Callable: # ''' # Create and return a new shallow copy of the passed callable. # # Specifically, this function creates and returns a new function sharing with # the passed callable the same: # # * Underlying code object (i.e., ``func.__code__``). # * Unqualified and fully-qualified names (i.e., ``func.__name__`` and # ``func.__qualname__``). # * Docstring (i.e., ``func.__doc__``). # * Type hints (i.e., ``func.__annotations__``). # * Global scope (i.e., ``func.__globals__``). # * Fully-qualified module name (i.e., ``func.__module__``). # * Default values of optional parameters (i.e., ``f.__defaults__`` and # ``f.__kwdefaults__``). # * Closure-specific cell variables (i.e., ``f.__closure__``). # * Custom public and private attributes. # # Parameters # ---------- # func : Callable # Callable to be copied. # exception_cls : type, optional # Type of exception to raise in the event of a fatal error. Defaults to # :exc:`_BeartypeUtilCallableException`. # # Returns # ---------- # Callable # Function shallowly copied from the passed callable. # # Raises # ---------- # exception_cls # If the passed callable is *not* pure-Python. # # See Also # ---------- # https://stackoverflow.com/a/30714299/2809027 # StackOverflow answer strongly inspiring this implementation. # ''' # # # If the passed callable is *NOT* pure-Python, raise an exception. # die_unless_func_python(func=func, exception_cls=exception_cls) # # # Function shallowly copied from the passed callable. # # # # Note that *ALL* pure-Python callables are guaranteed to define the # # following dunder attributes. # func_copy = FunctionType( # func.__code__, # func.__globals__, # type: ignore[attr-defined] # func.__name__, # func.__defaults__, # type: ignore[attr-defined] # func.__closure__, # type: ignore[attr-defined] # ) # # # Shallowly copy all remaining dunder attributes from the original callable # # onto this copy *NOT* already copied by the FunctionType.__init__() method # # called above. # # # # Note that *ALL* pure-Python callables are guaranteed to define the # # following dunder attributes. # func_copy.__annotations__ = func.__annotations__ # func_copy.__doc__ = func.__doc__ # func_copy.__kwdefaults__ = func.__kwdefaults__ # type: ignore[attr-defined] # func_copy.__module__ = func.__module__ # func_copy.__qualname__ = func.__qualname__ # # # Shallowly copy all custom attributes (i.e., non-dunder attributes # # explicitly set by the caller) from the original callable onto this copy. # func_copy.__dict__.update(func.__dict__) # # print(f'func.__dict__: {func.__dict__}') # # print(f'func_copy.__dict__: {func_copy.__dict__}') # # # Return this copy. # return func_copy beartype-0.18.5/beartype/_util/func/utilfuncscope.py000066400000000000000000000740031461113517100225430ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **callable scope** utilities (i.e., functions handling the possibly nested lexical scopes enclosing arbitrary callables). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import ( _BeartypeUtilCallableScopeException, _BeartypeUtilCallableScopeNotFoundException, ) from beartype.typing import ( Any, Optional, ) from beartype._util.utilobject import get_object_basename_scoped from beartype._data.hint.datahinttyping import ( LexicalScope, TypeException, ) from beartype._data.kind.datakinddict import DICT_EMPTY from collections.abc import Callable from types import CodeType # ....................{ GETTERS }.................... #FIXME: Unit test us up, please. def get_func_globals( # Mandatory parameters. func: Callable, # Optional parameters. exception_cls: TypeException = _BeartypeUtilCallableScopeException, ) -> LexicalScope: ''' **Global scope** (i.e., a dictionary mapping from the name to value of each globally scoped attribute declared by the module transitively declaring the passed pure-Python callable) for this callable. This getter transparently supports **wrapper callables** (i.e., higher-level callables whose identifying metadata was propagated from other lowel-level callables at either decoration time via the :func:`functools.wraps` decorator *or* after declaration via the :func:`functools.update_wrapper` function). Note that the primary (and indeed only, at the moment) use case for this getter is :pep:`563`-compliant resolution of postponed annotations. Parameters ---------- func : Callable Callable to be inspected. func_stack_frames_ignore : int, optional Number of frames on the call stack to be ignored (i.e., silently incremented past), such that the next non-ignored frame following the last ignored frame is the parent callable or module directly declaring the passed callable. Defaults to 0. exception_cls : Type[Exception], optional Type of exception to be raised in the event of a fatal error. Defaults to :class:`._BeartypeUtilCallableScopeException`. Returns ------- Dict[str, Any] Global scope for this callable. Raises ------ exception_cls If this callable is a wrapper wrapping a C-based rather than pure-Python wrappee callable. ''' assert callable(func), f'{repr(func)} not callable.' # Avoid circular import dependencies. from beartype._util.func.utilfunctest import die_unless_func_python from beartype._util.func.utilfuncwrap import unwrap_func_all_isomorphic # If this callable is *NOT* pure-Python, raise an exception. C-based # callables do *NOT* define the "__globals__" dunder attribute. die_unless_func_python(func=func, exception_cls=exception_cls) # Else, this callable is pure-Python. # Lowest-level wrappee callable wrapped by this wrapper callable. func_wrappee = unwrap_func_all_isomorphic(func) # Dictionary mapping from the name to value of each locally scoped # attribute accessible to this wrappee callable to be returned. # # Note that we intentionally do *NOT* return the global scope for this # wrapper callable, as wrappers are typically defined in different modules # (and thus different global scopes) by different module authors. return func_wrappee.__globals__ # type: ignore[attr-defined] def get_func_locals( # Mandatory parameters. func: Callable, # Optional parameters. func_scope_names_ignore: int = 0, func_stack_frames_ignore: int = 0, exception_cls: TypeException = _BeartypeUtilCallableScopeException, ) -> LexicalScope: ''' **Local scope** for the passed callable. This getter returns either: * If that callable is **nested** (i.e., is a method *or* is a non-method callable declared in the body of another callable), a dictionary mapping from the name to value of each **locally scoped attribute** (i.e., local attribute declared by a parent callable transitively declaring that callable) accessible to that callable. * Else, the empty dictionary otherwise (i.e., if that callable is a function directly declared by a module). This getter transparently supports methods, which in Python are lexically nested in the scope encapsulating all previously declared **class variables** (i.e., variables declared from class scope and thus accessible as type hints when annotating the methods of that class). When declaring a class, Python creates a stack frame for the declaration of that class whose local scope is the set of all class-scoped attributes declared in the body of that class (including class variables, class methods, static methods, and instance methods). When passed any method, this getter finds and returns that local scope. When passed the ``MuhClass.muh_method` method declared by the following example, for example, this getter returns the local scope containing the key ``'muh_class_var'`` with value ``int``: .. code-block:: python >>> from typing import ClassVar >>> class MuhClass(object): ... # Class variable declared in class scope. ... muh_class_var: ClassVar[type] = int ... # Instance method annotated by this class variable. ... def muh_method(self) -> muh_class_var: return 42 However, note that this getter's transparent support for methods does *not* extend to methods of the currently decorated class. Why? Because that class has yet to be declared and thus added to the call stack. Caveats ------- **This high-level getter requires the private low-level** :func:`sys._getframe` **getter.** If that getter is undefined, this getter unconditionally treats the passed callable as module-scoped by returning the empty dictionary rather than raising an exception. Since all standard Python implementations (e.g., CPython, PyPy) define that getter, this should typically *not* be a real-world concern. **This high-level getter is inefficient and should thus only be called if absolutely necessary.** Specifically, deciding the local scope for any callable is an ``O(k)`` operation for ``k`` the distance in call stack frames from the call of the current function to the call of the top-most parent scope transitively declaring the passed callable in its submodule. Ergo, this decision problem should be deferred as long as feasible to minimize space and time consumption. Parameters ---------- func : Callable Callable to be inspected. func_scope_names_ignore : int, optional Number of parent lexical scopes in the fully-qualified name of that callable to be ignored (i.e., silently incremented past), such that the next non-ignored lexical scope preceding the first ignored lexical scope is that of the parent callable or module directly declaring the passed callable. This parameter is typically used to ignore the parent lexical scopes of parent classes lexically nesting that callable that have yet to be fully declared and thus encapsulated by stack frames on the call stack (e.g., due to being currently decorated by :func:`beartype.beartype`). See the :mod:`beartype._decor._pep.pep563` submodule for the standard use case. Defaults to 0. func_stack_frames_ignore : int, optional Number of frames on the call stack to be ignored (i.e., silently incremented past), such that the next non-ignored frame following the last ignored frame is the parent callable or module directly declaring the passed callable. Defaults to 0. exception_cls : Type[Exception], optional Type of exception to be raised in the event of a fatal error. Defaults to :class:`._BeartypeUtilCallableScopeException`. Returns ------- LexicalScope Local scope for this callable. Raises ------ exception_cls If the next non-ignored frame following the last ignored frame is *not* the parent callable or module directly declaring the passed callable. _BeartypeUtilCallableScopeNotFoundException If this lexical scope cannot be found (i.e., if this getter found the lexical scope of the module declaring the passed callable *before* that of the parent callable or class declaring that callable), enabling callers to identify this common edge case. ''' assert callable(func), f'{repr(func)} not callable.' assert isinstance(func_scope_names_ignore, int), ( f'{func_scope_names_ignore} not integer.') assert func_scope_names_ignore >= 0, ( f'{func_scope_names_ignore} negative.') assert isinstance(func_stack_frames_ignore, int), ( f'{func_stack_frames_ignore} not integer.') assert func_stack_frames_ignore >= 0, ( f'{func_stack_frames_ignore} negative.') # print(f'\n--------- Capturing nested {func.__qualname__}() local scope...') # ..................{ IMPORTS }.................. # Avoid circular import dependencies. from beartype._data.func.datafunccodeobj import FUNC_CODEOBJ_NAME_MODULE from beartype._util.func.utilfunccodeobj import get_func_codeobj_or_none from beartype._util.func.utilfuncframe import iter_frames from beartype._util.func.utilfunctest import is_func_nested # ..................{ NOOP }.................. # Fully-qualified name of the module declaring the passed callable if that # callable was physically declared by an on-disk module *OR* "None" # otherwise (i.e., if that callable was dynamically declared in-memory). func_module_name = func.__module__ # Note that we intentionally return the local scope for this wrapper rather # than wrappee callable, as local scope can *ONLY* be obtained by # dynamically inspecting local attributes bound to call frames on the # current call stack. However, this wrapper was called at a higher call # frame than this wrappee. All local attributes declared within the body of # this wrapper callable are irrelevant to this wrappee callable, as Python # syntactically parsed the latter at a later time than the former. If we # returned the local scope for this wrappee rather than wrapper callable, # we would erroneously return local attributes that this wrappee callable # originally had no lexical access to. That's bad. So, we don't do that. # # If either... if ( # The passed callable is dynamically declared in-memory... func_module_name is None or # The passed callable is module-scoped rather than nested *OR*... not is_func_nested(func) # Then silently reduce to a noop by treating this nested callable as # module-scoped by preserving "func_locals" as the empty dictionary. ): return DICT_EMPTY # Else, all of the following constraints hold: # * The passed callable is physically declared on-disk. # * The passed callable is nested. # ..................{ LOCALS ~ scope }.................. # Local scope of the passed callable to be returned. func_scope: LexicalScope = {} # ..................{ LOCALS ~ scope : name }.................. # Unqualified name of this nested callable. func_name_unqualified = func.__name__ # Fully-qualified name of this nested callable. If this nested callable is # a non-method, this name contains one or more meaningless placeholders # "" -- each identifying one parent callable lexically containing # this nested callable: e.g., # >>> def muh_func(): # ... def muh_closure(): pass # ... return muh_closure() # >>> muh_func().__qualname__ # 'muh_func..muh_closure' func_name_qualified = get_object_basename_scoped(func) # Non-empty list of the unqualified names of all parent callables lexically # containing that nested callable (including that nested callable itself). # # Note that: # * The set of all callables embodied by the current runtime call stack is # a (usually proper) superset of the set of all callables embodied by the # lexical scopes encapsulating this nested callable. Ergo: # * Some stack frames have no corresponding lexical scopes (e.g., stack # frames embodying callables defined by different modules). # * *ALL* lexical scopes have a corresponding stack frame. # * Stack frames are only efficiently accessible relative to the initial # stack frame embodying this nested callable, which resides at the end of # the call stack. This implies we *MUST* iteratively search up the call # stack for frames with relevant lexical scopes and ignore intervening # frames with irrelevant lexical scopes, starting at the stack top (end). func_scope_names = func_name_qualified.rsplit(sep='.') # Number of lexical scopes encapsulating that callable. func_scope_names_len = len(func_scope_names) # If that nested callable is *NOT* encapsulated by at least two lexical # scopes identifying at least that nested callable and the parent callable # or class declaring that nested callable, raise an exception. # # You are probably now contemplating to yourself in the special darkness of # your own personal computer cave: "But @leycec, isn't this condition # *ALWAYS* the case? The above `not is_func_nested()` check already ignored # non-nested callables." # # Allow me to now explicate. By the check above, that callable is nested... # By the check below, however, only one lexical scope encapsulates that # callable. This is not a contradiction. This is just a malicious caller. # The get_object_basename_scoped() getter called above silently removed all # "." placeholders from this list of lexical scopes, because those # "." placeholders convey no meaningful semantics. But the # is_func_nested() tester detects nested callables by searching for those # "." placeholders. It follows that the caller triggered this # condition by maliciously renaming the "__qualname__" dunder attribute of # the passed callable to be erroneously prefixed by "". Curiously, # Python permits such manhandling: e.g., # # Python thinks this is fine. # >>> def muh_func(): pass # >>> muh_func.__qualname__ = '.muh_func' # <-- curse you! if func_scope_names_len < 2: raise exception_cls( f'{func_name_unqualified}() fully-qualified name ' f'{func.__qualname__}() invalid (e.g., placeholder substring ' f'"" not preceded by parent callable name).' ) # Else, that nested callable is encapsulated by at least two lexical # scopes identifying at least that nested callable and the parent callable # or class declaring that nested callable. # # If the unqualified basename of the last parent callable lexically # containing the passed callable is *NOT* that callable itself, the caller # maliciously renamed one but *NOT* both of "__qualname__" and "__name__". # In this case, raise an exception. Again, Python permits this. *sigh* elif func_scope_names[-1] != func_name_unqualified: raise exception_cls( f'{func_name_unqualified}() fully-qualified name ' f'{func.__qualname__}() invalid (i.e., last lexical scope ' f'"{func_scope_names[-1]}" != unqualified name ' f'"{func_name_unqualified}").' ) # Else, the unqualified basename of the last parent callable lexically # containing the passed callable is that callable itself. # 1-based negative index of the unqualified basename of the parent callable # or module directly lexically containing the passed callable in the list of # all unqualified basenames encapsulating that callable. By the above # validation, this index is guaranteed to begin at the second-to-last # basename in this list. func_scope_names_index = -2 - func_scope_names_ignore # Number of unignorable lexical scopes encapsulating that callable, # magically adding 1 to account for the fact that "func_scope_names_index" # is a 1-based negative index rather than 0-based positive index. func_scope_names_search_len = ( func_scope_names_len + func_scope_names_index + 1) # If exactly *ZERO* unignorable lexical scopes encapsulate that callable, # all lexical scopes encapsulating that callable are exactly ignorable, # implying that there is *NO* parent lexical scope to search for. In this # case, silently reduce to a noop by returning the empty dictionary. if func_scope_names_search_len == 0: return DICT_EMPTY # If a *NEGATIVE* number of unignorable lexical scopes encapsulate that # callable, the caller erroneously insists that there exist more ignorable # lexical scopes encapsulating that callable than there actually exist # lexical scopes encapsulating that callable. The caller is profoundly # mistaken. Whereas the prior branch is a non-erroneous condition that # commonly occurs, this current branch is an erroneous condition that should # *NEVER* occur. In this case... elif func_scope_names_search_len < 0: # Number of parent lexical scopes containing that callable. func_scope_parents_len = func_scope_names_len - 1 # Raise an exception. raise exception_cls( f'Callable name "{func_name_qualified}" contains only ' f'{func_scope_parents_len} parent lexical scope(s) but ' f'"func_scope_names_ignore" parameter ignores ' f'{func_scope_names_ignore} parent lexical scope(s), leaving ' f'{func_scope_names_search_len} parent lexical scope(s) to be ' f'searched for {func_name_qualified}() locals.' ) # Else, there are one or more unignorable lexical scopes to be searched. # Unqualified basename of the parent callable or module directly lexically # containing the passed callable. # # Note that that the parent callable's local runtime scope transitively # contains *ALL* local variables accessible to this nested callable # (including the local variables directly contained in the body of that # parent callable as well as the local variables directly contained in the # bodies of all parent callables of that callable in the same lexical # scope). Since that parent callable's local runtime scope is exactly the # dictionary to be returned, iteration below searches up the runtime call # stack for a stack frame embodying that parent callable and no further. func_scope_name = func_scope_names[func_scope_names_index] # print(f'Searching for parent {func_scope_name}() local scope...') # ..................{ LOCALS ~ frame }.................. # Code object underlying the parent callable associated with the current # stack frame if that callable is pure-Python *OR* "None". func_frame_codeobj: Optional[CodeType] = None # Fully-qualified name of that callable's module. func_frame_module_name = '' # Unqualified name of that callable. func_frame_name = '' # ..................{ SEARCH }.................. # While at least one frame remains on the call stack, iteratively search up # the call stack for a stack frame embodying the parent callable directly # declaring this nested callable, whereupon that parent callable's local # runtime scope is returned as is. # # Note this also implicitly skips past all other decorators applied *AFTER* # @beartype (and thus residing lexically above @beartype) in caller code to # this nested callable: e.g., # @the_way_of_kings <--- skipped past # @words_of_radiance <--- skipped past # @oathbringer <--- skipped past # @rhythm_of_war <--- skipped past # @beartype # def the_stormlight_archive(bruh: str) -> str: # return bruh for func_frame in iter_frames( # 0-based index of the first non-ignored frame following the last # ignored frame, ignoring an additional frame embodying the current # call to this getter. func_stack_frames_ignore=func_stack_frames_ignore + 1, ): # Code object underlying this frame's scope if that scope is # pure-Python *OR* "None" otherwise. func_frame_codeobj = get_func_codeobj_or_none(func_frame) # If this code object does *NOT* exist, this scope is C-based. In this # case, silently ignore this scope and proceed to the next frame. if func_frame_codeobj is None: continue # Else, this code object exists, implying this scope to be pure-Python. # Fully-qualified name of that scope's module. func_frame_module_name = func_frame.f_globals['__name__'] # Unqualified name of that scope. func_frame_name = func_frame_codeobj.co_name # print(f'{func_frame_name}() locals: {repr(func_frame.f_locals)}') # If that scope is the placeholder string assigned by the active Python # interpreter to all scopes encapsulating the top-most lexical scope of # a module in the current call stack, this search has just crossed a # module boundary and is thus no longer searching within the module # declaring this nested callable and has thus failed to find the # lexical scope of the parent declaring this nested callable. Why? # Because that scope *MUST* necessarily be in the same module as that # of this nested callable. In this case, raise an exception. if func_frame_name == FUNC_CODEOBJ_NAME_MODULE: raise _BeartypeUtilCallableScopeNotFoundException( f'{func_name_qualified}() parent lexical scope ' f'"{func_scope_name}" not found on call stack.' ) # Else, that scope is *NOT* a module. # # If... elif ( # That callable's name is that of the current lexical scope to be # found *AND*... func_frame_name == func_scope_name and # That callable's module is that of this nested callable's and thus # resides in the same lexical scope... func_frame_module_name == func_module_name ): # Then that callable embodies the lexical scope to be found. In this # case, that callable is the parent callable directly declaring this # nested callable. # # Note that this scope *CANNOT* be validated to declare this nested # callable. Why? Because this getter function is called by the # @beartype decorator when decorating this nested callable, which has # yet to be declared until @beartype creates and returns a new wrapper # function and is thus unavailable from this scope: e.g., # # This conditional *ALWAYS* raises an exception, because this # # nested callable has yet to be declared. # if func_name not in func_locals: # raise exception_cls( # f'{func_label} not declared by lexically scoped parent ' # f'callable(s) that declared local variables:\n{repr(func_locals)}' # ) # # Ergo, we have *NO* alternative but to blindly assume the above # algorithm correctly collected this scope, which we only do because we # have exhaustively tested this with *ALL* edge cases. # print(f'{func_frame_name}() locals: {repr(func_frame.f_locals)}') # Local scope of the passed callable. Since this nested callable is # directly declared in the body of this parent callable, the local # scope of this nested callable is *EXACTLY* the local scope of the # body of this parent callable. Well, isn't that special? func_scope = func_frame.f_locals # Halt iteration. break # Else, that callable does *NOT* embody the current lexical scope to be # found. In this case, silently ignore that callable and proceed to the # next frame in the call stack. # Return the local scope of the passed callable. return func_scope # ....................{ ADDERS }.................... def add_func_scope_attr( # Mandatory parameters. attr: Any, func_scope: LexicalScope, # Optional parameters. exception_prefix: str = 'Globally or locally scoped attribute ', ) -> str: ''' Add a new **scoped attribute** (i.e., new key-value pair of the passed dictionary mapping from the name to value of each globally or locally scoped attribute externally accessed elsewhere, whose key is a machine-readable name internally generated by this function to uniquely refer to the passed object and whose value is that object) to the passed scope *and* return that name. Parameters ---------- attr : Any Arbitrary object to be added to this scope. func_scope : LexicalScope Local or global scope to add this object to. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- str Name of the passed object in this scope generated by this function. Raises ------ _BeartypeUtilCallableScopeException If an attribute with the same name as that internally generated by this adder but having a different value already exists in this scope. This adder uniquifies names by object identifier and should thus *never* generate name collisions. This exception is thus intentionally raised as a private rather than public exception. ''' assert isinstance(func_scope, dict), f'{repr(func_scope)} not dictionary.' assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') # Possibly negative integer uniquely identifying the new attribute referring # to this object in this scope. attr_id = id(attr) # Name of the new attribute referring to this object in this scope, defined # as either... attr_name = ( # If this integer is positive, embed this positive integer into this # name as is. f'{_ATTR_NAME_PREFIX_ID_POSITIVE}{attr_id}' if attr_id >= 0 else # Else, this integer is negative. In this case, instead embed the # absolute value of this negative integer rather than this negative # integer itself. The unary prefix "-" is prohibited in Python # identifiers. Since this integer is subsequently embedded in a Python # identifier, this integer *MUST* be coerced from a negative to # non-negative integer first. # # Note that this edge case *ONLY* applies to PyPy. Specifically, under: # * CPython, this integer is guaranteed to be a memory address and thus # non-negative (i.e., "id(attr) >= 0" for all possible attributes). # * PyPy, this integer is typically (but *NOT* necessarily) a 0-based # index into an internal object array and thus also non-negative. # Unfortunately, this heuristic is *NOT* a universal guarantee. In a # small handful of edge cases (for presumably exotic objects of # unknown origin), this integer is negative under PyPy. This makes # testing non-deterministic and thus infeasible. # # This must be what it feels like when code cries. f'{_ATTR_NAME_PREFIX_ID_NEGATIVE}{-attr_id}' # pragma: no cover ) # If an attribute with the same name but differing value already exists in # this scope, raise an exception. if func_scope.get(attr_name, attr) is not attr: raise _BeartypeUtilCallableScopeException( f'{exception_prefix}"{attr_name}" already exists with ' f'differing value:\n' f'~~~~[ NEW VALUE ]~~~~\n{repr(attr)}\n' f'~~~~[ OLD VALUE ]~~~~\n{repr(func_scope[attr_name])}' ) # Else, either no attribute with this name exists in this scope *OR* an # attribute with this name and value already exists in this scope. # Refer to the passed object in this scope with this name. func_scope[attr_name] = attr # Return this name. return attr_name # ....................{ PRIVATE }.................... _ATTR_NAME_PREFIX_ID_POSITIVE = '__beartype_object_' ''' Arbitrary substring prefixing names dynamically synthesized by the :func:`.add_func_scope_attr` function for attributes whose **object ids** (i.e., the integers returned by the :func:`id` builtin) are positive. See Also -------- :data:`._ATTR_NAME_PREFIX_ID_NEGATIVE` Further details. ''' # NegaBeartype: I challenge you! _ATTR_NAME_PREFIX_ID_NEGATIVE = ( f'{_ATTR_NAME_PREFIX_ID_POSITIVE}oh_pypy_you_sweet_summer_child_') ''' Arbitrary substring prefixing names dynamically synthesized by the :func:`.add_func_scope_attr` function for attributes whose **object ids** (i.e., the integers returned by the :func:`id` builtin) are negative. That function coerces negative into positive attribute IDs. Doing so renders the former suitable for embedding in attribute names that are valid Python identifiers. That's good. Doing so naively, however, would invite name collisions between negative and positive attribute IDs whose absolute values are equal (e.g., ``|-42| == |42|``). To avoid this, the names of attributes whose IDs are negative are prefixed by a different substring than those of attributes whose IDs are positive. It's complicated. Did our hand-waving not convince you!? ''' beartype-0.18.5/beartype/_util/func/utilfunctest.py000066400000000000000000001047611461113517100224160ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **callable testers** (i.e., utility functions dynamically validating and inspecting various properties of passed callables). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilCallableException from beartype.typing import Any from beartype._cave._cavefast import MethodBoundInstanceOrClassType from beartype._data.hint.datahintfactory import TypeGuard from beartype._data.hint.datahinttyping import ( Codeobjable, TypeException, ) from beartype._util.cache.utilcachecall import callable_cached from beartype._util.func.arg.utilfuncargget import ( get_func_args_nonvariadic_len) from beartype._util.func.arg.utilfuncargtest import ( is_func_arg_variadic_positional, is_func_arg_variadic_keyword, ) from beartype._util.func.utilfunccodeobj import get_func_codeobj_or_none from collections.abc import Callable from inspect import ( CO_ASYNC_GENERATOR, CO_COROUTINE, CO_GENERATOR, ) # ....................{ CONSTANTS }.................... FUNC_NAME_LAMBDA = '' ''' Default name of all **pure-Python lambda functions** (i.e., function declared as a ``lambda`` expression embedded in a larger statement rather than as a full-blown ``def`` statement). Python initializes the names of *all* lambda functions to this lambda-specific placeholder string on lambda definition. Caveats ------- **Usage of this placeholder to differentiate lambda from non-lambda callables invites false positives in unlikely edge cases.** Technically, malicious third parties may externally change the name of any lambda function *after* defining that function. Pragmatically, no one sane should ever do such a horrible thing. While predictably absurd, this is also the only efficient (and thus sane) means of differentiating lambda from non-lambda callables. Alternatives require AST-based parsing, which comes with its own substantial caveats, concerns, edge cases, and false positives. If you must pick your poison, pick this one. ''' # ....................{ RAISERS }.................... def die_unless_func_python( # Mandatory parameters. func: Codeobjable, # Optional parameters. exception_cls: TypeException = _BeartypeUtilCallableException, exception_prefix: str = '', ) -> None: ''' Raise an exception if the passed callable is **C-based** (i.e., implemented in C as either a builtin bundled with the active Python interpreter *or* third-party C extension function). Equivalently, this validator raises an exception unless the passed function is **pure-Python** (i.e., implemented in Python as either a function or method). Parameters ---------- func : Codeobjable Callable to be inspected. exception_cls : TypeException, optional Type of exception to be raised. Defaults to :class:`._BeartypeUtilCallableException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Raises ------ exception_cls If the passed callable is C-based. See Also -------- :func:`.is_func_python` Further details. ''' # If that callable is *NOT* pure-Python, raise an exception. if not is_func_python(func): assert isinstance(exception_cls, type), ( f'{repr(exception_cls)} not class.') assert issubclass(exception_cls, Exception), ( f'{repr(exception_cls)} not exception subclass.') assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') # If that callable is uncallable, raise an appropriate exception. if not callable(func): raise exception_cls(f'{exception_prefix}{repr(func)} not callable.') # Else, that callable is callable. # Raise a human-readable exception. raise exception_cls( f'{exception_prefix}{repr(func)} not pure-Python function.') # Else, that callable is pure-Python. # ....................{ RAISERS ~ descriptors }.................... #FIXME: Unit test us up, please. def die_unless_func_boundmethod( # Mandatory parameters. func: Any, # Optional parameters. exception_cls: TypeException = _BeartypeUtilCallableException, exception_prefix: str = '', ) -> None: ''' Raise an exception unless the passed object is a **C-based bound instance method descriptor** callable implicitly instantiated and assigned on the instantiation of an object whose class declares an instance function (whose first parameter is typically named ``self``) as an instance variable of that object such that that callable unconditionally passes that object as the value of that first parameter on all calls to that callable). Parameters ---------- func : Any Object to be inspected. exception_cls : TypeException, optional Type of exception to be raised. Defaults to :class:`._BeartypeUtilCallableException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Raises ------ exception_cls If the passed object is *not* a bound method descriptor. See Also -------- :func:`.is_func_boundmethod` Further details. ''' # If this object is *NOT* a bound method descriptor, raise an exception. if not is_func_boundmethod(func): assert isinstance(exception_cls, type), ( f'{repr(exception_cls)} not class.') assert issubclass(exception_cls, Exception), ( f'{repr(exception_cls)} not exception subclass.') assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') # Raise a human-readable exception. raise exception_cls( f'{exception_prefix}{repr(func)} not ' f'C-based bound instance method descriptor.' ) # Else, this object is a bound method descriptor. def die_unless_func_classmethod( # Mandatory parameters. func: Any, # Optional parameters. exception_cls: TypeException = _BeartypeUtilCallableException, exception_prefix: str = '', ) -> None: ''' Raise an exception unless the passed object is a **C-based unbound class method descriptor** (i.e., method decorated by the builtin :class:`classmethod` decorator, yielding a non-callable instance of that :class:`classmethod` decorator class implemented in low-level C and accessible via the low-level :attr:`object.__dict__` dictionary rather than as class or instance attributes). Parameters ---------- func : Any Object to be inspected. exception_cls : TypeException, optional Type of exception to be raised. Defaults to :class:`._BeartypeUtilCallableException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Raises ------ exception_cls If the passed object is *not* a class method descriptor. See Also -------- :func:`.is_func_classmethod` Further details. ''' # If this object is *NOT* a class method descriptor, raise an exception. if not is_func_classmethod(func): assert isinstance(exception_cls, type), ( f'{repr(exception_cls)} not class.') assert issubclass(exception_cls, Exception), ( f'{repr(exception_cls)} not exception subclass.') assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') # Raise a human-readable exception. raise exception_cls( f'{exception_prefix}{repr(func)} not ' f'C-based unbound class method descriptor.' ) # Else, this object is a class method descriptor. def die_unless_func_property( # Mandatory parameters. func: Any, # Optional parameters. exception_cls: TypeException = _BeartypeUtilCallableException, exception_prefix: str = '', ) -> None: ''' Raise an exception unless the passed object is a **C-based unbound property method descriptor** (i.e., method decorated by the builtin :class:`property` decorator, yielding a non-callable instance of that :class:`property` decorator class implemented in low-level C and accessible as a class rather than instance attribute). Parameters ---------- func : Any Object to be inspected. exception_cls : TypeException, optional Type of exception to be raised. Defaults to :property:`_BeartypeUtilCallableException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Raises ------ exception_cls If the passed object is *not* a property method descriptor. See Also -------- :func:`.is_func_property` Further details. ''' # If this object is *NOT* a property method descriptor, raise an exception. if not is_func_property(func): assert isinstance(exception_cls, type), ( f'{repr(exception_cls)} not class.') assert issubclass(exception_cls, Exception), ( f'{repr(exception_cls)} not exception subclass.') assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') # Raise a human-readable exception. raise exception_cls( f'{exception_prefix}{repr(func)} not ' f'C-based unbound property method descriptor.' ) # Else, this object is a property method descriptor. def die_unless_func_staticmethod( # Mandatory parameters. func: Any, # Optional parameters. exception_cls: TypeException = _BeartypeUtilCallableException, exception_prefix: str = '', ) -> None: ''' Raise an exception unless the passed object is a **C-based unbound static method descriptor** (i.e., method decorated by the builtin :class:`staticmethod` decorator, yielding a non-callable instance of that :class:`staticmethod` decorator class implemented in low-level C and accessible via the low-level :attr:`object.__dict__` dictionary rather than as class or instance attributes). Parameters ---------- func : Any Object to be inspected. exception_cls : TypeException, optional Type of exception to be raised. Defaults to :static:`_BeartypeUtilCallableException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Raises ------ exception_cls If the passed object is *not* a static method descriptor. See Also -------- :func:`.is_func_staticmethod` Further details. ''' # If this object is *NOT* a static method descriptor, raise an exception. if not is_func_staticmethod(func): assert isinstance(exception_cls, type), ( f'{repr(exception_cls)} not class.') assert issubclass(exception_cls, Exception), ( f'{repr(exception_cls)} not exception subclass.') assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') # Raise a human-readable exception. raise exception_cls( f'{exception_prefix}{repr(func)} not ' f'C-based unbound static method descriptor.' ) # Else, this object is a static method descriptor. # ....................{ TESTERS }.................... def is_func_lambda(func: Any) -> TypeGuard[Callable]: ''' :data:`True` only if the passed object is a **pure-Python lambda function** (i.e., function declared as a ``lambda`` expression embedded in a larger statement rather than as a full-blown ``def`` statement). Parameters ---------- func : object Object to be inspected. Returns ------- bool :data:`True` only if this object is a pure-Python lambda function. ''' # Return true only if this both... return ( # This callable is pure-Python *AND*... is_func_python(func) and # This callable's name is the lambda-specific placeholder name # initially given by Python to *ALL* lambda functions. Technically, # this name may be externally changed by malicious third parties after # the declaration of this lambda. Pragmatically, no one sane would ever # do such a horrible thing. Would they!?!? # # While predictably absurd, this is also the only efficient (and thus # sane) means of differentiating lambda from non-lambda callables. # Alternatives require AST-based parsing, which comes with its own # substantial caveats, concerns, and edge cases. func.__name__ == FUNC_NAME_LAMBDA ) def is_func_python(func: object) -> TypeGuard[Callable]: ''' :data:`True` only if the passed object is a **pure-Python callable** (i.e., implemented in Python as either a function or method rather than in C as either a builtin bundled with the active Python interpreter *or* third-party C extension function). Parameters ---------- func : object Object to be inspected. Returns ------- bool :data:`True` only if this object is a pure-Python callable. ''' # Return true only if a pure-Python code object underlies this object. # C-based callables are associated with *NO* code objects. return get_func_codeobj_or_none(func) is not None # ....................{ TESTERS ~ descriptor }.................... #FIXME: Unit test us up, please. def is_func_boundmethod(func: Any) -> TypeGuard[MethodBoundInstanceOrClassType]: ''' :data:`True` only if the passed object is a **C-based bound instance method descriptor** (i.e., callable implicitly instantiated and assigned on the instantiation of an object whose class declares an instance function (whose first parameter is typically named ``self``) as an instance variable of that object such that that callable unconditionally passes that object as the value of that first parameter on all calls to that callable). Caveats ------- Instance method objects are *only* directly accessible as instance attributes. When accessed as either class attributes *or* via the low-level :attr:`object.__dict__` dictionary, instance methods are only functions (i.e., instances of the standard :class:`beartype.cave.FunctionType` type). Instance method objects are callable. Indeed, the callability of instance method objects is the entire point of instance method objects. Parameters ---------- func : object Object to be inspected. Returns ------- bool :data:`True` only if this object is a C-based bound instance method descriptor. ''' # Only the penitent one-liner shall pass. return isinstance(func, MethodBoundInstanceOrClassType) def is_func_classmethod(func: Any) -> TypeGuard[classmethod]: ''' :data:`True` only if the passed object is a **C-based unbound class method descriptor** (i.e., method decorated by the builtin :class:`classmethod` decorator, yielding a non-callable instance of that :class:`classmethod` decorator class implemented in low-level C and accessible via the low-level :attr:`object.__dict__` dictionary rather than as class or instance attributes). Caveats ------- Class method objects are *only* directly accessible via the low-level :attr:`object.__dict__` dictionary. When accessed as class or instance attributes, class methods reduce to instances of the standard :class:`MethodBoundInstanceOrClassType` type. Class method objects are *not* callable, as their implementations fail to define the ``__call__`` dunder method. Parameters ---------- func : object Object to be inspected. Returns ------- bool :data:`True` only if this object is a C-based unbound class method descriptor. ''' # Now you too have seen the pure light of the one-liner. return isinstance(func, classmethod) def is_func_property(func: Any) -> TypeGuard[property]: ''' :data:`True` only if the passed object is a **C-based unbound property method descriptor** (i.e., method decorated by the builtin :class:`property` decorator, yielding a non-callable instance of that :class:`property` decorator class implemented in low-level C and accessible as a class rather than instance attribute). Caveats ------- Property objects are directly accessible both as class attributes *and* via the low-level :attr:`object.__dict__` dictionary. Property objects are *not* accessible as instance attributes, for hopefully obvious reasons. Property objects are *not* callable, as their implementations fail to define the ``__call__`` dunder method. Parameters ---------- func : object Object to be inspected. Returns ------- bool :data:`True` only if this object is a pure-Python property. ''' # We rejoice in the shared delight of one-liners. return isinstance(func, property) def is_func_staticmethod(func: Any) -> TypeGuard[staticmethod]: ''' :data:`True` only if the passed object is a **C-based unbound static method descriptor** (i.e., method decorated by the builtin :class:`staticmethod` decorator, yielding a non-callable instance of that :class:`staticmethod` decorator class implemented in low-level C and accessible via the low-level :attr:`object.__dict__` dictionary rather than as class or instance attributes). Caveats ------- Static method objects are *only* directly accessible via the low-level :attr:`object.__dict__` dictionary. When accessed as class or instance attributes, static methods reduce to instances of the standard :class:`FunctionType` type. Static method objects are *not* callable, as their implementations fail to define the ``__call__`` dunder method. Parameters ---------- func : object Object to be inspected. Returns ------- bool :data:`True` only if this object is a pure-Python static method. ''' # Does the one-liner have Buddhahood? Mu. return isinstance(func, staticmethod) # ....................{ TESTERS ~ async }.................... def is_func_async(func: object) -> TypeGuard[Callable]: ''' :data:`True` only if the passed object is an **asynchronous callable factory** (i.e., awaitable factory callable implicitly creating and returning an awaitable object (i.e., satisfying the :class:`collections.abc.Awaitable` protocol) by being declared via the ``async def`` syntax and thus callable *only* when preceded by comparable ``await`` syntax). Parameters ---------- func : object Object to be inspected. Returns ------- bool :data:`True` only if this object is an asynchronous callable. See Also -------- :func:`inspect.iscoroutinefunction` :func:`inspect.isasyncgenfunction` Stdlib functions strongly inspiring this implementation. ''' # Code object underlying this pure-Python callable if any *OR* "None". # # Note this tester intentionally: # * Inlines the tests performed by the is_func_coro() and # is_func_async_generator() testers for efficiency. # * Calls the get_func_codeobj_or_none() with "is_unwrap" disabled # rather than enabled. Why? Because the asynchronicity of this possibly # higher-level wrapper has *NO* relation to that of the possibly # lower-level wrappee wrapped by this wrapper. Notably, it is both # feasible and commonplace for third-party decorators to enable: # * Synchronous callables to be called asynchronously by wrapping # synchronous callables with asynchronous closures. # * Asynchronous callables to be called synchronously by wrapping # asynchronous callables with synchronous closures. Indeed, our # top-level "conftest.py" pytest plugin does exactly this -- enabling # asynchronous tests to be safely called by pytest's currently # synchronous framework. func_codeobj = get_func_codeobj_or_none(func) # If this object is *NOT* a pure-Python callable, immediately return false. if func_codeobj is None: return False # Else, this object is a pure-Python callable. # Bit field of OR-ed binary flags describing this callable. func_codeobj_flags = func_codeobj.co_flags # Return true only if these flags imply this callable to be either... return ( # An asynchronous coroutine *OR*... func_codeobj_flags & CO_COROUTINE != 0 or # An asynchronous generator. func_codeobj_flags & CO_ASYNC_GENERATOR != 0 ) def is_func_coro(func: object) -> TypeGuard[Callable]: ''' :data:`True` only if the passed object is an **asynchronous coroutine factory** (i.e., awaitable callable containing *no* ``yield`` expression implicitly creating and returning an awaitable object (i.e., satisfying the :class:`collections.abc.Awaitable` protocol) by being declared via the ``async def`` syntax and thus callable *only* when preceded by comparable ``await`` syntax). Parameters ---------- func : object Object to be inspected. Returns ------- bool :data:`True` only if this object is an asynchronous coroutine factory. See Also -------- :func:`inspect.iscoroutinefunction` Stdlib function strongly inspiring this implementation. ''' # Code object underlying this pure-Python callable if any *OR* "None". func_codeobj = get_func_codeobj_or_none(func) # Return true only if... return ( # This object is a pure-Python callable *AND*... func_codeobj is not None and # This callable's code object implies this callable to be an # asynchronous coroutine. func_codeobj.co_flags & CO_COROUTINE != 0 ) def is_func_async_generator(func: object) -> TypeGuard[Callable]: ''' :data:`True` only if the passed object is an **asynchronous generator factory** (i.e., awaitable callable containing one or more ``yield`` expressions implicitly creating and returning an awaitable object (i.e., satisfying the :class:`collections.abc.Awaitable` protocol) by being declared via the ``async def`` syntax and thus callable *only* when preceded by comparable ``await`` syntax). Parameters ---------- func : object Object to be inspected. Returns ------- bool :data:`True` only if this object is an asynchronous generator. See Also -------- :func:`inspect.isasyncgenfunction` Stdlib function strongly inspiring this implementation. ''' # Code object underlying this pure-Python callable if any *OR* "None". func_codeobj = get_func_codeobj_or_none(func) # Return true only if... return ( # This object is a pure-Python callable *AND*... func_codeobj is not None and # This callable's code object implies this callable to be an # asynchronous generator. func_codeobj.co_flags & CO_ASYNC_GENERATOR != 0 ) # ....................{ TESTERS ~ sync }.................... def is_func_sync_generator(func: object) -> TypeGuard[Callable]: ''' :data:`True` only if the passed object is an **synchronous generator factory** (i.e., awaitable callable containing one or more ``yield`` expressions implicitly creating and returning a generator object (i.e., satisfying the :class:`collections.abc.Generator` protocol) by being declared via the ``def`` rather than ``async def`` syntax). Parameters ---------- func : object Object to be inspected. Returns ------- bool :data:`True` only if this object is a synchronous generator. See Also -------- :func:`inspect.isgeneratorfunction` Stdlib function strongly inspiring this implementation. ''' # If this object is uncallable, immediately return False. # # Note this test is explicitly required to differentiate synchronous # generator callables from synchronous generator objects (i.e., the objects # they implicitly create and return). Whereas both asynchronous coroutine # objects *AND* asynchronous generator objects do *NOT* contain code # objects whose "CO_COROUTINE" and "CO_ASYNC_GENERATOR" flags are non-zero, # synchronous generator objects do contain code objects whose # "CO_GENERATOR" flag is non-zero. This implies synchronous generator # callables to create and return synchronous generator objects that are # themselves technically valid synchronous generator callables, which is # absurd. We prohibit this ambiguity by differentiating the two here. if not callable(func): return False # Else, this object is callable. # Code object underlying this pure-Python callable if any *OR* "None". func_codeobj = get_func_codeobj_or_none(func) # Return true only if... return ( # This object is a pure-Python callable *AND*... func_codeobj is not None and # This callable's code object implies this callable to be a # synchronous generator. func_codeobj.co_flags & CO_GENERATOR != 0 ) # ....................{ TESTERS : nested }.................... def is_func_nested(func: Callable) -> bool: ''' :data:`True` only if the passed callable is **nested** (i.e., a pure-Python callable declared in the body of another pure-Python callable or class). Equivalently, this tester returns :data:`True` only if that callable is either: * A closure, which by definition is nested inside another callable. * A method, which by definition is nested inside its class. * A **nested non-closure function** (i.e., a closure-like function that does *not* reference local attributes of the parent callable enclosing that function and is thus technically *not* a closure): e.g., .. code-block:: python def muh_parent_callable(): # <-- parent callable def muh_nested_callable(): pass # <-- nested non-closure function return muh_nested_callable Parameters ---------- func : Callable Callable to be inspected. Returns ------- bool :data:`True` only if this callable is nested. ''' # Return true only if either... return ( # That callable is a closure (in which case that closure is necessarily # nested inside another callable) *OR*... # # Note that this tester intentionally tests for whether that callable is # a closure first, as doing so efficiently reduces to a constant-time # attribute test -- whereas the following test for non-closure nested # callables inefficiently requires a linear-time string search. is_func_closure(func) or # The fully-qualified name of that callable contains one or more "." # delimiters, each signifying a nested lexical scope. Since *ALL* # callables (i.e., both pure-Python and C-based) define a non-empty # "__qualname__" dunder variable containing at least their unqualified # names, this simplistic test is guaranteed to be safe. # # Note this tester intentionally tests for the general-purpose existence # of a "." delimiter rather than the special-cased existence of a # ".." placeholder substring. Why? Because there are two types # of nested callables: # * Non-methods, which are lexically nested in a parent callable whose # scope encapsulates all previously declared local variables. For # unknown reasons, the unqualified names of nested non-method # callables are *ALWAYS* prefixed by ".." in their # "__qualname__" variables: # >>> from collections.abc import Callable # >>> def muh_parent_callable() -> Callable: # ... def muh_nested_callable() -> None: pass # ... return muh_nested_callable # >>> muh_nested_callable = muh_parent_callable() # >>> muh_parent_callable.__qualname__ # 'muh_parent_callable' # >>> muh_nested_callable.__qualname__ # 'muh_parent_callable..muh_nested_callable' # * Methods, which are lexically nested in the scope encapsulating all # previously declared class variables (i.e., variables declared in # class scope and thus accessible as method annotations). For unknown # reasons, the unqualified names of methods are *NEVER* prefixed by # ".." in their "__qualname__" variables: e.g., # >>> from typing import ClassVar # >>> class MuhClass(object): # ... # Class variable declared in class scope. # ... muh_class_var: ClassVar[type] = int # ... # Instance method annotated by this class variable. # ... def muh_method(self) -> muh_class_var: return 42 # >>> MuhClass.muh_method.__qualname__ # 'MuhClass.muh_method' '.' in func.__qualname__ ) # ....................{ TESTERS ~ nested : closure }.................... def is_func_closure(func: Any) -> TypeGuard[Callable]: ''' :data:`True` only if the passed callable is a **closure** (i.e., nested callable accessing one or more variables declared by the parent callable also declaring that callable). Note that all closures are necessarily nested callables but that the converse is *not* necessarily the case. In particular, a nested callable accessing *no* variables declared by the parent callable also declaring that callable is *not* a closure; it's simply a nested callable. Parameters ---------- func : Callable Callable to be inspected. Returns ------- bool :data:`True` only if this callable is a closure. ''' # Return true only if that callable defines the closure-specific # "__closure__" dunder variable whose value is either: # * If that callable is a closure, a tuple of zero or more cell variables. # * If that callable is a pure-Python non-closure, "None". # * If that callable is C-based, undefined. return getattr(func, '__closure__', None) is not None # ....................{ TESTERS ~ wrapper }.................... def is_func_wrapper(func: Any) -> TypeGuard[Callable]: ''' :data:`True` only if the passed object is a **callable wrapper** (i.e., callable decorated by the standard :func:`functools.wraps` decorator for wrapping a pure-Python callable with additional functionality defined by a higher-level decorator). Note that this tester returns :data:`True` for both pure-Python and C-based callable wrappers. As an example of the latter, the standard :func:`functools.lru_cache` decorator creates and returns low-level C-based callable wrappers of the private type :class:`functools._lru_cache_wrapper` wrapping pure-Python callables. Parameters ---------- func : object Object to be inspected. Returns ------- bool :data:`True` only if this object is a callable wrapper. ''' # Return true only if this object defines a dunder attribute uniquely # specific to the @functools.wraps decorator. # # Technically, *ANY* callable (including non-wrappers *NOT* created by the # @functools.wraps decorator) could trivially define this attribute; ergo, # this invites the possibility of false positives. Pragmatically, doing so # would violate ad-hoc standards and real-world practice across the # open-source ecosystem; ergo, this effectively excludes false positives. return hasattr(func, '__wrapped__') @callable_cached def is_func_wrapper_isomorphic(func: Any) -> TypeGuard[Callable]: ''' :data:`True` only if the passed object is an **isomorphic wrapper** (i.e., callable decorated by the standard :func:`functools.wraps` decorator for wrapping a pure-Python callable with additional functionality defined by a higher-level decorator such that that wrapper isomorphically preserves both the number and types of all passed parameters and returns by accepting only a variadic positional argument and a variadic keyword argument). This tester enables callers to detect when a user-defined callable has been decorated by an isomorphic decorator, which constitutes *most* real-world decorators of interest. This tester is memoized for efficiency. Caveats ------- **This tester is merely a heuristic** -- albeit a reasonably robust heuristic likely to succeed in almost all real-world use cases. Nonetheless, this tester *could* return false positives and negatives in edge cases. Parameters ---------- func : object Object to be inspected. Returns ------- bool :data:`True` only if this object is an isomorphic decorator wrapper. ''' # If the passed callable is *NOT* a wrapper, immediately return false. if not is_func_wrapper(func): return False # Else, that callable is a wrapper. # Code object underlying that callable as is (rather than possibly unwrapped # to another code object entirely) if that callable is pure-Python *OR* # "None" otherwise (i.e., if that callable is C-based). func_codeobj = get_func_codeobj_or_none(func) # Return true only if... return ( # That callable is pure-Python *AND*... func_codeobj is not None and # That callable accepts a variadic positional argument *AND*... is_func_arg_variadic_positional(func_codeobj) and # That callable accepts a variadic keyword argument *AND*... is_func_arg_variadic_keyword(func_codeobj) and # That callable accepts *NO* non-variadic arguments. get_func_args_nonvariadic_len(func_codeobj) == 0 ) beartype-0.18.5/beartype/_util/func/utilfuncwrap.py000066400000000000000000000335101461113517100224010ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **callable wrapper** (i.e., higher-level callable, typically implemented as a decorator, wrapping a lower-level callable) utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilCallableWrapperException from beartype.typing import Any from beartype._cave._cavefast import MethodBoundInstanceOrClassType from beartype._data.hint.datahinttyping import TypeException from collections.abc import Callable # ....................{ UNWRAPPERS ~ once }.................... #FIXME: Unit test us up, please. def unwrap_func_once(func: Any) -> Callable: ''' Immediate **wrappee** (i.e., callable wrapped by the passed wrapper callable) of the passed higher-level **wrapper** (i.e., callable wrapping the wrappee callable to be returned) if the passed callable is a wrapper *or* that callable as is otherwise (i.e., if that callable is *not* a wrapper). Specifically, this getter undoes the work performed by any of the following: * A single use of the :func:`functools.wrap` decorator on the wrappee callable to be returned. * A single call to the :func:`functools.update_wrapper` function on the wrappee callable to be returned. Parameters ---------- func : Callable Wrapper callable to be unwrapped. Returns ------- Callable The immediate wrappee callable wrapped by the passed wrapper callable. Raises ------ _BeartypeUtilCallableWrapperException If the passed callable is *not* a wrapper. ''' # Immediate wrappee callable wrapped by the passed wrapper callable if any # *OR* "None" otherwise (i.e., if that callable is *NOT* a wrapper). func_wrappee = getattr(func, '__wrapped__', None) # If that callable is *NOT* a wrapper, raise an exception. if func_wrappee is None: raise _BeartypeUtilCallableWrapperException( f'Callable {repr(func)} not wrapper ' f'(i.e., has no "__wrapped__" dunder attribute ' f'defined by @functools.wrap or functools.update_wrapper()).' ) # Else, that callable is a wrapper. # Return this immediate wrappee callable. return func_wrappee # ....................{ UNWRAPPERS ~ once : descriptor }.................... #FIXME: Unit test us up, please. def unwrap_func_boundmethod_once( # Mandatory parameters. func: MethodBoundInstanceOrClassType, # Optional parameters. exception_cls: TypeException = _BeartypeUtilCallableWrapperException, exception_prefix: str = '', ) -> Callable: ''' Pure-Python unbound function wrapped by the passed **C-based bound instance method descriptor** (i.e., callable implicitly instantiated and assigned on the instantiation of an object whose class declares an instance function (whose first parameter is typically named ``self``) as an instance variable of that object such that that callable unconditionally passes that object as the value of that first parameter on all calls to that callable). Parameters ---------- func : MethodBoundInstanceOrClassType Bound method descriptor to be inspected. exception_cls : TypeException, optional Type of exception to be raised in the event of a fatal error. Defaults to :exc:`._BeartypeUtilCallableWrapperException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- Callable Pure-Python unbound function wrapped by this bound method descriptor. Raises ------ exception_cls If the passed object is *not* a bound method descriptor. See Also -------- :func:`beartype._util.func.utilfunctest.is_func_boundmethod` Further details. ''' # Avoid circular import dependencies. from beartype._util.func.utilfunctest import die_unless_func_boundmethod # If this object is *NOT* a class method descriptor, raise an exception. die_unless_func_boundmethod( func=func, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # Else, this object is a class method descriptor. # Return the pure-Python function wrapped by this descriptor. Just do it! return func.__func__ def unwrap_func_classmethod_once( # Mandatory parameters. func: classmethod, # Optional parameters. exception_cls: TypeException = _BeartypeUtilCallableWrapperException, exception_prefix: str = '', ) -> Callable: ''' Pure-Python unbound function wrapped by the passed **C-based unbound class method descriptor** (i.e., method decorated by the builtin :class:`classmethod` decorator, yielding a non-callable instance of that :class:`classmethod` decorator class implemented in low-level C and accessible via the low-level :attr:`object.__dict__` dictionary rather than as class or instance attributes). Parameters ---------- func : classmethod Class method descriptor to be inspected. exception_cls : TypeException, optional Type of exception to be raised in the event of a fatal error. Defaults to :exc:`._BeartypeUtilCallableWrapperException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- Callable Pure-Python unbound function wrapped by this class method descriptor. Raises ------ exception_cls If the passed object is *not* a class method descriptor. See Also -------- :func:`beartype._util.func.utilfunctest.is_func_classmethod` Further details. ''' # Avoid circular import dependencies. from beartype._util.func.utilfunctest import die_unless_func_classmethod # If this object is *NOT* a class method descriptor, raise an exception. die_unless_func_classmethod( func=func, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # Else, this object is a class method descriptor. # Return the pure-Python function wrapped by this descriptor. Just do it! return func.__func__ def unwrap_func_staticmethod_once( # Mandatory parameters. func: staticmethod, # Optional parameters. exception_cls: TypeException = _BeartypeUtilCallableWrapperException, exception_prefix: str = '', ) -> Callable: ''' Pure-Python unbound function wrapped by the passed **C-based unbound static method descriptor** (i.e., method decorated by the builtin :class:`staticmethod` decorator, yielding a non-callable instance of that :class:`staticmethod` decorator class implemented in low-level C and accessible via the low-level :attr:`object.__dict__` dictionary rather than as class or instance attributes). Parameters ---------- func : staticmethod Static method descriptor to be inspected. exception_cls : TypeException, optional Type of exception to be raised in the event of a fatal error. Defaults to :exc:`._BeartypeUtilCallableWrapperException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- Callable Pure-Python unbound function wrapped by this static method descriptor. Raises ------ exception_cls If the passed object is *not* a static method descriptor. See Also -------- :func:`beartype._util.func.utilfunctest.is_func_staticmethod` Further details. ''' # Avoid circular import dependencies. from beartype._util.func.utilfunctest import die_unless_func_staticmethod # If this object is *NOT* a static method descriptor, raise an exception. die_unless_func_staticmethod( func=func, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # Else, this object is a static method descriptor. # Return the pure-Python function wrapped by this descriptor. Just do it! return func.__func__ # ....................{ UNWRAPPERS ~ all }.................... def unwrap_func_all(func: Any) -> Callable: ''' Lowest-level **wrappee** (i.e., callable wrapped by the passed wrapper callable) of the passed higher-level **wrapper** (i.e., callable wrapping the wrappee callable to be returned) if the passed callable is a wrapper *or* that callable as is otherwise (i.e., if that callable is *not* a wrapper). Specifically, this getter iteratively undoes the work performed by: * One or more consecutive uses of the :func:`functools.wrap` decorator on the wrappee callable to be returned. * One or more consecutive calls to the :func:`functools.update_wrapper` function on the wrappee callable to be returned. Parameters ---------- func : Callable Wrapper callable to be unwrapped. Returns ------- Callable Either: * If the passed callable is a wrapper, the lowest-level wrappee callable wrapped by that wrapper. * Else, the passed callable as is. ''' #FIXME: Not even this suffices to avoid a circular import, sadly. *sigh* # Avoid circular import dependencies. # from beartype._util.func.utilfunctest import is_func_wrapper # While this callable still wraps another callable, unwrap one layer of # wrapping by reducing this wrapper to its next wrappee. while hasattr(func, '__wrapped__'): # while is_func_wrapper(func): func = func.__wrapped__ # type: ignore[attr-defined] # Return this wrappee, which is now guaranteed to *NOT* be a wrapper. return func #FIXME: Unit test us up, please. def unwrap_func_all_isomorphic(func: Any) -> Callable: ''' Lowest-level **non-isomorphic wrappee** (i.e., callable wrapped by the passed wrapper callable) of the passed higher-level **isomorphic wrapper** (i.e., closure wrapping the wrappee callable to be returned by accepting both a variadic positional and keyword argument and thus preserving both the positions and types of all parameters originally passed to that wrappee) if the passed callable is an isomorphic wrapper *or* that callable as is otherwise (i.e., if that callable is *not* an isomorphic wrapper). Specifically, this getter iteratively undoes the work performed by: * One or more consecutive decorations of the :func:`functools.wrap` decorator on the wrappee callable to be returned. * One or more consecutive calls to the :func:`functools.update_wrapper` function on the wrappee callable to be returned. Parameters ---------- func : Callable Wrapper callable to be unwrapped. Returns ------- Callable Either: * If the passed callable is an isomorphic wrapper, the lowest-level non-isomorphic wrappee callable wrapped by that wrapper. * Else, the passed callable as is. ''' # Avoid circular import dependencies. from beartype._util.func.utilfunctest import ( is_func_python, is_func_wrapper_isomorphic, ) # While that callable is a higher-level isomorphic wrapper wrapping a # lower-level callable... while is_func_wrapper_isomorphic(func): # Undo one layer of wrapping by reducing the former to the latter. # print(f'Unwrapping isomorphic closure wrapper {func} to wrappee {func.__wrapped__}...') func_wrapped = func.__wrapped__ # type: ignore[attr-defined] # If the lower-level object wrapped by this higher-level isomorphic # wrapper is *NOT* a pure-Python callable, this object is something # uselessly pathological like a class or C-based callable. Silently # ignore this useless object by halting iteration. Doing so preserves # this useful higher-level isomorphic wrapper as is. # # Note that this insane edge case arises due to the standard # @functools.wraps() decorator, which passively accepts possibly C-based # classes by wrapping those classes with pure-Python functions: e.g., # from beartype import beartype # from functools import wraps # from typing import Any # # @beartype # @wraps(list) # def wrapper(*args: Any, **kwargs: Any): # return list(*args, **kwargs) # # In the above example, the higher-level isomorphic wrapper wrapper() # wraps the lower-level C-based class "list". # # Unwrapping this wrapper to this class would induce insanity throughout # the codebase, which sanely expects wrappers to be callables rather # than classes. Clearly, classes have *NO* signatures. Technically, a # pure-Python class may define __new__() and/or __init__() dunder # methods that could be considered to be the signatures of those # classes. Nonetheless, C-based classes like "list" have *NO* such # analogues. The *ONLY* sane approach here is to pretend that we never # saw this pathological edge case. if not is_func_python(func_wrapped): break # Else, this lower-level callable is pure-Python. # Reduce this higher-level wrapper to this lower-level wrappee. func = func_wrapped # Return this wrappee, which is now guaranteed to *NOT* be an isomorphic # wrapper but might very well still be a wrapper, which is fine. return func beartype-0.18.5/beartype/_util/hint/000077500000000000000000000000001461113517100173115ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/hint/__init__.py000066400000000000000000000000001461113517100214100ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/hint/nonpep/000077500000000000000000000000001461113517100206105ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/hint/nonpep/__init__.py000066400000000000000000000000001461113517100227070ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/hint/nonpep/mod/000077500000000000000000000000001461113517100213675ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/hint/nonpep/mod/__init__.py000066400000000000000000000000001461113517100234660ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/hint/nonpep/mod/utilmodnumpy.py000066400000000000000000000375161461113517100245230ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **PEP-noncompliant NumPy type hint** (i.e., type hint defined by the third-party :mod:`numpy` package) utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: The top-level of this module should avoid importing from third-party # optional libraries, both because those libraries cannot be guaranteed to be # either installed or importable here *AND* because those imports are likely to # be computationally expensive, particularly for imports transitively importing # C extensions (e.g., anything from NumPy or SciPy). #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype.roar import ( BeartypeDecorHintNonpepNumpyException, BeartypeDecorHintNonpepNumpyWarning, ) from beartype.typing import ( Any, FrozenSet, ) from beartype._util.cache.utilcachecall import callable_cached from beartype._util.error.utilerrwarn import issue_warning from beartype._util.hint.pep.utilpepget import get_hint_pep_args from beartype._util.api.utilapityping import import_typing_attr_or_none from beartype._util.utilobject import is_object_hashable # ....................{ REDUCERS }.................... #FIXME: Refactor this function to make this function *EFFECTIVELY* cached. How? #By splitting this function up into smaller functions -- each of which is #actually cached by @callable_cached and thus called with positional arguments. def reduce_hint_numpy_ndarray( hint: Any, exception_prefix: str, *args, **kwargs ) -> Any: ''' Reduce the passed **PEP-noncompliant typed NumPy array** (i.e., subscription of the third-party :attr:`numpy.typing.NDArray` type hint factory) to the equivalent PEP-compliant beartype validator validating arbitrary objects be instances of that array type -- which has the substantial merit of already being well-supported, well-tested, and well-known to generate optimally efficient type-checking by the :func:`beartype.beartype` decorator. Technically, beartype could instead explicitly handle typed NumPy arrays throughout the codebase. Of course, doing so would yield *no* tangible benefits while imposing a considerable maintenance burden. This reducer is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object PEP-noncompliant typed NumPy array to be reduced. exception_prefix : str Human-readable label prefixing the representation of this object in the exception message. All remaining passed arguments are silently ignored. Returns ---------- object This PEP-noncompliant typed NumPy array reduced to a PEP-compliant type hint supported by :mod:`beartype`. Raises ---------- BeartypeDecorHintNonpepNumpyException If either: * The active Python interpreter targets Python < 3.9 and either: * The third-party :mod:`typing_extensions` module is unimportable. * The third-party :mod:`typing_extensions` module is importable but sufficiently old that it fails to declare the :attr:`typing_extensions.Annotated` attribute. * This hint is a typed NumPy array but either: * *Not* subscripted by exactly two arguments. * Subscripted by exactly two arguments but whose second argument is neither: * A **NumPy data type** (i.e., :class:`numpy.dtype` instance). * An object coercible into a NumPy data type by passing to the :meth:`numpy.dtype.__init__` method. ''' # ..................{ IMPORTS }.................. # Defer heavyweight imports until *AFTER* validating this hint to be a # typed NumPy array. Why? Because these imports are *ONLY* safely # importable if this hint is a typed NumPy array. Why? Because # instantiating this hint required these imports. QED. # # Note that third-party packages should typically *ONLY* be imported via # utility functions raising human-readable exceptions when those packages # are either uninstalled or unimportable. In this case, however, NumPy will # almost *ALWAYS* be importable. Why? Because this hint was externally # instantiated by the user by first importing the "numpy.typing.NDArray" # attribute passed to this getter. from beartype.vale import IsAttr, IsEqual, IsSubclass from numpy import dtype, ndarray # pyright: ignore[reportMissingImports] from numpy.typing import NDArray # type: ignore[attr-defined] #FIXME: Consider submitting an upstream issue about this. We don't #particularly feel like arguing tonight, because that's a lonely hill. # If this hint is the unsubscripted "NDArray" type hint, this hint # permissively matches *ALL* NumPy arrays rather than strictly matching # *ONLY* appropriately typed NumPy arrays. In this case, reduce this hint # to the untyped "numpy.ndarray" class. # # Note the similar test matching the subscripted "NDArray[Any]" hint below. # Moreover, note this test *CANNOT* be performed elsewhere (e.g., by # adding "HintSignNumpyArray" to the "HINT_SIGNS_ORIGIN_ISINSTANCEABLE" # frozen set of all signs whose unsubscripted type hint factories are # shallowly type-checkable). Why? Because the "NDArray" type hint factory # violates type hinting standards. Specifically, this factory implicitly # subscripts *AND* parametrizes itself with the "numpy.ScalarType" type # variable bounded above by the "numpy.generic" abstract base class for # NumPy scalars. # # We have *NO* idea why NumPy does this. This implicit behaviour is # semantically lossy rather than lossless and thus arguably constitutes an # upstream bug. Why? Because this behaviour violates: # * The NumPy API. The "NDArray" type hint factory is subscriptable by more # than merely NumPy scalar types. Ergo, "NDArray" is semantically # inaccurate! # * PEP 484, which explicitly standardizes an equivalence between # unsubscripted type hint factories and the same factories subscripted by # the "typing.Any" singleton. However, "NDArray" is *MUCH* semantically # narrower than and thus *NOT* equivalent to "NDArray[Any]"! # # Of course, upstream is unlikely to see it that way. We're *NOT* dying on # an argumentative hill about semantics. Upstream makes the rules. Do it. if hint is NDArray: return ndarray # ..................{ CONSTANTS }.................. # Frozen set of all NumPy scalar data type abstract base classes (ABCs). NUMPY_DTYPE_TYPE_ABCS = _get_numpy_dtype_type_abcs() # ..................{ ARGS }.................. # Objects subscripting this hint if any *OR* the empty tuple otherwise. hint_args = get_hint_pep_args(hint) # If this hint was *NOT* subscripted by exactly two arguments, this hint is # malformed as a typed NumPy array. In this case, raise an exception. if len(hint_args) != 2: raise BeartypeDecorHintNonpepNumpyException( f'{exception_prefix}typed NumPy array {repr(hint)} ' f'not subscripted by exactly two arguments.' ) # Else, this hint was subscripted by exactly two arguments. # Data type subhint subscripting this hint. Yes, the "numpy.typing.NDArray" # type hint bizarrely encapsulates its data type argument into a private # "numpy._DTypeMeta" type subhint. Why? We have absolutely no idea, but we # have no say in the matter. NumPy, you're on notice for stupidity. hint_dtype_subhint = hint_args[1] # Objects subscripting this subhint if any *OR* the empty tuple otherwise. hint_dtype_subhint_args = get_hint_pep_args(hint_dtype_subhint) # If this hint was *NOT* subscripted by exactly one argument, this subhint # is malformed as a data type subhint. In this case, raise an exception. if len(hint_dtype_subhint_args) != 1: raise BeartypeDecorHintNonpepNumpyException( f'{exception_prefix}typed NumPy array {repr(hint)} ' f'data type subhint {repr(hint_dtype_subhint)} ' f'not subscripted by exactly one argument.' ) # Else, this subhint was subscripted by exactly one argument. # Data type-like object subscripting this subhint. Look, just do it. hint_dtype_like = hint_dtype_subhint_args[0] # If this dtype-like is "typing.Any", this hint permissively matches *ALL* # NumPy arrays rather than strictly matching *ONLY* appropriately typed # NumPy arrays. In this case, reduce this hint to the untyped # "numpy.ndarray" class. # # Note the similar test matching the unsubscripted "NDArray" hint above. if hint_dtype_like is Any: return ndarray # ..................{ REDUCTION }.................. #FIXME: Safely replace this with "from typing import Annotated" after #dropping Python 3.8 support. # "typing.Annotated" type hint factory safely imported from whichever of # the "typing" or "typing_extensions" modules declares this attribute if # one or more do *OR* "None" otherwise (i.e., if none do). typing_annotated = import_typing_attr_or_none('Annotated') # If this factory is unimportable, this typed NumPy array *CANNOT* be # reduced to a subscription of this factory by one or more semantically # equivalent beartype validators. In this case... if typing_annotated is None: # Emit a non-fatal warning informing the user of this issue. issue_warning( cls=BeartypeDecorHintNonpepNumpyWarning, message=( f'{exception_prefix}typed NumPy array {repr(hint)} ' f'reduced to untyped NumPy array {repr(ndarray)} ' f'(i.e., as neither "typing.Annotated" nor ' f'"typing_extensions.Annotated" importable).' ), ) # Reduce this hint to the untyped "ndarray" class with apologies. return ndarray # Else, this factory is importable. # Equivalent nested beartype validator reduced from this hint. hint_validator = None # type: ignore[assignment] # If... if ( # This dtype-like is hashable *AND*... is_object_hashable(hint_dtype_like) and # This dtype-like is a scalar data type abstract base class (ABC)... hint_dtype_like in NUMPY_DTYPE_TYPE_ABCS ): # Then avoid attempting to coerce this possibly non-dtype into a proper # dtype. Although NumPy previously silently coerced these ABCs into # dtypes (e.g., from "numpy.floating" to "numpy.float64"), recent # versions of NumPy now emit non-fatal deprecation warnings on doing so # and will presumably raise fatal exceptions in the near future: # >>> import numpy as np # >>> np.dtype(np.floating) # DeprecationWarning: Converting `np.inexact` or `np.floating` to a # dtype is deprecated. The current result is `float64` which is not # strictly correct. # # We instead follow mypy's lead presumably defined somewhere in the # incredibly complex innards of NumPy's mypy plugin -- which we # admittedly failed to grep despite ~~wasting~~ "investing" several # hours in doing so. Specifically, mypy treats subscriptions of the # "numpy.typing.NDArray" type hint factory by one of these ABCs (rather # than either a scalar or proper dtype) as a type inheritance (rather # than object equality) relation. Since this is sensible, we do too. # Equivalent nested beartype validator reduced from this hint. hint_validator = ( IsAttr['dtype', IsAttr['type', IsSubclass[hint_dtype_like]]]) # Else, this dtype-like is either unhashable *OR* not such an ABC. else: # Attempt to coerce this possibly non-dtype into a proper dtype. Note # that the dtype.__init__() constructor efficiently maps non-dtype # scalar types (e.g., "numpy.float64") to corresponding cached dtypes: # >>> import numpy # >>> i4_dtype = numpy.dtype('>i4') # >>> numpy.dtype(i4_dtype) is numpy.dtype(i4_dtype) # True # >>> numpy.dtype(numpy.float64) is numpy.dtype(numpy.float64) # True # # Ergo, the call to this constructor here is guaranteed to already # effectively be memoized. try: hint_dtype = dtype(hint_dtype_like) # If this object is *NOT* coercible into a dtype, raise an exception. # This is essential. As of NumPy 1.21.0, "numpy.typing.NDArray" fails # to validate its subscripted argument to actually be a dtype: e.g., # >>> from numpy.typing import NDArray # >>> NDArray['wut'] # numpy.ndarray[typing.Any, numpy.dtype['wut']] # <-- you kidding me? except Exception as exception: raise BeartypeDecorHintNonpepNumpyException( f'{exception_prefix}typed NumPy array {repr(hint)} ' f'data type {repr(hint_dtype_like)} invalid ' f'(i.e., neither data type nor coercible into data type).' ) from exception # Else, this object is now a proper dtype. # Equivalent nested beartype validator reduced from this hint. hint_validator = IsAttr['dtype', IsEqual[hint_dtype]] # Replace the usually less readable representation of this validator with # the usually more readable representation of this hint (e.g., # "numpy.ndarray[typing.Any, numpy.float64]"). hint_validator.get_repr = repr(hint) # Return this validator annotating the NumPy array type. return typing_annotated[ndarray, hint_validator] # ....................{ PRIVATE ~ getter }.................... #FIXME: File an upstream NumPy issue politely requesting they publicize either: #* An equivalent container listing these types. #* Documentation officially listing these types. @callable_cached def _get_numpy_dtype_type_abcs() -> FrozenSet[type]: ''' Frozen set of all **NumPy scalar data type abstract base classes** (i.e., superclasses of all concrete NumPy scalar data types (e.g., :class:`numpy.int64`, :class:`numpy.float32`)). This getter is memoized for efficiency. To defer the substantial cost of importing from NumPy, the frozen set memoized by this getter is intentionally deferred to call time rather than globalized as a constant. Caveats ---------- **NumPy currently provides no official container listing these classes.** Likewise, NumPy documentation provides no official list of these classes. Ergo, this getter. This has the dim advantage of working but the profound disadvantage of inviting inevitable discrepancies between the :mod:`beartype` and :mod:`numpy` codebases. So it goes. ''' # Defer heavyweight imports. from numpy import ( # pyright: ignore[reportMissingImports] character, complexfloating, flexible, floating, generic, inexact, integer, number, signedinteger, unsignedinteger, ) # Create, return, and cache a frozen set listing these ABCs. return frozenset(( character, complexfloating, flexible, floating, generic, inexact, integer, number, signedinteger, unsignedinteger, )) beartype-0.18.5/beartype/_util/hint/nonpep/mod/utilmodpandera.py000066400000000000000000000220341461113517100247520ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **PEP-noncompliant NumPy type hint** (i.e., type hint defined by the third-party :mod:`numpy` package) utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: The top-level of this module should avoid importing from third-party # optional libraries, both because those libraries cannot be guaranteed to be # either installed or importable here *AND* because those imports are likely to # be computationally expensive, particularly for imports transitively importing # C extensions (e.g., anything from NumPy or SciPy). #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype.roar import BeartypeDecorHintNonpepPanderaException # ....................{ REDUCERS }.................... def reduce_hint_pandera( hint: object, exception_prefix: str, *args, **kwargs ) -> type: ''' Reduce the passed **PEP-noncompliant Pandera type hint** (i.e., subscription of *any* PEP-noncompliant type hint factory published by the third-party :mod:`pandera.typing` type hint factory) to the isinstanceable Pandas type subclassed by this hint, which :mod:`beartype` then subsequently subjects to shallow type-checking. This reducer enables :mod:`beartype` to at least shallowly type-check Pandera type hints while allowing Pandera itself to deeply type-check the same hints. Pandera publishes its own Pandera-specific PEP-noncompliant runtime type-checking decorator :func:`pandera.check_types` that supports *only* Pandera-specific PEP-noncompliant :mod:`pandera.typing` type hints. Since Pandera users are already accustomed to decorating *all* Pandera-based callables (i.e., callables accepting one or more parameters and/or returning one or more values annotated by Pandera type hints) with :func:`pandera.check_types`, attempting to deeply type-check the same objects already type-checked by that decorator would only inefficiently and needlessly slow type-checking wrappers generated by the :func:`beartype.beartype` decorator. Moreover, doing so is infeasible. Pandera type hints are extremely non-standard and thus *not* reasonably type-checkable by any standards-compliant static or runtime type-checkers. Shallowly type-checking Pandera type hints is still beneficial, however, as: * Pandera itself currently fails to shallowly type-check its own type hints. That is to say, if a caller passes a string rather than a Pandas data frame to a :func:`pandera.check_types`-decorated function, that function will silently accept that string rather than raise an exception. *sigh* * Functions annotated by one or more Pandera type hints that are (either intentionally or accidentally) *not* decorated by :func:`pandera.check_types` will still receive at least a modicum of shallow type-checking support from :mod:`beartype` itself. This reducer is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Motivation ---------- The core issue with Pandera type hints is somewhat more subtle than the glib hand-waving performed above. Yes, Pandera type hints *are* PEP-noncompliant, but they're more than just that. Pandera type hints fundamentally contravene established semantics for PEP-compliant generics. Generally speaking, generics are *not* simply descriptive type hints; they're full-blown classes intended to be instantiated as objects throughout the codebase using those generics as type hints. The unsubscripted portion of a generic hint is an instanceable class (e.g., the "list" in "list[str]" is itself an instanceable class). @beartype expects any object annotated by a generic type hint to be an instance of that generic: e.g., .. code-block:: pycon # A PEP 585-compliant generic. >>> class ListOfStrings(list[str]): pass # An instance of this generic satisfies this generic used as a type hint. >>> from beartype import beartype >>> @beartype ... def accept_list_of_strings(lst: ListOfStrings): return 'Okie-dokie!' >>> accept_list_of_strings(ListOfStrings()) 'Okie-dokie!' Pandera type hints violate this expectation. Syntactically, Pandera type hints are PEP-compliant generics (of course); semantically, Pandera type hints are PEP-noncompliant generics, because the objects they describe (i.e., Pandas data frames) are *not* instances of these generics. Pandas data frames are instances of the Pandera-agnostic "pandas.core.frame.DataFrame" non-generic class rather than Pandera-specific "pandera.typing.pandas.DataFrame" generic. Pandera type hints *should* have been instead defined as PEP 544-compliant protocols that exploit ephemeral duck typing. Since they weren't, downstream consumers like @beartype must now pretend that Pandera type hints are the Pandas types they semantically alias by reducing the former to the latter. Caveats ---------- **This reducer does not validate the callable annotated by this Pandera type hint to be decorated by the** :func:`pandera.check_types` **decorator.** Ideally, this reducer would do so to prevent :mod:`beartype` from emitting false positives and negatives from calls to callables for which the user accidentally omitted the :func:`pandera.check_types` decorator. Unfortunately, order of decoration is arbitrary. :mod:`beartype` has no means of distinguishing between these two cases: * The valid case in which the user decorated this callable first by :func:`beartype.beartype` and then by :func:`pandera.check_types`. In this case, :func:`beartype.beartype` runs first and has no efficient means of deciding that the :func:`pandera.check_types` will be run immediately after -- short of abstract syntax tree (AST) inspection, which would be extraordinarily inefficient, non-portable, and fragile. * The invalid case in which the user accidentally omitted the :func:`pandera.check_types` decorator. **This reducer does not validate that this Pandera type hint annotates a callable.** Technically, Pandera type hints are invalid in *all* type hinting contexts except as callable annotations -- including: * As a class type hint. * As an attribute assignment type hint. * As the type hint passed to a statement-level runtime type-checker (e.g., :func:`beartype.door.is_bearable`). Pragmatically, refactoring :mod:`beartype` to inform reducers of whether or not the current hint (that may be a nested child type hint of a parent type hint) annotates a callable or not would be extraordinarily non-trivial. Doing so would require refactoring our low-level: * Code generators to accept an additional parameter describing this case. * Type hint reducers to transitively pass that same parameter here. Since Pandera type hints are already PEP-noncompliant, the only sane approach is to continue unconditionally ignoring them. Let us not break :mod:`beartype` for the PEP-noncompliant. Parameters ---------- hint : object PEP-noncompliant typed NumPy array to return the data type of. exception_prefix : str Human-readable label prefixing the representation of this object in the exception message. All remaining passed arguments are silently ignored. Returns ---------- type Isinstanceable Pandas type subclassed by this Pandera type hint. Raises ---------- BeartypeDecorHintNonpepPanderaException If either: * This hint is *not* a PEP 484- or 585-compliant generic. * This hint is a PEP 484- or 585-compliant generic *not* subclassing one or more Pandas-specific superclasses. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.proposal.pep484585.utilpep484585generic import ( find_hint_pep484585_generic_module_base_first) # Find and return the first Pandas type subclassed by this Pandera generic # type hint. # # Note that we intentionally pass positional rather than keyword arguments # as a microoptimization for improved cache-time efficiency. Gah! return find_hint_pep484585_generic_module_base_first( hint=hint, module_name=_PANDAS_MODULE_NAME, exception_cls=BeartypeDecorHintNonpepPanderaException, exception_prefix=exception_prefix, ) # ....................{ PRIVATE ~ constants }.................... _PANDAS_MODULE_NAME='pandas' ''' Fully-qualified name of the package providing the third-party Pandas project. ''' beartype-0.18.5/beartype/_util/hint/nonpep/utilnonpeptest.py000066400000000000000000000543251461113517100242700ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **PEP-noncompliant type hint tester** (i.e., callable validating an arbitrary object to be a PEP-noncompliant type hint) utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... #FIXME: Validate strings to be syntactically valid classnames via a globally #scoped compiled regular expression. Raising early exceptions at decoration #time is preferable to raising late exceptions at call time. #FIXME: Indeed, we now provide such a callable: # from beartype._util.module.utilmodget import die_unless_module_attr_name # ....................{ IMPORTS }.................... from beartype.roar import BeartypeDecorHintNonpepException from beartype._util.cache.utilcachecall import callable_cached from beartype._util.cls.pep.utilpep3119 import ( die_unless_type_isinstanceable, is_type_isinstanceable, ) from beartype._data.hint.datahinttyping import TypeException # ....................{ VALIDATORS }.................... #FIXME: Unit test us up, please. def die_if_hint_nonpep( # Mandatory parameters. hint: object, # Optional parameters. is_str_valid: bool = True, exception_cls: TypeException = BeartypeDecorHintNonpepException, exception_prefix: str = '', ) -> None: ''' Raise an exception if the passed object is a **PEP-noncompliant type hint** (i.e., :mod:`beartype`-specific annotation *not* compliant with annotation-centric PEPs). This validator is effectively (but technically *not*) memoized. See the :func:`beartype._util.hint.utilhinttest.die_unless_hint` validator. Parameters ---------- hint : object Object to be validated. is_str_valid : bool, optional :data:`True` only if this function permits this tuple to contain strings. Defaults to :data:`False`. If this boolean is: * :data:`True`, this tuple is valid only when containing classes and/or classnames. * :data:`False`, this object is valid only when containing classes. exception_cls : type[Exception] Type of the exception to be raised by this function. Defaults to :class:`BeartypeDecorHintNonpepException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Raises ------ exception_cls If this object is either: * An **isinstanceable type** (i.e., standard class passable as the second parameter to the :func:`isinstance` builtin and thus typically *not* compliant with annotation-centric PEPs). * A **non-empty tuple** (i.e., semantic union of types) containing one or more: * Non-:mod:`typing` types. * If ``is_str_valid``, **strings** (i.e., forward references specified as either fully-qualified or unqualified classnames). ''' # If this object is a PEP-noncompliant type hint, raise an exception. # # Note that this memoized call is intentionally passed positional rather # than keyword parameters to maximize efficiency. if is_hint_nonpep(hint, is_str_valid): assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') assert isinstance(exception_cls, type), ( f'{repr(exception_cls)} not type.') assert issubclass(exception_cls, Exception), ( f'{repr(exception_cls)} not exception type.') raise exception_cls( f'{exception_prefix}type hint {repr(hint)} ' f'is PEP-noncompliant (e.g., neither ' + ( ( 'isinstanceable class, forward reference, nor tuple of ' 'isinstanceable classes and/or forward references).' ) if is_str_valid else 'isinstanceable class nor tuple of isinstanceable classes).' ) ) # Else, this object is *NOT* a PEP-noncompliant type hint. #FIXME: Unit test this function with respect to non-isinstanceable classes. def die_unless_hint_nonpep( # Mandatory parameters. hint: object, # Optional parameters. is_str_valid: bool = True, exception_cls: TypeException = BeartypeDecorHintNonpepException, exception_prefix: str = '', ) -> None: ''' Raise an exception unless the passed object is a **PEP-noncompliant type hint** (i.e., :mod:`beartype`-specific annotation *not* compliant with annotation-centric PEPs). This validator is effectively (but technically *not*) memoized. See the :func:`beartype._util.hint.utilhinttest.die_unless_hint` validator. Parameters ---------- hint : object Object to be validated. is_str_valid : bool, optional :data:`True` only if this function permits this tuple to contain strings. Defaults to :data:`False`. If this boolean is: * :data:`True`, this tuple is valid only when containing classes and/or classnames. * :data:`False`, this object is valid only when containing classes. exception_cls : type[Exception], optional Type of the exception to be raised by this function. Defaults to :class:`BeartypeDecorHintNonpepException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Raises ------ exception_cls If this object is neither: * An **isinstanceable type** (i.e., standard class passable as the second parameter to the :func:`isinstance` builtin and thus typically *not* compliant with annotation-centric PEPs). * A **non-empty tuple** (i.e., semantic union of types) containing one or more: * Non-:mod:`typing` types. * If ``is_str_valid``, **strings** (i.e., forward references specified as either fully-qualified or unqualified classnames). ''' # If this object is a PEP-noncompliant type hint, reduce to a noop. # # Note that this memoized call is intentionally passed positional rather # than keyword parameters to maximize efficiency. if is_hint_nonpep(hint, is_str_valid): return # Else, this object is *NOT* a PEP-noncompliant type hint. In this case, # subsequent logic raises an exception specific to the passed parameters. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # BEGIN: Synchronize changes here with the is_hint_nonpep() tester below. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! assert isinstance(exception_cls, type), ( f'{repr(exception_cls)} not type.') assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') # If this object is a class... if isinstance(hint, type): # If this class is *NOT* PEP-noncompliant, raise an exception. die_unless_hint_nonpep_type( hint=hint, exception_prefix=exception_prefix, exception_cls=exception_cls, ) # Else, this class is isinstanceable. In this case, silently accept # this class as is. return # Else, this object is *NOT* a class. # # If this object is a tuple, raise a tuple-specific exception. elif isinstance(hint, tuple): die_unless_hint_nonpep_tuple( hint=hint, is_str_valid=is_str_valid, exception_prefix=exception_prefix, exception_cls=exception_cls, ) # Else, this object is neither a type nor type tuple. # Raise a generic exception. raise exception_cls( f'{exception_prefix}type hint {repr(hint)} either ' f'PEP-noncompliant or currently unsupported by @beartype.' ) # ....................{ VALIDATORS ~ kind }.................... #FIXME: Unit test us up. def die_unless_hint_nonpep_type( # Mandatory parameters. hint: type, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintNonpepException, exception_prefix: str = '', ) -> None: ''' Raise an exception unless the passed object is an **isinstanceable type** (i.e., standard class passable as the second parameter to the :func:`isinstance` builtin and thus typically *not* compliant with annotation-centric PEPs). This validator is effectively (but technically *not*) memoized. See the :func:`beartype._util.hint.utilhinttest.die_unless_hint` validator. Parameters ---------- hint : type Object to be validated. exception_cls : Optional[type] Type of the exception to be raised by this function. Defaults to :class:`BeartypeDecorHintNonpepException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Raises ------ BeartypeDecorHintPep3119Exception If this object is *not* an isinstanceable class (i.e., class passable as the second argument to the :func:`isinstance` builtin). exception_cls If this object is a PEP-compliant type hint. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.utilpeptest import die_if_hint_pep # If this object is a PEP-compliant type hint, raise an exception. die_if_hint_pep( hint=hint, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # Else, this object is *NOT* a PEP-noncompliant type hint. # # If this object is *NOT* an isinstanceable class, raise an exception. Note # that this validation is typically slower than the prior validation and # thus intentionally performed last. die_unless_type_isinstanceable( cls=hint, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # If this object is an isinstanceable class. #FIXME: Unit test this function with respect to tuples containing #non-isinstanceable classes. #FIXME: Optimize both this and the related _is_hint_nonpep_tuple() tester #defined below. The key realization here is that EAFP is *MUCH* faster in this #specific case than iteration. Why? Because iteration is guaranteed to #internally raise a stop iteration exception, whereas EAFP only raises an #exception if this tuple is invalid, in which case efficiency is no longer a #concern. So, what do we do instead? Simple. We internally refactor: #* If "is_str_valid" is True, we continue to perform the existing # implementation of both functions. *shrug* #* Else, we: # * Perform a new optimized EAFP-style isinstance() check resembling that # performed by die_unless_type_isinstanceable(). # * Likewise for _is_hint_nonpep_tuple() vis-a-vis is_type_isinstanceable(). #Fortunately, tuple unions are now sufficiently rare in the wild (i.e., in #real-world use cases) that this mild inefficiency probably no longer matters. #FIXME: Indeed! Now that we have the die_unless_object_isinstanceable() #validator, this validator should reduce to efficiently calling #die_unless_object_isinstanceable() directly if "is_str_valid" is False. #die_unless_object_isinstanceable() performs the desired EAFP-style #isinstance() check in an optimally efficient manner. def die_unless_hint_nonpep_tuple( # Mandatory parameters. hint: object, # Optional parameters. is_str_valid: bool = False, exception_cls: TypeException = BeartypeDecorHintNonpepException, exception_prefix: str = '', ) -> None: ''' Raise an exception unless the passed object is a **PEP-noncompliant tuple** (i.e., :mod:`beartype`-specific tuple of one or more PEP-noncompliant types *not* compliant with annotation-centric PEPs). This validator is effectively (but technically *not*) memoized. See the :func:`beartype._util.hint.utilhinttest.die_unless_hint` validator. Parameters ---------- hint : object Object to be validated. is_str_valid : bool, optional :data:`True` only if this function permits this tuple to contain strings. Defaults to :data:`False`. If this boolean is: * :data:`True`, this tuple is valid only when containing classes and/or classnames. * :data:`False`, this object is valid only when containing classes. exception_cls : type, optional Type of the exception to be raised by this function. Defaults to :class:`BeartypeDecorHintNonpepException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Raises ------ exception_cls If this object is neither: * A non-:mod:`typing` type (i.e., class *not* defined by the :mod:`typing` module, whose public classes are used to instantiate PEP-compliant type hints or objects satisfying such hints that typically violate standard class semantics and thus require PEP-specific handling). * A **non-empty tuple** (i.e., semantic union of types) containing one or more: * Non-:mod:`typing` types. * If ``is_str_valid``, **strings** (i.e., forward references specified as either fully-qualified or unqualified classnames). ''' # If this object is a tuple union, reduce to a noop. # # Note that this memoized call is intentionally passed positional rather # than keyword parameters to maximize efficiency. if _is_hint_nonpep_tuple(hint, is_str_valid): return # Else, this object is *NOT* a tuple union. In this case, subsequent logic # raises an exception specific to the passed parameters. # # Note that the prior call has already validated "is_str_valid". assert isinstance(exception_cls, type), f'{repr(exception_cls)} not type.' assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # BEGIN: Synchronize changes here with the _is_hint_nonpep_tuple() tester. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # If this object is *NOT* a tuple, raise an exception. if not isinstance(hint, tuple): raise exception_cls( f'{exception_prefix}type hint {repr(hint)} not tuple.') # Else, this object is a tuple. # # If this tuple is empty, raise an exception. elif not hint: raise exception_cls(f'{exception_prefix}tuple type hint empty.') # Else, this tuple is non-empty. # For each item of this tuple... for hint_item in hint: # Duplicate the above logic. For negligible efficiency gains (and more # importantly to avoid exhausting the stack), avoid calling this # function recursively to do so. *shrug* # If this item is a class... if isinstance(hint_item, type): # If this class is *NOT* isinstanceable, raise an exception. die_unless_type_isinstanceable( cls=hint_item, exception_prefix=exception_prefix, exception_cls=exception_cls, ) # Else, this item is *NOT* a class. # # If this item is a forward reference... elif isinstance(hint_item, str): # If forward references are unsupported, raise an exception. if not is_str_valid: raise exception_cls( f'{exception_prefix}tuple type hint {repr(hint)} ' f'forward reference "{hint_item}" unsupported.' ) # Else, silently accept this item. # Else, this item is neither a class nor forward reference. Ergo, # this item is *NOT* a PEP-noncompliant type hint. In this case, # raise an exception whose message contextually depends on whether # forward references are permitted or not. else: raise exception_cls( f'{exception_prefix}tuple type hint {repr(hint)} ' f'item {repr(hint_item)} invalid ' f'{"neither type nor string" if is_str_valid else "not type"}.' ) # ....................{ TESTERS }.................... def is_hint_nonpep( # Mandatory parameters. hint: object, # Optional parameters. is_str_valid: bool = False, ) -> bool: ''' :data:`True` only if the passed object is a **PEP-noncompliant type hint** (i.e., :mod:`beartype`-specific annotation *not* compliant with annotation-centric PEPs). This tester is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object Object to be inspected. is_str_valid : bool, optional :data:`True` only if this function permits this tuple to contain strings. Defaults to :data:`False`. If this boolean is: * :data:`True`, this tuple is valid only when containing classes and/or classnames. * :data:`False`, this object is valid only when containing classes. Returns ------- bool :data:`True` only if this object is either: * A non-:mod:`typing` type (i.e., class *not* defined by the :mod:`typing` module, whose public classes are used to instantiate PEP-compliant type hints or objects satisfying such hints that typically violate standard class semantics and thus require PEP-specific handling). * A **non-empty tuple** (i.e., semantic union of types) containing one or more: * Non-:mod:`typing` types. * If ``is_str_valid``, **strings** (i.e., forward references specified as either fully-qualified or unqualified classnames). ''' assert isinstance(is_str_valid, bool), f'{repr(is_str_valid)} not boolean.' #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # BEGIN: Synchronize changes here with die_unless_hint_nonpep() above. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Return true only if either... return ( # If this object is a class, return true only if this is *NOT* a # PEP-compliant class, in which case this *MUST* be a PEP-noncompliant # class by definition. _is_hint_nonpep_type(hint) if isinstance(hint, type) else # Else, this object is *NOT* a class. # # If this object is a tuple, return true only if this tuple contains # only one or more caller-permitted forward references and # PEP-noncompliant classes. _is_hint_nonpep_tuple(hint, is_str_valid) if isinstance(hint, tuple) # Else, this object is neither a class nor tuple. Return false, as this # object *CANNOT* be PEP-noncompliant. else False ) # ....................{ TESTERS ~ private }.................... @callable_cached def _is_hint_nonpep_tuple( # Mandatory parameters. hint: object, # Optional parameters. is_str_valid: bool = False, ) -> bool: ''' :data:`True` only if the passed object is a PEP-noncompliant non-empty tuple of one or more types. This tester is memoized for efficiency. Parameters ---------- hint : object Object to be inspected. is_str_valid : bool, optional :data:`True` only if this function permits this tuple to contain strings. Defaults to :data:`False`. If this boolean is: * :data:`True`, this tuple is valid only when containing classes and/or classnames. * :data:`False`, this object is valid only when containing classes. Returns ------- bool :data:`True` only if this object is a **non-empty tuple** (i.e., semantic union of types) containing one or more: * Non-:mod:`typing` types. * If ``is_str_valid``, **strings** (i.e., forward references specified as either fully-qualified or unqualified classnames). ''' assert isinstance(is_str_valid, bool), f'{repr(is_str_valid)} not boolean.' #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # BEGIN: Synchronize changes here with die_unless_hint_nonpep() above. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Return true only if this object is... return ( # A tuple *AND*... isinstance(hint, tuple) and # This tuple is non-empty *AND*... len(hint) > 0 and # Each item of this tuple is either a caller-permitted forward # reference *OR* an isinstanceable class. all( is_type_isinstanceable(hint_item) if isinstance(hint_item, type) else is_str_valid if isinstance(hint_item, str) else False for hint_item in hint ) ) def _is_hint_nonpep_type(hint: object) -> bool: ''' :data:`True` only if the passed object is a PEP-noncompliant type. This tester is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object Object to be inspected. Returns ------- bool :data:`True` only if this object is a PEP-noncompliant type. ''' #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # BEGIN: Synchronize changes here with die_unless_hint_nonpep() above. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Avoid circular import dependencies. from beartype._util.hint.pep.utilpeptest import is_hint_pep # Return true only if this object is isinstanceable and *NOT* a # PEP-compliant class, in which case this *MUST* be a PEP-noncompliant # class by definition. return is_type_isinstanceable(hint) and not is_hint_pep(hint) beartype-0.18.5/beartype/_util/hint/pep/000077500000000000000000000000001461113517100200755ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/hint/pep/__init__.py000066400000000000000000000000001461113517100221740ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/hint/pep/proposal/000077500000000000000000000000001461113517100217345ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/hint/pep/proposal/__init__.py000066400000000000000000000000001461113517100240330ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/hint/pep/proposal/pep484/000077500000000000000000000000001461113517100227605ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/hint/pep/proposal/pep484/__init__.py000066400000000000000000000000001461113517100250570ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/hint/pep/proposal/pep484/utilpep484.py000066400000000000000000000270641461113517100252650ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`484`-compliant type hint utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.meta import URL_PEP585_DEPRECATIONS from beartype.roar import BeartypeDecorHintPep585DeprecationWarning from beartype._cave._cavefast import NoneType from beartype._data.hint.pep.datapeprepr import ( HINTS_PEP484_REPR_PREFIX_DEPRECATED) from beartype._util.error.utilerrwarn import issue_warning # Intentionally import PEP 484-compliant "typing" type hint factories rather # than possibly PEP 585-compliant "beartype.typing" type hint factories. from typing import ( Generic, Tuple, ) # ....................{ HINTS }.................... HINT_PEP484_TUPLE_EMPTY = Tuple[()] ''' :pep:`484`-compliant empty fixed-length tuple type hint. ''' # ....................{ TESTERS ~ ignorable }.................... #FIXME: Shift into a more appropriate submodule, please. def is_hint_pep484585_generic_ignorable(hint: object) -> bool: ''' :data:`True` only if the passed :pep:`484`- or :pep:`585`-compliant generic is ignorable. Specifically, this tester ignores *all* parametrizations of the :class:`typing.Generic` abstract base class (ABC) by one or more type variables. As the name implies, this ABC is generic and thus fails to impose any meaningful constraints. Since a type variable in and of itself also fails to impose any meaningful constraints, these parametrizations are safely ignorable in all possible contexts: e.g., .. code-block:: python from typing import Generic, TypeVar T = TypeVar('T') def noop(param_hint_ignorable: Generic[T]) -> T: pass This tester is intentionally *not* memoized (e.g., by the ``callable_cached`` decorator), as this tester is only safely callable by the memoized parent :func:`beartype._util.hint.utilhinttest.is_hint_ignorable` tester. Parameters ---------- hint : object Type hint to be inspected. Returns ------- bool :data:`True` only if this :pep:`484`-compliant type hint is ignorable. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.utilpepget import get_hint_pep_origin_or_none # print(f'Testing generic hint {repr(hint)} deep ignorability...') # If this generic is the "typing.Generic" superclass directly parametrized # by one or more type variables (e.g., "typing.Generic[T]"), return true. # # Note that we intentionally avoid calling the # get_hint_pep_origin_type_isinstanceable_or_none() function here, which has # been intentionally designed to exclude PEP-compliant type hints # originating from "typing" type origins for stability reasons. if get_hint_pep_origin_or_none(hint) is Generic: # print(f'Testing generic hint {repr(hint)} deep ignorability... True') return True # Else, this generic is *NOT* the "typing.Generic" superclass directly # parametrized by one or more type variables and thus *NOT* an ignorable # non-protocol. # # Note that this condition being false is *NOT* sufficient to declare this # hint to be unignorable. Notably, the origin type originating both # ignorable and unignorable protocols is "Protocol" rather than "Generic". # Ergo, this generic could still be an ignorable protocol. # print(f'Testing generic hint {repr(hint)} deep ignorability... False') #FIXME: Probably insufficient. *shrug* return False #FIXME: Remove this *AFTER* properly supporting type variables. For now, #ignoring type variables is required ta at least shallowly support generics #parametrized by one or more type variables. def is_hint_pep484_typevar_ignorable(hint: object) -> bool: ''' :data:`True` unconditionally. This tester currently unconditionally ignores *all* :pep:`484`-compliant type variables, which require non-trivial and currently unimplemented code generation support. This tester is intentionally *not* memoized (e.g., by the ``callable_cached`` decorator), as this tester is only safely callable by the memoized parent :func:`beartype._util.hint.utilhinttest.is_hint_ignorable` tester. Parameters ---------- hint : object Type hint to be inspected. Returns ------- bool :data:`True` only if this :pep:`484`-compliant type hint is ignorable. ''' # Ignore *ALL* PEP 484-compliant type variables. return True #FIXME: Shift into a more appropriate submodule, please. def is_hint_pep484604_union_ignorable(hint: object) -> bool: ''' :data:`True` only if the passed :pep:`484`- or :pep:`604`-compliant union is ignorable. Specifically, this tester ignores the :obj:`typing.Optional` or :obj:`typing.Union` singleton subscripted by one or more ignorable type hints (e.g., ``typing.Union[typing.Any, bool]``). Why? Because unions are by definition only as narrow as their widest child hint. However, shallowly ignorable type hints are ignorable precisely because they are the widest possible hints (e.g., :class:`object`, :attr:`typing.Any`), which are so wide as to constrain nothing and convey no meaningful semantics. A union of one or more shallowly ignorable child hints is thus the widest possible union, which is so wide as to constrain nothing and convey no meaningful semantics. Since there exist a countably infinite number of possible :data:`Union` subscriptions by one or more ignorable type hints, these subscriptions *cannot* be explicitly listed in the :data:`beartype._data.hint.pep.datapeprepr.HINTS_REPR_IGNORABLE_SHALLOW` frozenset. Instead, these subscriptions are dynamically detected by this tester at runtime and thus referred to as **deeply ignorable type hints.** This tester is intentionally *not* memoized (e.g., by the ``callable_cached`` decorator), as this tester is only safely callable by the memoized parent :func:`beartype._util.hint.utilhinttest.is_hint_ignorable` tester. Parameters ---------- hint : object Type hint to be inspected. Returns ------- bool :data:`True` only if this :pep:`484`-compliant type hint is ignorable. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.utilpepget import get_hint_pep_args from beartype._util.hint.utilhinttest import is_hint_ignorable # Return true only if one or more child hints of this union are recursively # ignorable. See the function docstring. return any( is_hint_ignorable(hint_child) for hint_child in get_hint_pep_args(hint)) # ....................{ REDUCERS }.................... def reduce_hint_pep484_deprecated( hint: object, exception_prefix : str, *args, **kwargs) -> object: ''' Preserve the passed :pep:`484`-compliant type hint as is while emitting one non-fatal deprecation warning for this type hint if **deprecated** (i.e., obsoleted by an equivalent :pep:`585`-compliant type hint *and* the active Python interpreter targets Python >= 3.9). While *not* explicitly defined by the :mod:`typing` module, :pep:`484` explicitly supports this singleton: When used in a type hint, the expression :data:`None` is considered equivalent to ``type(None)``. This reducer is intentionally *not* memoized (e.g., by the ``callable_cached`` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object Type hint to be reduced. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the warning message. Defaults to the empty string. All remaining passed arguments are silently ignored. Returns ------- object This hint unmodified. Warns ----- BeartypeDecorHintPep585DeprecationWarning If this :pep:`484`-compliant type hint is deprecated by :pep:`585` *and* the active Python interpreter targets Python >= 3.9. ''' assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') # print(f'Testing PEP 484 type hint {repr(hint)} for PEP 585 deprecation...') # print(f'{HINTS_PEP484_REPR_PREFIX_DEPRECATED}') # Avoid circular import dependencies. from beartype._util.hint.utilhintget import get_hint_repr # Machine-readable representation of this hint. hint_repr = get_hint_repr(hint) # Substring of the machine-readable representation of this hint preceding # the first "[" delimiter if this representation contains that delimiter # *OR* this representation as is otherwise. # # Note that the str.partition() method has been profiled to be the optimally # efficient means of parsing trivial prefixes. hint_repr_bare, _, _ = hint_repr.partition('[') # If this hint is a PEP 484-compliant type hint originating from an origin # type (e.g., "typing.List[int]"), this hint has been deprecated by the # equivalent PEP 585-compliant type hint (e.g., "list[int]"). In this case, # emit a non-fatal PEP 585-specific deprecation warning. if hint_repr_bare in HINTS_PEP484_REPR_PREFIX_DEPRECATED: issue_warning( cls=BeartypeDecorHintPep585DeprecationWarning, message=( f'{exception_prefix}PEP 484 type hint {repr(hint)} ' f'deprecated by PEP 585. ' f'This hint is scheduled for removal in the first Python ' f'version released after October 5th, 2025. To resolve this, ' f'import this hint from "beartype.typing" rather than "typing". ' f'For further commentary and alternatives, see also:\n' f' {URL_PEP585_DEPRECATIONS}' ), ) # Else, this hint is *NOT* deprecated. In this case, reduce to a noop. # Preserve this hint as is, regardless of deprecation. return hint # Note that this reducer is intentionally typed as returning "type" rather than # "NoneType". While the former would certainly be preferable, mypy erroneously # emits false positives when this reducer is typed as returning "NoneType": # beartype/_util/hint/pep/proposal/pep484/utilpep484.py:190: error: Variable # "beartype._cave._cavefast.NoneType" is not valid as a type [valid-type] def reduce_hint_pep484_none(hint: object, *args, **kwargs) -> type: ''' Reduce the passed :pep:`484`-compliant :data:`None` type hint to the type of that type hint (i.e., the builtin :class:`types.NoneType` class). While *not* explicitly defined by the :mod:`typing` module, :pep:`484` explicitly supports this singleton: When used in a type hint, the expression :data:`None` is considered equivalent to ``type(None)``. This reducer is intentionally *not* memoized (e.g., by the ``callable_cached`` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object Type variable to be reduced. All remaining passed arguments are silently ignored. Returns ------- NoneType Type of the :data:`None` singleton. ''' assert hint is None, f'Type hint {hint} not "None" singleton.' # Unconditionally return the type of the "None" singleton. return NoneType beartype-0.18.5/beartype/_util/hint/pep/proposal/pep484/utilpep484generic.py000066400000000000000000000521341461113517100266160ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`484`-compliant **generic type hint utilities** (i.e., callables generically applicable to :pep:`484`-compliant generic classes). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeDecorHintPep484Exception from beartype.typing import ( Any, Generic, ) from beartype._data.hint.datahinttyping import TypeException from beartype._util.cache.utilcachecall import callable_cached from beartype._util.cls.utilclstest import is_type_subclass # ....................{ TESTERS }.................... def is_hint_pep484_generic(hint: object) -> bool: ''' :data:`True` only if the passed object is a :pep:`484`-compliant **generic** (i.e., object that may *not* actually be a class originally subclassing at least one PEP-compliant type hint defined by the :mod:`typing` module). Specifically, this tester returns :data:`True` only if this object was originally defined as a class subclassing a combination of: * At least one of: * The :pep:`484`-compliant :mod:`typing.Generic` superclass. * The :pep:`544`-compliant :mod:`typing.Protocol` superclass. * Zero or more non-class :mod:`typing` pseudo-superclasses (e.g., ``typing.List[int]``). * Zero or more other standard superclasses. This tester is intentionally *not* memoized (e.g., by the ``callable_cached`` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object Object to be inspected. Returns ------- bool :data:`True` only if this object is a :mod:`typing` generic. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.proposal.pep484585.utilpep484585generic import ( get_hint_pep484585_generic_type_or_none) # If this hint is *NOT* a class, this hint is *NOT* an unsubscripted # generic but could still be a subscripted generic (i.e., generic # subscripted by one or more PEP-compliant child type hints). To # decide, reduce this hint to the object originating this hint if any, # enabling the subsequent test to test whether this origin object is an # unsubscripted generic, which would then imply this hint to be a # subscripted generic. If this strikes you as insane, you're not alone. hint = get_hint_pep484585_generic_type_or_none(hint) # Return true only if this hint is a subclass of the "typing.Generic" # abstract base class (ABC), in which case this hint is a user-defined # generic. # # Note that this test is robust against edge cases, as the "typing" # module guarantees all user-defined classes subclassing one or more # "typing" pseudo-superclasses to subclass the "typing.Generic" # abstract base class (ABC) regardless of whether those classes did so # explicitly. How? By type erasure, of course, the malignant gift that # keeps on giving: # >>> import typing as t # >>> class MuhList(t.List): pass # >>> MuhList.__orig_bases__ # (typing.List) # >>> MuhList.__mro__ # (__main__.MuhList, list, typing.Generic, object) # # Note that: # * This issubclass() call implicitly performs a surprisingly # inefficient search over the method resolution order (MRO) of all # superclasses of this hint. In theory, the cost of this search might # be circumventable by observing that this ABC is expected to reside # at the second-to-last index of the tuple exposing this MRO far all # generics by virtue of fragile implementation details violating # privacy encapsulation. In practice, this codebase is already # fragile enough. # * The following logic superficially appears to implement the same # test *WITHOUT* the onerous cost of a search: # return len(get_hint_pep484_generic_bases_unerased_or_none(hint)) > 0 # Why didn't we opt for that, then? Because this tester is routinely # passed objects that *CANNOT* be guaranteed to be PEP-compliant. # Indeed, the high-level is_hint_pep() tester establishing the # PEP-compliance of arbitrary objects internally calls this # lower-level tester to do so. Since the # get_hint_pep484_generic_bases_unerased_or_none() getter internally # reduces to returning the tuple of the general-purpose # "__orig_bases__" dunder attribute formalized by PEP 560, testing # whether that tuple is non-empty or not in no way guarantees this # object to be a PEP-compliant generic. return is_type_subclass(hint, Generic) # type: ignore[arg-type] # ....................{ GETTERS }.................... @callable_cached def get_hint_pep484_generic_base_erased_from_unerased(hint: Any) -> type: ''' Erased superclass originating the passed :pep:`484`-compliant **unerased pseudo-superclass** (i.e., :mod:`typing` object originally listed as a superclass prior to its implicit type erasure by the :mod:`typing` module). This getter is intentionally *not* memoized (e.g., by the ``callable_cached`` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object :pep:`484`-compliant unerased pseudo-superclass to be reduced to its erased superclass. Returns ------- type Erased superclass originating this :pep:`484`-compliant unerased pseudo-superclass. Raises ------ BeartypeDecorHintPep484Exception if this object is *not* a :pep:`484`-compliant unerased pseudo-superclass. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.utilpepget import get_hint_pep_origin_or_none # Erased superclass originating this unerased pseudo-superclass if any *OR* # "None" otherwise. hint_origin_type = get_hint_pep_origin_or_none(hint) # If this hint originates from *NO* such superclass, raise an exception. if hint_origin_type is None: raise BeartypeDecorHintPep484Exception( f'Unerased PEP 484 generic or PEP 544 protocol {repr(hint)} ' f'originates from no erased superclass.' ) # Else, this hint originates from such a superclass. # Return this superclass. return hint_origin_type @callable_cached def get_hint_pep484_generic_bases_unerased( # Mandatory parameters. hint: Any, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintPep484Exception, exception_prefix: str = '', ) -> tuple: ''' Tuple of all unerased :mod:`typing` **pseudo-superclasses** (i.e., :mod:`typing` objects originally listed as superclasses prior to their implicit type erasure under :pep:`560`) of the passed :pep:`484`-compliant **generic** (i.e., class subclassing at least one non-class :mod:`typing` object). This getter is memoized for efficiency. Parameters ---------- hint : object Object to be inspected. exception_cls : TypeException Type of exception to be raised. Defaults to :exc:`BeartypeDecorHintPep484Exception`. exception_prefix : str, optional Human-readable substring prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- tuple Tuple of the one or more unerased pseudo-superclasses of this :mod:`typing` generic. Specifically: * If this generic defines an ``__orig_bases__`` dunder instance variable, the value of that variable as is. * Else, the value of the ``__mro__`` dunder instance variable stripped of all ignorable classes conveying *no* semantic meaning, including: * This generic itself. * The :class:`object` root superclass. Raises ------ exception_cls If this hint is either: * *Not* a :mod:`typing` generic. * A :mod:`typing` generic that erased *none* of its superclasses but whose method resolution order (MRO) lists strictly less than four classes. Valid :pep:`484`-compliant generics should list at least four classes, including (in order): #. This class itself. #. The one or more :mod:`typing` objects directly subclassed by this generic. #. The :class:`typing.Generic` superclass. #. The zero or more non-:mod:`typing` superclasses subsequently subclassed by this generic (e.g., :class:`abc.ABC`). #. The :class:`object` root superclass. See Also -------- :func:`beartype._util.hint.pep.proposal.pep484585.utilpep484585generic.get_hint_pep484585_generic_bases_unerased` Further details. ''' #FIXME: This tuple appears to be implemented erroneously -- at least under #Python 3.7, anyway. Although this tuple is implemented correctly for the #common case of user-defined types directly subclassing "typing" types, #this tuple probably is *NOT* implemented correctly for the edge case of #user-defined types indirectly subclassing "typing" types: e.g., # # >>> import collections.abc, typing # >>> T = typing.TypeVar('T') # >>> class Direct(collections.abc.Sized, typing.Generic[T]): pass # >>> Direct.__orig_bases__ # (collections.abc.Sized, typing.Generic[~T]) # >>> class Indirect(collections.abc.Container, Direct): pass # >>> Indirect.__orig_bases__ # (collections.abc.Sized, typing.Generic[~T]) # #*THAT'S COMPLETELY INSANE.* Clearly, their naive implementation failed to #account for actual real-world use cases. # #On the bright side, the current implementation prevents us from actually #having to perform a breadth-first traversal of all original superclasses #of this class in method resolution order (MRO). On the dark side, it's #pants-on-fire balls -- but there's not much we can do about that. *sigh* # #If we ever need to perform that breadth-first traversal, resurrect this: # # # If this class was *NOT* subject to type erasure, reduce to a noop. # if not hint_bases: # return hint_bases # # # Fixed list of all typing super attributes to be returned. # superattrs = acquire_fixed_list(FIXED_LIST_SIZE_MEDIUM) # # # 0-based index of the last item of this list. # superattrs_index = 0 # # # Fixed list of all transitive superclasses originally listed by this # # class iterated in method resolution order (MRO). # hint_orig_mro = acquire_fixed_list(FIXED_LIST_SIZE_MEDIUM) # # # 0-based indices of the current and last items of this list. # hint_orig_mro_index_curr = 0 # hint_orig_mro_index_last = 0 # # # Initialize this list with the tuple of all direct superclasses of this # # class, which iteration then expands to all transitive superclasses. # hint_orig_mro[:len(hint_bases)] = hint_bases # # # While the heat death of the universe has been temporarily forestalled... # while (True): # # Currently visited superclass of this class. # hint_base = hint_orig_mro[hint_orig_mro_index_curr] # # # If this superclass is a typing attribute... # if is_hint_pep_type_typing(hint_base): # # Avoid inserting this attribute into the "hint_orig_mro" list. # # Most typing attributes are *NOT* actual classes and those that # # are have no meaningful public superclass. Ergo, iteration # # terminates with typing attributes. # # # # Insert this attribute at the current item of this list. # superattrs[superattrs_index] = hint_base # # # Increment this index to the next item of this list. # superattrs_index += 1 # # # If this class subclasses more than the maximum number of "typing" # # attributes supported by this function, raise an exception. # if superattrs_index >= FIXED_LIST_SIZE_MEDIUM: # raise BeartypeDecorHintPep560Exception( # '{} PEP type {!r} subclasses more than ' # '{} "typing" types.'.format( # exception_prefix, # hint, # FIXED_LIST_SIZE_MEDIUM)) # # Else, this superclass is *NOT* a typing attribute. In this case... # else: # # Tuple of all direct superclasses originally listed by this class # # prior to PEP 484 type erasure if any *OR* the empty tuple # # otherwise. # hint_base_bases = getattr(hint_base, '__orig_bases__') # # #FIXME: Implement breadth-first traversal here. # # # Tuple sliced from the prefix of this list assigned to above. # superattrs_tuple = tuple(superattrs[:superattrs_index]) # # # Release and nullify this list *AFTER* defining this tuple. # release_fixed_list(superattrs) # del superattrs # # # Return this tuple as is. # return superattrs_tuple # #Also resurrect this docstring snippet: # # Raises # ---------- # BeartypeDecorHintPep560Exception # If this object defines the ``__orig_bases__`` dunder attribute but that # attribute transitively lists :data:`FIXED_LIST_SIZE_MEDIUM` or more :mod:`typing` # attributes. # #Specifically: # * Acquire a fixed list of sufficient size (e.g., 64). We probably want # to make this a constant in "utilcachelistfixedpool" for reuse # everywhere, as this is clearly becoming a common idiom. # * Slice-assign "__orig_bases__" into this list. # * Maintain two simple 0-based indices into this list: # * "bases_index_curr", the current base being visited. # * "bases_index_last", the end of this list also serving as the list # position to insert newly discovered bases at. # * Iterate over this list and keep slice-assigning from either # "__orig_bases__" (if defined) or "__mro__" (otherwise) into # "list[bases_index_last:len(__orig_bases__)]". Note that this has the # unfortunate disadvantage of temporarily iterating over duplicates, # but... *WHO CARES.* It still works and we subsequently # eliminate duplicates at the end. # * Return a frozenset of this list, thus implicitly eliminating # duplicate superclasses. # Avoid circular import dependencies. from beartype._util.hint.pep.proposal.pep484585.utilpep484585generic import ( get_hint_pep484585_generic_type_or_none) # If this hint is *NOT* a class, reduce this hint to the object originating # this hint if any. See is_hint_pep484_generic() for details. hint = get_hint_pep484585_generic_type_or_none(hint) # If this hint is *NOT* a PEP 484-compliant generic, raise an exception. if not is_hint_pep484_generic(hint): raise exception_cls( f'{exception_prefix}type hint {repr(hint)} neither ' f'PEP 484 generic nor PEP 544 protocol.' ) # Else, this hint is a PEP 484-compliant generic. # Unerased pseudo-superclasses of this generic if any *OR* "None" otherwise # (e.g., if this generic is a single-inherited protocol). hint_bases = getattr(hint, '__orig_bases__', None) # If this generic erased its superclasses, return these superclasses as is. if hint_bases is not None: return hint_bases # Else, this generic erased *NONE* of its superclasses. These superclasses # *MUST* by definition be unerased and thus safely returnable as is. # Unerased superclasses of this generic defined by the method resolution # order (MRO) for this generic. hint_bases = hint.__mro__ # If this MRO lists strictly less than four classes, raise an exception. # The MRO for any unerased generic should list at least four classes: # * This class itself. # * The one or more "typing" objects directly subclassed by this generic. # * The "typing.Generic" superclass. Note that this superclass is typically # but *NOT* necessarily the second-to-last superclass. Since this ad-hoc # heuristic is *NOT* an actual constraint, we intentionally avoid # asserting this to be the case. An example in which "typing.Generic" is # *NOT* the second-to-last superclass is: # class ProtocolCustomSuperclass(Protocol): pass # class ProtocolCustomABC(ProtocolCustomSuperclass, ABC): pass # * The "object" root superclass. if len(hint_bases) < 4: raise exception_cls( f'{exception_prefix}PEP 484 generic {repr(hint)} ' f'subclasses less than four superclasses {repr(hint_bases)}.' ) # Else, this MRO lists at least four classes. # # If any class listed by this MRO fails to comply with the above # expectations, raise an exception. elif hint_bases[0] is not hint: raise exception_cls( f'{exception_prefix}PEP 484 generic {repr(hint)} ' f'first superclass {repr(hint_bases[0])} != {repr(hint)}.' ) elif hint_bases[-1] is not object: raise exception_cls( f'{exception_prefix}PEP 484 generic {repr(hint)} ' f'last superclass {repr(hint_bases[-1])} != {repr(object)}.' ) # Else, all classes listed by this MRO comply with the above expectations. # Return a slice of this tuple preserving *ONLY* the non-ignorable # superclasses listed by this tuple for conformance with the tuple returned # by this getter from the "__orig_bases__", which similarly lists *ONLY* # non-ignorable superclasses. Specifically, strip from this tuple: # * This class itself. # * The "object" root superclass. # # Ideally, the ignorable "(beartype.|)typing.(Generic|Protocol)" # superclasses would also be stripped. Sadly, as exemplified by the above # counter-example, those superclasses are *NOT* guaranteed to occupy the # third- and second-to-last positions (respectively) of this tuple. Ergo, # stripping these superclasses safely would require an inefficient # iterative O(n) search across this tuple for those superclasses. Instead, # we defer ignoring these superclasses to the caller -- which necessarily # already (and hopefully efficiently) ignores ignorable superclasses. return hint_bases[1:-1] # ....................{ REDUCERS }.................... def reduce_hint_pep484_generic( hint: object, exception_prefix: str, **kwargs) -> object: ''' Reduce the passed :pep:`484`-compliant **generic** (i.e., object that may *not* actually be a class originally subclassing at least one PEP-compliant type hint defined by the :mod:`typing` module) to a more suitable type hint better supported by :mod:`beartype` if necessary. This reducer is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object Generic to be reduced. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. All remaining passed arguments are silently ignored. Returns ------- object More suitable type hint better supported by :mod:`beartype`. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.proposal.utilpep544 import ( is_hint_pep484_generic_io, reduce_hint_pep484_generic_io_to_pep544_protocol, ) # If this hint is a PEP 484-compliant IO generic base class *AND* the active # Python interpreter targets Python >= 3.8 and thus supports PEP # 544-compliant protocols, reduce this functionally useless hint to the # corresponding functionally useful beartype-specific PEP 544-compliant # protocol implementing this hint. # # IO generic base classes are extremely rare and thus detected even later. # # Note that PEP 484-compliant IO generic base classes are technically # usable under Python < 3.8 (e.g., by explicitly subclassing those classes # from third-party classes). Ergo, we can neither safely emit warnings nor # raise exceptions on visiting these classes under *ANY* Python version. if is_hint_pep484_generic_io(hint): hint = reduce_hint_pep484_generic_io_to_pep544_protocol( hint=hint, exception_prefix=exception_prefix) # Else, this hint is either *NOT* a PEP 484-compliant IO generic base class # *OR* is but the active Python interpreter targets Python < 3.8 and thus # fails to support PEP 544-compliant protocols. In either case, preserve # this hint as is. # Return this possibly reduced hint. return hint beartype-0.18.5/beartype/_util/hint/pep/proposal/pep484/utilpep484namedtuple.py000066400000000000000000000143641461113517100273430ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`484`-compliant **named tuple utilities** (i.e., callables generically applicable to :pep:`484`-compliant named tuples -- which is to say, instances of concrete subclasses of the standard :attr:`typing.NamedTuple` superclass). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._util.cls.utilclstest import is_type_subclass_proper # from beartype.roar import BeartypeDecorHintPep484Exception # from beartype.typing import Any # from beartype._data.hint.pep.sign.datapepsigns import HintSignNewType # from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_10 # from types import FunctionType # ....................{ TESTERS }.................... #FIXME: Unit test us up, please. #FIXME: Actually call this tester in the get_hint_pep_sign_or_none() getter to #map "typing.NamedTuple" subclasses to the "HintSignNamedTuple" sign, please. #FIXME: Actually type-check type hints identified by the "HintSignNamedTuple" #sign. Specifically, for each "typing.NamedTuple" subclass identified by that #sign, type-check that subclass as follows: #* If that subclass is decorated by @beartype, reduce to the standard trivial # isinstance() check. Since @beartype already type-checks instances of that # subclass on instantiation, *NO* further type-checking is required or desired. #* Else, that subclass is *NOT* decorated by @beartype. In this case, matters # become considerably non-trivial. Why? Because: # * This situation commonly arises when type-checking "typing.NamedTuple" # subclasses *NOT* under user control (e.g., defined by upstream third-party # packages in an app stack). Since these subclasses are *NOT* under user # control, there exists *NO* safe means for @beartype to monkey-patch these # subclasses with itself. Ergo, instances of these subclasses are guaranteed # to *NOT* be type-checked at instantiation time. # * The prior point implies that @beartype must instead type-check instances of # these subclasses at @beartype call time. However, the naive approach to # doing so is likely to prove inefficient. The naive approach is simply to # type-check *ALL* fields of these instances *EVERY* time these instances are # type-checked at @beartype call time. Since these fields could themselves # refer to other "typing.NamedTuple" subclasses, combinatorial explosion # violating O(1) constraints becomes a real possibility here. # * *RECURSION.* Both direct and indirect recursion are feasible here. Both # require PEP 563 and are thus unlikely. Nonetheless: # * Direct recursion occurs under PEP 563 as follows: # from __future__ import annotations # from typing import NamedTuple # # class DirectlyRecursiveNamedTuple(NamedTuple): # uhoh: DirectlyRecursiveNamedTuple # * Indirect recursion occurs as PEP 563 follows: # from typing import NamedTuple # # class IndirectlyRecursiveNamedTuple(NamedTuple): # uhoh: YetAnotherNamedTuple # # class YetAnotherNamedTuple(NamedTuple): # ohboy: IndirectlyRecursiveNamedTuple # #Guarding against both combinatorial explosion *AND* recursion is imperative. To #do so, we'll need to fundamentally refactor our existing breadth-first search #(BFS) over type hints into a new depth-first search (DFS) over type hints. #We've extensively documented this in the "beartype._check.code.__init__" #submodule. Simply know that this will be non-trivial, albeit fun and needed! def is_hint_pep484_namedtuple_subclass(hint: object) -> bool: ''' ``True`` only if the passed object is a :pep:`484`-compliant **named tuple subclass** (i.e., concrete subclass of the standard :attr:`typing.NamedTuple` superclass). Note that the :attr:`typing.NamedTuple` attribute is *not* actually a superclass; that attribute only superficially masquerades (through inscrutable metaclass trickery) as a superclass. As one might imagine, detecting "subclasses" of a non-existent superclass is non-trivial. This tester is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object Object to be inspected. Returns ---------- bool ``True`` only if this object is a :pep:`484`-compliant named tuple subclass. ''' # Return true only if... return ( # This hint is a proper tuple subclass (i.e., subclass of the builtin # "tuple" type but *NOT* that type itself) *AND*... is_type_subclass_proper(hint, tuple) and #FIXME: Implement us up, please. To do so efficiently, we'll probably #want to: #* Declare a private global frozenset of the names of all uniquely # identifying "typing.NamedTuple" attributes: e.g., # _NAMEDTUPLE_UNIQUE_ATTR_NAMES = frozenset(( # # "typing.NamedTuple"-specific quasi-public attributes. # '__annotations__', # # # "collections.namedtuple"-specific quasi-public attributes. # '_asdict', # '_field_defaults', # '_fields', # '_make', # '_replace', # )) #* Efficiently take the set intersection of that frozenset and # "dir(tuple)". If that intersection is non-empty, then this type is # *PROBABLY* a "typing.NamedTuple" subclass. # #Note that there does exist an alternative. Sadly, that alternative #requires an O(n) test and is thus non-ideal. Nonetheless: # typing.NamedTuple in getattr(hint, '__orig_bases__', ()) # #That *DOES* have the advantage of being deterministic. But the above #set intersection test is mostly deterministic and considerably #faster... we think. Actually, is it? We have *NO* idea. Perhaps we #should simply opt for the simplistic and deterministic O(n) approach. True ) beartype-0.18.5/beartype/_util/hint/pep/proposal/pep484/utilpep484newtype.py000066400000000000000000000243141461113517100266740ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`484`-compliant **new type hint utilities** (i.e., callables generically applicable to :pep:`484`-compliant types). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeDecorHintPep484Exception from beartype.typing import Any from beartype._data.hint.pep.sign.datapepsigns import HintSignNewType from beartype._util.cache.utilcachecall import callable_cached from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_10 from types import FunctionType # ....................{ TESTERS }.................... def is_hint_pep484_newtype_ignorable(hint: object) -> bool: ''' :data:`True` only if the passed :pep:`484`-compliant :obj:`typing.NewType`-style type alias is ignorable. Specifically, this tester ignores the :obj:`typing.NewType` factory passed an ignorable child type hint. Unlike most :mod:`typing` constructs, that factory does *not* cache the objects it returns: e.g., .. code-block:: python >>> from typing import NewType >>> NewType('TotallyNotAStr', str) is NewType('TotallyNotAStr', str) False Since this implies every call to ``NewType({same_name}, object)`` returns a new closure, the *only* means of ignoring ignorable new type aliases is dynamically within this function. This tester is intentionally *not* memoized (e.g., by the ``callable_cached`` decorator), as this tester is only safely callable by the memoized parent :func:`beartype._util.hint.utilhinttest.is_hint_ignorable` tester. Parameters ---------- hint : object Type hint to be inspected. Returns ------- bool :data:`True` only if this :pep:`484`-compliant type hint is ignorable. ''' # Avoid circular import dependencies. from beartype._util.hint.utilhinttest import is_hint_ignorable # Return true only if this hint aliases an ignorable child type hint. return is_hint_ignorable(get_hint_pep484_newtype_alias(hint)) # If the active Python interpreter targets Python >= 3.10 and thus defines # "typing.NewType" type hints as instances of that class, implement this tester # unique to prior Python versions to raise an exception. if IS_PYTHON_AT_LEAST_3_10: def is_hint_pep484_newtype_pre_python310(hint: object) -> bool: raise BeartypeDecorHintPep484Exception( 'is_hint_pep484_newtype_pre_python310() assumes Python < 3.10, ' 'but current Python interpreter targets Python >= 3.10.' ) # Else, the active Python interpreter targets Python < 3.10 and thus defines # "typing.NewType" type hints as closures returned by that function. Since # these closures are sufficiently dissimilar from all other type hints to # require unique detection, implement this tester unique to this obsolete # Python version to detect these closures. else: def is_hint_pep484_newtype_pre_python310(hint: object) -> bool: # Return true only if... return ( # This hint is a pure-Python function *AND*... # # Note that we intentionally do *NOT* call the callable() builtin # here, as that builtin erroneously returns false positives for # non-standard classes defining the __call__() dunder method to # unconditionally raise exceptions. Critically, this includes most # PEP 484-compliant type hints, which naturally fail to define both # the "__module__" *AND* "__qualname__" dunder instance variables # accessed below. Shoot me now, fam. isinstance(hint, FunctionType) and # This callable is a closure created and returned by the # typing.NewType() function. Note that: # # * The "__module__" and "__qualname__" dunder instance variables # are *NOT* generally defined for arbitrary objects but are # specifically defined for callables. # * "__qualname__" is safely available under Python >= 3.3. # * This test derives from the observation that the concatenation # of this callable's "__qualname__" and "__module" dunder # instance variables suffices to produce a string unambiguously # identifying whether this hint is a "NewType"-generated closure: # >>> from typing import NewType # >>> UserId = t.NewType('UserId', int) # >>> UserId.__qualname__ # >>> 'NewType..new_type' # >>> UserId.__module__ # >>> 'typing' f'{hint.__module__}.{hint.__qualname__}'.startswith( 'typing.NewType.') ) is_hint_pep484_newtype_pre_python310.__doc__ = ''' :data:`True` only if the passed object is a Python < 3.10-specific :pep:`484`-compliant **new type** (i.e., closure created and returned by the :func:`typing.NewType` closure factory function). This tester is intentionally *not* memoized (e.g., by the ``callable_cached`` decorator), as the implementation trivially reduces to an efficient one-liner. Caveats ------- **New type aliases are a complete farce and thus best avoided.** Specifically, these PEP-compliant type hints are *not* actually types but rather **identity closures** that return their passed parameters as is. Instead, where distinct types are: * *Not* required, simply annotate parameters and return values with the desired superclasses. * Required, simply: * Subclass the desired superclasses as usual. * Annotate parameters and return values with those subclasses. Parameters ---------- hint : object Object to be inspected. Returns ------- bool :data:`True` only if this object is a Python < 3.10-specific :pep:`484`-compliant new type. ''' # ....................{ GETTERS }.................... #FIXME: Unit test us up, please. @callable_cached def get_hint_pep484_newtype_alias( hint: Any, exception_prefix: str = '') -> object: ''' Unaliased type hint (i.e., type hint that is *not* a :obj:`typing.NewType`) encapsulated by the passed **newtype** (i.e., object created and returned by the :pep:`484`-compliant :obj:`typing.NewType` type hint factory). This getter is memoized for efficiency. Parameters ---------- hint : object Object to be inspected. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- object Unaliased type hint encapsulated by this newtype. Raises ------ BeartypeDecorHintPep484Exception If this object is *not* a new type. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.utilpepget import ( get_hint_pep_sign, get_hint_pep_sign_or_none, ) # If this object is *NOT* a new type, raise an exception. if get_hint_pep_sign(hint) is not HintSignNewType: raise BeartypeDecorHintPep484Exception( f'{exception_prefix}type hint {repr(hint)} not ' f'PEP 484 "typing.NewType(...)" object.' ) # Else, this object is a new type. # While the Universe continues infinitely expanding... while True: # Reduce this new type to the type hint encapsulated by this new type, # which itself is possibly a nested new type. Oh, it happens. hint = hint.__supertype__ # If this type hint is *NOT* a nested new type, break this iteration. if get_hint_pep_sign_or_none(hint) is not HintSignNewType: break # Else, this type hint is a nested new type. In this case, continue # iteratively unwrapping this nested new type. # Return this unaliased type hint. return hint # ....................{ REDUCERS }.................... def reduce_hint_pep484_newtype( hint: object, exception_prefix: str, *args, **kwargs) -> object: ''' Reduce the passed **new type** (i.e., object created and returned by the :pep:`484`-compliant :func:`typing.NewType` type hint factory) to the **non-new type type hint** (i.e., PEP-compliant type hint that is *not* a new type) encapsulated by this new type. This reducer is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Caveats ------- **This reducer has worst-case linear time complexity** :math:`O(k)` for :math:`k` the number of **nested new types** (e.g., :math:`k = 2` for the doubly nested new type ``NewType('a', NewType('b', int))``) embedded within this new type. Pragmatically, this reducer has average-case constant time complexity :math:`O(1)`. Why? Because nested new types are extremely rare. Almost all real-world new types are non-nested. Indeed, it took three years for a user to submit an issue presenting the existence of a nested new type. Parameters ---------- hint : object Final type hint to be reduced. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. All remaining passed arguments are silently ignored. Returns ------- type Non-new type type hint encapsulated by this new type. ''' # Reduce this new type to the non-new type type hint encapsulated by this # new type. # # Note that: # * This reducer *CANNOT* be reduced to an efficient alias of the # get_hint_pep484_newtype_alias() getter, as this reducer accepts # ignorable arguments *NOT* accepted by that getter. # * get_hint_pep484_newtype_alias() is memoized and thus intentionally # called with positional arguments. return get_hint_pep484_newtype_alias(hint, exception_prefix) beartype-0.18.5/beartype/_util/hint/pep/proposal/pep484/utilpep484typevar.py000066400000000000000000000163051461113517100266740ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`484`-compliant **type variable utilities** (i.e., callables generically applicable to :pep:`484`-compliant type variables). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeDecorHintPep484Exception from beartype.typing import TypeVar from beartype._util.cache.utilcachecall import callable_cached # ....................{ GETTERS }.................... @callable_cached def get_hint_pep484_typevar_bound_or_none( hint: TypeVar, exception_prefix: str = '') -> object: ''' PEP-compliant type hint synthesized from all bounded constraints parametrizing the passed :pep:`484`-compliant **type variable** (i.e., :class:`typing.TypeVar` instance) if any *or* :data:`None` otherwise (i.e., if this type variable was parametrized by *no* bounded constraints). Specifically, if this type variable was parametrized by: #. One or more **constraints** (i.e., positional arguments passed by the caller to the :meth:`typing.TypeVar.__init__` call initializing this type variable), this getter returns a new **PEP-compliant union type hint** (i.e., :attr:`typing.Union` subscription) of those constraints. #. One **upper bound** (i.e., ``bound`` keyword argument passed by the caller to the :meth:`typing.TypeVar.__init__` call initializing this type variable), this getter returns that bound as is. #. Else, this getter returns the ``None`` singleton. Caveats ---------- **This getter treats constraints and upper bounds as semantically equivalent,** preventing callers from distinguishing between these two technically distinct variants of type variable metadata. For runtime type-checking purposes, type variable constraints and bounds are sufficiently similar as to be semantically equivalent for all intents and purposes. To simplify handling of type variables, this getter ambiguously aggregates both into the same tuple. For static type-checking purposes, type variable constraints and bounds are *still* sufficiently similar as to be semantically equivalent for all intents and purposes. Any theoretical distinction between the two is likely to be lost on *most* engineers, who tend to treat the two interchangeably. To quote :pep:`484`: ...type constraints cause the inferred type to be _exactly_ one of the constraint types, while an upper bound just requires that the actual type is a subtype of the boundary type. Inferred types are largely only applicable to static type-checkers, which internally assign type variables contextual types inferred from set and graph theoretic operations on the network of all objects (nodes) and callables (edges) relating those objects. Runtime type-checkers have *no* analogous operations, due to runtime space and time constraints. This getter is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator). If this type variable was parametrized by one or more constraints, the :attr:`typing.Union` type hint factory already caches these constraints; else, this getter performs no work. In any case, this getter effectively performs to work. Parameters ---------- hint : object :pep:`484`-compliant type variable to be inspected. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ---------- object Either: * If this type variable was parametrized by one or more constraints, a new PEP-compliant union type hint aggregating those constraints. * If this type variable was parametrized by an upper bound, that bound. * Else, :data:`None`. Raises ---------- BeartypeDecorHintPep484Exception if this object is *not* a :pep:`484`-compliant type variable. ''' # If this hint is *NOT* a type variable, raise an exception. if not isinstance(hint, TypeVar): raise BeartypeDecorHintPep484Exception( f'{exception_prefix}type hint {repr(hint)} ' f'not PEP 484 type variable.' ) # Else, this hint is a type variable. # If this type variable was parametrized by one or more constraints... if hint.__constraints__: # Avoid circular import dependencies. from beartype._util.hint.pep.proposal.pep484.utilpep484union import ( make_hint_pep484_union) # Create and return the PEP 484-compliant union of these constraints. return make_hint_pep484_union(hint.__constraints__) # Else, this type variable was parametrized by *NO* constraints. # # If this type variable was parametrized by an upper bound, return that # bound as is. elif hint.__bound__ is not None: return hint.__bound__ # Else, this type variable was parametrized by neither constraints *NOR* an # upper bound. # Return "None". return None # ....................{ REDUCERS }.................... #FIXME: Remove this function *AFTER* deeply type-checking type variables. def reduce_hint_pep484_typevar( hint: TypeVar, exception_prefix: str, *args, **kwargs) -> object: ''' Reduce the passed :pep:`484`-compliant **type variable** (i.e., :class:`typing.TypedDict` instance) to a lower-level type hint currently supported by :mod:`beartype`. This reducer is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object Type variable to be reduced. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. All remaining passed arguments are silently ignored. Returns ---------- object Lower-level type hint currently supported by :mod:`beartype`. ''' # PEP-compliant type hint synthesized from all bounded constraints # parametrizing this type variable if any *OR* "None" otherwise. # # Note that this function call is intentionally passed positional rather # positional keywords for efficiency with respect to @callable_cached. hint_bound = get_hint_pep484_typevar_bound_or_none(hint, exception_prefix) # print(f'Reducing PEP 484 type variable {repr(hint)} to {repr(hint_bound)}...') # print(f'Reducing non-beartype PEP 593 type hint {repr(hint)}...') # Return either... return ( # If this type variable was parametrized by *NO* bounded constraints, # this type variable preserved as is; hint if hint_bound is None else # Else, this type variable was parametrized by one or more bounded # constraints. In this case, these constraints. hint_bound ) beartype-0.18.5/beartype/_util/hint/pep/proposal/pep484/utilpep484union.py000066400000000000000000000043031461113517100263250ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`484`-compliant **union type hint utilities** (i.e., callables generically applicable to :pep:`484`-compliant :attr:`typing.Union` subscriptions). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import Union from beartype._util.cache.utilcachecall import callable_cached # ....................{ FACTORIES }.................... @callable_cached def make_hint_pep484_union(hints: tuple) -> object: ''' :pep:`484`-compliant **union type hint** (:attr:`typing.Union` subscription) synthesized from the passed tuple of two or more PEP-compliant type hints if this tuple contains two or more items, the one PEP-compliant type hint in this tuple if this tuple contains only one item, *or* raise an exception otherwise (i.e., if this tuple is empty). This factory is memoized for efficiency. Technically, the :attr:`typing.Union` type hint factory already caches its subscripted arguments. Pragmatically, that caching is slow and thus worth optimizing with trivial optimization on our end. Moreover, this factory is called by the performance-sensitive :func:`beartype._check.convert.convcoerce.coerce_hint_any` coercer in an early-time code path of the :func:`beartype.beartype` decorator. Optimizing this factory thus optimizes :func:`beartype.beartype` itself. Parameters ---------- hint : object Type hint to be inspected. Returns ------- object Either: * If this tuple contains two or more items, the union type hint synthesized from these items. * If this tuple contains only one item, this item as is. Raises ------ TypeError If this tuple is empty. ''' assert isinstance(hints, tuple), f'{repr(hints)} not tuple.' # These are the one-liners of our lives. return Union.__getitem__(hints) # pyright: ignore beartype-0.18.5/beartype/_util/hint/pep/proposal/pep484585/000077500000000000000000000000001461113517100232225ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/hint/pep/proposal/pep484585/__init__.py000066400000000000000000000000001461113517100253210ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/hint/pep/proposal/pep484585/utilpep484585.py000066400000000000000000000146651461113517100257740ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`484`- and :pep:`585`-compliant **dual type hint utilities** (i.e., callables generically applicable to both :pep:`484`- and :pep:`585`-compliant type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeDecorHintPep585Exception from beartype.typing import Union from beartype._util.hint.pep.proposal.pep484.utilpep484 import ( HINT_PEP484_TUPLE_EMPTY) from beartype._util.hint.pep.proposal.utilpep585 import ( HINT_PEP585_TUPLE_EMPTY) # ....................{ TESTERS ~ kind : tuple }.................... def is_hint_pep484585_tuple_empty(hint: object) -> bool: ''' :data:`True` only if the passed object is either a :pep:`484`- or :pep:`585`-compliant **empty fixed-length tuple type hint** (i.e., hint constraining objects to be the empty tuple). This tester is intentionally *not* memoized (e.g., by the ``callable_cached`` decorator), as this tester is only called under fairly uncommon edge cases. Motivation ---------- Since type variables are not themselves types but rather placeholders dynamically replaced with types by type checkers according to various arcane heuristics, both type variables and types parametrized by type variables warrant special-purpose handling. Parameters ---------- hint : object Object to be inspected. Returns ------- bool :data:`True` only if this object is an empty fixed-length tuple hint. ''' # Return true only if this hint resembles either the PEP 484- or # 585-compliant fixed-length empty tuple type hint. Since there only exist # two such hints *AND* comparison against these hints is mostly fast, this # test is efficient in the general case. # # Note that this test may also be inefficiently performed by explicitly # obtaining this hint's sign and then subjecting this hint to specific # tests conditionally depending on which sign and thus PEP this hint # complies with: e.g., # # Return true only if this hint is either... # return true ( # # A PEP 585-compliant "tuple"-based hint subscripted by no # # child hints *OR*... # ( # hint.__origin__ is tuple and # hint_childs_len == 0 # ) or # # A PEP 484-compliant "typing.Tuple"-based hint subscripted # # by exactly one child hint *AND* this child hint is the # # empty tuple,.. # ( # hint.__origin__ is Tuple and # hint_childs_len == 1 and # hint_childs[0] == () # ) # ) return ( hint == HINT_PEP585_TUPLE_EMPTY or hint == HINT_PEP484_TUPLE_EMPTY ) # ....................{ GETTERS }.................... #FIXME: Unit test us up, please. def get_hint_pep484585_args( hint: object, args_len: int, exception_prefix: str) -> object: ''' Either the single argument or tuple of all arguments subscripting the passed :pep:`484`- or :pep:`585`-compliant type hint if this hint is subscripted by exactly the passed number of child type hints *or* raise an exception otherwise. This getter returns either: * If ``args_len == 1``, the single argument subscripting this hint as a convenience to the caller. * Else, the tuple of all arguments subscripting this hint. This getter is intentionally *not* memoized (e.g., by the ``callable_cached`` decorator), as the implementation trivially reduces to an efficient one-liner. Caveats ------- **This higher-level getter should always be called in lieu of directly accessing the low-level** ``__args__`` **dunder attribute,** which is typically *not* validated at runtime and thus should *not* be assumed to be sane. Although the :mod:`typing` module usually validates the arguments subscripting :pep:`484`-compliant type hints and thus the ``__args__`` **dunder attribute at hint instantiation time, C-based CPython internals fail to similarly validate the arguments subscripting :pep:`585`-compliant type hints at any time: .. code-block:: python >>> import typing >>> typing.Type[str, bool] TypeError: Too many parameters for typing.Type; actual 2, expected 1 >>> type[str, bool] type[str, bool] # <-- when everything is okay, nothing is okay Parameters ---------- hint : Any Type hint to be inspected. args_len : int Number of child type hints expected to subscript this hint. exception_prefix : str Human-readable label prefixing the representation of this object in the exception message. Returns ------- Union[object, tuple] Either the single argument or tuple of all arguments subscripting this type hint. Raises ------ BeartypeDecorHintPep585Exception If this hint is subscripted by an unexpected number of child type hints. ''' assert isinstance(args_len, int), f'{repr(args_len)} not integer.' assert args_len >= 1, f'{args_len} < 0.' # Avoid circular import dependencies. from beartype._util.hint.pep.utilpepget import get_hint_pep_args # Tuple of all arguments subscripting this hint. hint_args = get_hint_pep_args(hint) # If this hint is *NOT* subscripted by the expected number of child type # hints... if len(hint_args) != args_len: assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') # Raise an exception. raise BeartypeDecorHintPep585Exception( f'{exception_prefix}PEP 585 type hint {repr(hint)} ' f'not subscripted (indexed) by {args_len} arguments (i.e., ' f'subscripted by {len(hint_args)} != {args_len} arguments).' ) # Else, this hint is subscripted by the expected number of child type hints. # Return either... return ( # If this hint is subscripted by only child hint, this child hint; hint_args[0] if args_len == 1 else # Else, this tuple of arguments as is. hint_args ) beartype-0.18.5/beartype/_util/hint/pep/proposal/pep484585/utilpep484585callable.py000066400000000000000000000407741461113517100274540ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`484`- and :pep:`585`-compliant **callable type hint utilities** (i.e., callables generically applicable to both :pep:`484`- and :pep:`585`-compliant ``Callable[...]`` type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... import beartype.typing as typing from beartype.roar import BeartypeDecorHintPep484585Exception from beartype.typing import ( TYPE_CHECKING, Union, ) from beartype._data.hint.datahinttyping import TypeException from beartype._data.hint.pep.sign.datapepsigns import HintSignCallable from beartype._data.hint.pep.sign.datapepsignset import ( HINT_SIGNS_CALLABLE_PARAMS) from beartype._data.kind.datakindsequence import TUPLE_EMPTY from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_10 # ....................{ HINTS }.................... # If an external static type checker (e.g., "mypy") is currently subjecting # "beartype" to static analysis, reduce this hint to a simplistic facsimile of # its full form tolerated by static type checkers. if TYPE_CHECKING: _HINT_PEP484585_CALLABLE_PARAMS = Union[ # For hints of the form "Callable[[{arg_hints}], {return_hint}]". tuple, # For hints of the form "Callable[typing.ParamSpec[...], {return_hint}]". typing.ParamSpec ] # Else, expand this hint to its full form supported by runtime type checkers. else: _HINT_PEP484585_CALLABLE_PARAMS = Union[ # For hints of the form "Callable[[{arg_hints}], {return_hint}]". tuple, # For hints of the form "Callable[..., {return_hint}]". type(Ellipsis), # If the active Python interpreter targets Python >= 3.10, a union # additionally matching the PEP 612-compliant "ParamSpec" type. ( # For hints of the form "Callable[typing.ParamSpec[...], {return_hint}]". typing.ParamSpec if IS_PYTHON_AT_LEAST_3_10 else # Else, the active Python interpreter targets Python < 3.10. In this # case, a meaninglessly redundant type listed above reducing to a noop. tuple ) ] ''' PEP-compliant type hint matching the first argument originally subscripting a :pep:`484`- or :pep:`585`-compliant **callable type hint** (i.e., ``typing.Callable[...]`` or ``collections.abc.Callable[...]`` type hint). ''' # ....................{ VALIDATORS }.................... def _die_unless_hint_pep484585_callable( # Mandatory parameters. hint: object, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintPep484585Exception, exception_prefix: str = '', ) -> None: ''' Raise an exception unless the passed object is either a :pep:`484`- or :pep:`585`-compliant **callable type hint** (i.e., ``typing.Callable[...]`` or ``collections.abc.Callable[...]`` type hint). Parameters ---------- hint : object Object to be validated. exception_cls : TypeException, optional Type of exception to be raised. Defaults to :exc:`BeartypeDecorHintPep484585Exception`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Raises ---------- :exc:`exception_cls` If this hint is either: * PEP-compliant but *not* uniquely identifiable by a sign. * PEP-noncompliant. * *Not* a hint (i.e., neither PEP-compliant nor -noncompliant). * *Not* a callable type hint (i.e., ``typing.Callable[...]`` or ``collections.abc.Callable[...]``). ''' # Avoid circular import dependencies. from beartype._util.hint.pep.utilpepget import get_hint_pep_sign # Sign uniquely identifying this hint if any *OR* raise an exception. hint_sign = get_hint_pep_sign( hint=hint, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # If this object is *NOT* a callable type hint, raise an exception. if hint_sign is not HintSignCallable: assert isinstance(exception_cls, type), ( f'{repr(exception_cls)} not exception class.') assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') raise exception_cls( f'{exception_prefix}type hint {repr(hint)} not ' f'PEP 484 or 585 callable type hint ' f'(i.e., "typing.Callable[...]" or ' f'"collections.abc.Callable[...]").' ) # Else, this object is a callable type hint, raise an exception. # ....................{ GETTERS }.................... def get_hint_pep484585_callable_params( # Mandatory parameters. hint: object, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintPep484585Exception, exception_prefix: str = '', ) -> _HINT_PEP484585_CALLABLE_PARAMS: ''' Object describing all **parameter type hints** (i.e., PEP-compliant child type hints typing the parameters accepted by a passed or returned callable) of the passed **callable type hint** (i.e., :pep:`484`-compliant ``typing.Callable[...]`` or :pep:`585`-compliant ``collections.abc.Callable[...]`` type hint). This getter returns one of several different types of objects, conditionally depending on the type of the first argument originally subscripting this hint. Specifically, if this hint was of the form: * ``Callable[[{arg_hints}], {return_hint}]``, this getter returns a tuple of the zero or more parameter type hints subscripting (indexing) this hint. * ``Callable[..., {return_hint}]``, the :data:`Ellipsis` singleton. * ``Callable[typing.ParamSpec[...], {return_hint}]``, the ``typing.ParamSpec[...]`` subscripting (indexing) this hint. This getter is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation requires no iteration and thus exhibits guaranteed constant-time behaviour. Parameters ---------- hint : object Callable type hint to be inspected. exception_cls : TypeException, optional Type of exception to be raised. Defaults to :exc:`.BeartypeDecorHintPep484585Exception`. exception_prefix : str, optional Human-readable substring prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ---------- _HINT_PEP484585_CALLABLE_PARAMS First argument originally subscripting this hint. Raises ---------- exception_cls If this hint is *not* a callable type hint. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.utilpepget import ( get_hint_pep_args, get_hint_pep_sign_or_none, ) # If this hint is *NOT* a callable type hint, raise an exception. _die_unless_hint_pep484585_callable(hint) # Else, this hint is a callable type hint. # Flattened tuple of the one or more child type hints subscripting this # callable type hint. Presumably for space efficiency reasons, both PEP 484- # *AND* 585-compliant callable type hints implicitly flatten the "__args__" # dunder tuple from the original data structure subscripting those hints. # CPython produces this flattened tuple as the concatenation of: # # * Either: # * If the first child type originally subscripting this hint was a list, # all items subscripting the nested list of zero or more parameter type # hints originally subscripting this hint as is: e.g., # >>> Callable[[], bool].__args__ # (bool,) # >>> Callable[[int, str], bool].__args__ # (int, str, bool) # # This includes a list containing only the empty tuple signifying a # callable accepting *NO* parameters, in which case that empty tuple is # preserved as is: e.g., # >>> Callable[[()], bool].__args__ # ((), bool) # * Else, the first child type originally subscripting this hint as is. In # this case, that child type is required to be either: # * An ellipsis object (i.e., the "Ellipsis" builtin singleton): e.g., # >>> Callable[..., bool].__args__ # (Ellipsis, bool) # * A PEP 612-compliant parameter specification (i.e., # "typing.ParamSpec[...]" type hint): e.g., # >>> Callable[ParamSpec('P'), bool].__args__ # (~P, bool) # * A PEP 612-compliant parameter concatenation (i.e., # "typing.Concatenate[...]" type hint): e.g., # >>> Callable[Concatenate[str, ParamSpec('P')], bool].__args__ # (typing.Concatenate[str, ~P], bool) # * The return type hint originally subscripting this hint. # # Note that both PEP 484- *AND* 585-compliant callable type hints guarantee # this tuple to contain at least one child type hint. Ergo, we avoid # validating that constraint here: e.g., # >>> from typing import Callable # >>> Callable[()] # TypeError: Callable must be used as Callable[[arg, ...], result]. # >>> from collections.abc import Callable # >>> Callable[()] # TypeError: Callable must be used as Callable[[arg, ...], result]. hint_args = get_hint_pep_args(hint) # Number of parameter type hints flattened into this tuple, calculated by # excluding the trailing return type hint also flattened into this tuple. # # Note that by the above constraint, this number is guaranteed to be # non-negative: e.g., # >>> hint_args_len >= 0 # True hint_params_len = len(hint_args) - 1 # If this callable type hint was subscripted by *NO* parameter type hints, # return the empty tuple for efficiency. if hint_params_len == 0: return () # Else, this callable type hint was subscripted by one or more parameter # type hints. # # If this callable type hint was subscripted by two or more parameter type # hints, this callable type hint *CANNOT* have been subscripted by a single # "special" parameter type hint (e.g., ellipsis, parameter specification). # By elimination, the only remaining category of parameter type hint is a # nested list of two or more parameter type hints. In this case, return the # tuple slice containing the parameter type hints omitting the trailing # return type hint. elif hint_params_len >= 2: return hint_args[:-1] # Else, this callable type hint was subscripted by exactly one parameter # type hint... which could be either a nested list of one or more parameter # type hints *OR* a "special" parameter type hint. To differentiate the # former from the latter, we explicitly detect all possible instances of the # latter and only fallback to the former after exhausting the latter. # Single parameter type hint subscripting this callable type hint. hint_param = hint_args[0] # If this parameter type hint is either... # # Note that we intentionally avoid attempting to efficiently test this # parameter type hint against a set (e.g., "hint_param in {..., ()}"). This # parameter type hint is *NOT* guaranteed to be hashable and thus testable # against a hash-based collection. if ( # An ellipsis, return an ellipsis. hint_param is ... or # The empty tuple, reduce this unlikely (albeit possible) edge case # to the empty tuple returned for the more common case of a callable # type hint subscripted by an empty list. That is, reduce these two # cases to the same empty tuple for simplicity: e.g., # >>> Callable[[], bool].__args__ # (bool,) # <------ this is good # >>> Callable[[()], bool].__args__ # ((), bool,) # <-- this is bad, so pretend this never happens hint_param is TUPLE_EMPTY ): return hint_param # Else, this parameter type hint is neither the empty tuple *NOR* an # ellipsis. # Sign uniquely identifying this parameter type hint if any *OR* "None". hint_param_sign = get_hint_pep_sign_or_none(hint_param) # If this parameter type hint is a PEP-compliant parameter type (i.e., # uniquely identifiable by a sign), return this hint as is. # # Note that: # * This requires callers to handle all possible categories of # PEP-compliant parameter type hints -- including both # "typing.ParamSpec[...]" and "typing.Concatenate[...]" parameter type # hints, presumably by (...somewhat redundantly, but what can you do) # calling the get_hint_pep_sign_or_none() getter themselves. # * Both PEP 484- *AND* 585-compliant callable type hints guarantee # this parameter type hint to be constrained to the subset of # PEP-compliant parameter type hints. Arbitrary parameter type hints # are prohibited. Ergo, we avoid validating that constraint here: # e.g., # >>> from typing import Callable, List # >>> Callable[List[int], bool] # TypeError: Callable[args, result]: args must be a list. Got # typing.List[int] if hint_param_sign in HINT_SIGNS_CALLABLE_PARAMS: return hint_param # Else, this parameter type hint is *NOT* a PEP-compliant parameter type # hint. This hint *CANNOT* be "special" and *MUST* thus be the single # parameter type hint of a nested list: e.g., # >>> Callable[[list[int]], bool].__args__ # (list[int], bool) # In this case, return the 1-tuple containing exactly this hint. # print(f'get_hint_pep484585_callable_params({repr(hint)}) == ({repr(hint_param)},)') # print(f'{repr(hint_param)} sign: {repr(hint_param_sign)}') return (hint_param,) def get_hint_pep484585_callable_return( # Mandatory parameters. hint: object, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintPep484585Exception, exception_prefix: str = '', ) -> object: ''' **Return type hint** (i.e., PEP-compliant child type hint typing the return returned by a passed or returned callable) of the passed **callable type hint** (i.e., :pep:`484`-compliant ``typing.Callable[...]`` or :pep:`585`-compliant ``collections.abc.Callable[...]`` type hint). This getter is considerably more trivial than the companion :func:`get_hint_pep484585_callable_params` getter. Although this getter exists largely for orthogonality and completeness, callers are advised to defer to this getter rather than access this return type hint directly. This getter is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object Callable type hint to be inspected. exception_cls : TypeException, optional Type of exception to be raised. Defaults to :exc:`BeartypeDecorHintPep484585Exception`. exception_prefix : str, optional Human-readable substring prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ---------- object Last argument originally subscripting this hint. Raises ---------- :exc:`exception_cls` If this hint is *not* a callable type hint. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.utilpepget import get_hint_pep_args # If this hint is *NOT* a callable type hint, raise an exception. _die_unless_hint_pep484585_callable(hint) # Else, this hint is a callable type hint. # Flattened tuple of the one or more child type hints subscripting this # callable type hint. See get_hint_pep484585_callable_params() for details. hint_args = get_hint_pep_args(hint) # Return the last object subscripting this hint. # # Note that both the PEP 484-compliant "typing.Callable" factory *AND* the # PEP 585-compliant "collections.abc.Callable" factory guarantee this object # to exist. Ergo, we intentionally avoid repeating any validation here: # $ python3.10 # >>> from collections.abc import Callable # >>> Callable[()] # TypeError: Callable must be used as Callable[[arg, ...], result]. return hint_args[-1] beartype-0.18.5/beartype/_util/hint/pep/proposal/pep484585/utilpep484585func.py000066400000000000000000000224401461113517100266360ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`484`- and :pep:`585`-compliant **decorated callable type hint utilities** (i.e., callables generically applicable to both :pep:`484`- and :pep:`585`-compliant type hints directly annotating the user-defined callable currently being decorated by :func:`beartype.beartype`). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeDecorHintPep484585Exception from beartype.typing import Tuple from beartype._data.func.datafuncarg import ARG_NAME_RETURN from beartype._data.hint.datahinttyping import TypeException from beartype._data.hint.pep.sign.datapepsigns import HintSignCoroutine from beartype._data.hint.pep.sign.datapepsignset import ( HINT_SIGNS_RETURN_GENERATOR_ASYNC, HINT_SIGNS_RETURN_GENERATOR_SYNC, ) from beartype._util.cls.utilclstest import is_type_subclass from beartype._util.func.utilfunctest import ( is_func_coro, is_func_async_generator, is_func_sync_generator, ) from beartype._util.text.utiltextprefix import prefix_callable_return from collections.abc import ( AsyncGenerator, Callable, Generator, ) # ....................{ REDUCERS ~ return }.................... def reduce_hint_pep484585_func_return( func: Callable, exception_prefix: str, ) -> object: ''' Reduce the possibly PEP-noncompliant type hint annotating the return of the passed callable if any to a simpler form to generate optimally efficient type-checking by the :func:`beartype.beartype` decorator. Parameters ---------- func : Callable Currently decorated callable to be inspected. exception_prefix : str Human-readable label prefixing the representation of this object in the exception message. Returns ---------- object Single argument subscripting this hint. Raises ---------- BeartypeDecorHintPep484585Exception If this callable is either: * A synchronous generator *not* annotated by a type hint identified by a sign in the :data:`HINT_SIGNS_RETURN_GENERATOR_SYNC` set. * An asynchronous generator *not* annotated by a type hint identified by a sign in the :data:`HINT_SIGNS_RETURN_GENERATOR_ASYNC` set. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.proposal.pep484585.utilpep484585 import ( get_hint_pep484585_args) from beartype._util.hint.pep.utilpepget import get_hint_pep_sign_or_none # Type hint annotating this callable's return, which the caller has already # explicitly guaranteed to exist. hint = func.__annotations__[ARG_NAME_RETURN] # Sign uniquely identifying this hint if any *OR* "None" otherwise (e.g., # if this hint is an isinstanceable class). hint_sign = get_hint_pep_sign_or_none(hint) # If the decorated callable is a coroutine... if is_func_coro(func): # If this hint is "Coroutine[...]"... if hint_sign is HintSignCoroutine: # 3-tuple of all child type hints subscripting this hint if # subscripted by three such hints *OR* raise an exception. hint_args: Tuple[object, object, object] = get_hint_pep484585_args( # type: ignore[assignment] hint=hint, args_len=3, exception_prefix=exception_prefix) # Reduce this hint to the last child type hint subscripting this # hint, whose value is the return type hint for this coroutine. # # All other child type hints are currently ignorable, as the *ONLY* # means of validating objects sent to and yielded from a coroutine # is to wrap that coroutine with a @beartype-specific wrapper # object, which we are currently unwilling to do. Why? Because # safety and efficiency. Coroutines receiving and yielding multiple # objects are effectively iterators; type-checking all iterator # values is an O(n) rather than O(1) operation, violating the core # @beartype guarantee. # # Likewise, the parent "Coroutine" type is *ALWAYS* ignorable. # Since Python itself implicitly guarantees *ALL* coroutines to # return coroutine objects, validating that constraint is silly. hint = hint_args[-1] # Else, this hint is *NOT* "Coroutine[...]". In this case, silently # accept this hint as if this hint had instead been expanded to the # semantically equivalent PEP 484- or 585-compliant coroutine hint # "Coroutine[None, None, {hint}]". # Else, the decorated callable is *NOT* a coroutine. # # If the decorated callable is an asynchronous generator... elif is_func_async_generator(func): # If this hint is neither... if not ( # A PEP-compliant type hint acceptable as the return annotation of # an synchronous generator *NOR*... hint_sign in HINT_SIGNS_RETURN_GENERATOR_ASYNC or # The "collections.abc.AsyncGenerator" abstract base class (ABC) or # a subclass of that ABC... is_type_subclass(hint, AsyncGenerator) # Then this hint is semantically invalid as the return annotation of # this callable. In this case, raise an exception. ): _die_of_hint_return_invalid( func=func, exception_suffix=( ' (i.e., expected either ' 'collections.abc.AsyncGenerator[YieldType, SendType], ' 'collections.abc.AsyncIterable[YieldType], ' 'collections.abc.AsyncIterator[YieldType], ' 'typing.AsyncGenerator[YieldType, SendType], ' 'typing.AsyncIterable[YieldType], or ' 'typing.AsyncIterator[YieldType] ' 'type hint).' ), ) # Else, this hint is valid as the return annotation of this callable. # Else, the decorated callable is *NOT* an asynchronous generator. # # If the decorated callable is a synchronous generator... elif is_func_sync_generator(func): # If this hint is neither... if not ( # A PEP-compliant type hint acceptable as the return annotation of a # synchronous generator *NOR*... hint_sign in HINT_SIGNS_RETURN_GENERATOR_SYNC or # The "collections.abc.Generator" abstract base class (ABC) or a # subclass of that ABC... is_type_subclass(hint, Generator) # Then this hint is semantically invalid as the return annotation of # this callable. In this case, raise an exception. ): _die_of_hint_return_invalid( func=func, exception_suffix=( ' (i.e., expected either ' 'collections.abc.Generator[YieldType, SendType, ReturnType], ' 'collections.abc.Iterable[YieldType], ' 'collections.abc.Iterator[YieldType], ' 'typing.Generator[YieldType, SendType, ReturnType], ' 'typing.Iterable[YieldType], or ' 'typing.Iterator[YieldType] ' 'type hint).' ), ) # Else, this hint is valid as the return annotation of this callable. # Else, the decorated callable is none of the kinds detected above. # Return this possibly reduced hint. return hint # ....................{ PRIVATE ~ validators }.................... def _die_of_hint_return_invalid( # Mandatory parameters. func: Callable, exception_suffix: str, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintPep484585Exception, ) -> str: ''' Raise an exception of the passed type with a message suffixed by the passed substring explaining why the possibly PEP-noncompliant type hint annotating the return of the passed decorated callable is contextually invalid. Parameters ---------- func : Callable Decorated callable whose return is annotated by an invalid type hint. exception_cls : TypeException Type of exception to be raised. Defaults to :exc:`.BeartypeDecorHintPep484585Exception`. exception_suffix : str Substring suffixing the exception message to be raised. Raises ---------- exception_cls Exception explaining the invalidity of this return type hint. ''' assert callable(func), f'{repr(func)} not callable.' assert isinstance(exception_cls, type), f'{repr(exception_cls)} not class.' assert isinstance(exception_suffix, str), ( f'{repr(exception_suffix)} not string.') # Type hint annotating this callable's return, which the caller has already # explicitly guaranteed to exist. hint = func.__annotations__[ARG_NAME_RETURN] # Raise an exception of this type with a message suffixed by this suffix. raise exception_cls( f'{prefix_callable_return(func)}type hint ' f'{repr(hint)} contextually invalid{exception_suffix}' ) beartype-0.18.5/beartype/_util/hint/pep/proposal/pep484585/utilpep484585generic.py000066400000000000000000001150711461113517100273220ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`484`- and :pep:`585`-compliant **generic type hint utilities** (i.e., callables generically applicable to both :pep:`484`- and :pep:`585`-compliant generic classes). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeDecorHintPep484585Exception from beartype.typing import ( Optional, Tuple, ) from beartype._conf.confcls import BeartypeConf from beartype._data.hint.datahinttyping import TypeException from beartype._data.hint.pep.sign.datapepsigns import HintSignGeneric from beartype._util.cache.utilcachecall import callable_cached from beartype._util.cache.pool.utilcachepoollistfixed import ( FIXED_LIST_SIZE_MEDIUM, acquire_fixed_list, release_fixed_list, ) from beartype._util.hint.pep.proposal.pep484.utilpep484generic import ( get_hint_pep484_generic_bases_unerased, is_hint_pep484_generic, ) from beartype._util.hint.pep.proposal.utilpep585 import ( get_hint_pep585_generic_bases_unerased, is_hint_pep585_generic, ) from collections.abc import Iterable # ....................{ TESTERS }.................... @callable_cached def is_hint_pep484585_generic(hint: object) -> bool: ''' :data:`True` only if the passed object is either a :pep:`484`- or :pep:`585`-compliant **generic** (i.e., object that may *not* actually be a class despite subclassing at least one PEP-compliant type hint that also may *not* actually be a class). Specifically, this tester returns :data:`True` only if this object is either: * A :pep:`484`-compliant generic as tested by the lower-level :func:`is_hint_pep484_generic` function. * A :pep:`585`-compliant generic as tested by the lower-level :func:`is_hint_pep585_generic` function. This tester is memoized for efficiency. Although the implementation trivially reduces to a one-liner, constant factors associated with that one-liner are non-negligible. Moreover, this tester is called frequently enough to warrant its reduction to an efficient lookup. Caveats ------- **Generics are not necessarily classes,** despite originally being declared as classes. Although *most* generics are classes, subscripting a generic class usually produces a generic non-class that *must* nonetheless be transparently treated as a generic class: e.g., .. code-block:: python >>> from typing import Generic, TypeVar >>> S = TypeVar('S') >>> T = TypeVar('T') >>> class MuhGeneric(Generic[S, T]): pass >>> non_class_generic = MuhGeneric[S, T] >>> isinstance(non_class_generic, type) False Parameters ---------- hint : object Object to be inspected. Returns ------- bool :data:`True` only if this object is a generic. See Also -------- :func:`beartype._util.hint.pep.utilpeptest.is_hint_pep_typevars` Commentary on the relation between generics and parametrized hints. ''' # Return true only if this hint is... return ( # A PEP 484-compliant generic. Note this test trivially reduces to # an O(1) operation and is thus tested first. is_hint_pep484_generic(hint) or # A PEP 585-compliant generic. Note this test is O(n) in n the # number of pseudo-superclasses originally subclassed by this # generic and is thus tested last. is_hint_pep585_generic(hint) ) # ....................{ GETTERS ~ bases }.................... def get_hint_pep484585_generic_bases_unerased( # Mandatory parameters. hint: object, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintPep484585Exception, exception_prefix: str = '', ) -> Tuple[object, ...]: ''' Tuple of the one or more **unerased pseudo-superclasses** (i.e., PEP-compliant objects originally listed as superclasses prior to their implicit type erasure under :pep:`560`) of the passed :pep:`484`- or :pep:`585`-compliant **generic** (i.e., class superficially subclassing at least one PEP-compliant type hint that is possibly *not* an actual class) if this object is a generic *or* raise an exception otherwise (i.e., if this object is either not a class *or* is a class subclassing no non-class PEP-compliant objects). This getter is guaranteed to return a non-empty tuple. By definition, a generic is a type subclassing one or more generic superclasses. This getter is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Caveats ------- **This function should always be called in lieu of attempting to directly access the low-level** ``__orig_bases__`` **dunder instance variable.** Most PEP-compliant type hints fail to declare that variable, guaranteeing :class:`AttributeError` exceptions from all general-purpose logic attempting to directly access that variable. Thus this function, which "fills in the gaps" by implementing this oversight. **This function returns tuples possibly containing a mixture of actual superclasses and pseudo-superclasses superficially masquerading as actual superclasses subscripted by one or more PEP-compliant child hints or type variables** (e.g., ``(typing.Iterable[T], typing.Sized[T])``). Indeed, most type hints used as superclasses produced by subscripting PEP-compliant type hint factories are *not* actually types but singleton objects devilishly masquerading as types. Most actual :mod:`typing` superclasses are private, fragile, and prone to alteration or even removal between Python versions. Motivation ---------- :pep:`560` (i.e., "Core support for typing module and generic types) formalizes the ``__orig_bases__`` dunder attribute first informally introduced by the :mod:`typing` module's implementation of :pep:`484`. Naturally, :pep:`560` remains as unusable as :pep:`484` itself. Ideally, :pep:`560` would have generalized the core intention of preserving each original user-specified subclass tuple of superclasses as a full-blown ``__orig_mro__`` dunder attribute listing the original method resolution order (MRO) of that subclass had that tuple *not* been modified. Naturally, :pep:`560` did no such thing. The original MRO remains obfuscated and effectively inaccessible. While computing that MRO would technically be feasible, doing so would also be highly non-trivial, expensive, and fragile. Instead, this function retrieves *only* the tuple of :mod:`typing`-specific pseudo-superclasses that this object's class originally attempted (but failed) to subclass. You are probably now agitatedly cogitating to yourself in the darkness: "But @leycec: what do you mean :pep:`560`? Wasn't :pep:`560` released *after* :pep:`484`? Surely no public API defined by the Python stdlib would be so malicious as to silently alter the tuple of base classes listed by a user-defined subclass?" As we've established both above and elsewhere throughout the codebase, everything developed for :pep:`484` -- including :pep:`560`, which derives its entire raison d'etre from :pep:`484` -- are fundamentally insane. In this case, :pep:`484` is insane by subjecting parametrized :mod:`typing` types employed as base classes to "type erasure," because: ...it is common practice in languages with generics (e.g. Java, TypeScript). Since Java and TypeScript are both terrible languages, blindly recapitulating bad mistakes baked into such languages is an equally bad mistake. In this case, "type erasure" means that the :mod:`typing` module *intentionally* destroys runtime type information for nebulous and largely unjustifiable reasons (i.e., Big Daddy Java and TypeScript do it, so it must be unquestionably good). Specifically, the :mod:`typing` module intentionally munges :mod:`typing` types listed as base classes in user-defined subclasses as follows: * All base classes whose origin is a builtin container (e.g., ``typing.List[T]``) are reduced to that container (e.g., :class:`list`). * All base classes derived from an abstract base class declared by the :mod:`collections.abc` subpackage (e.g., ``typing.Iterable[T]``) are reduced to that abstract base class (e.g., ``collections.abc.Iterable``). * All surviving base classes that are parametrized (e.g., ``typing.Generic[S, T]``) are stripped of that parametrization (e.g., :class:`typing.Generic`). Since there exists no counterpart to the :class:`typing.Generic` superclass, the :mod:`typing` module preserves that superclass in unparametrized form. Naturally, this is useless, as an unparametrized :class:`typing.Generic` superclass conveys no meaningful type information. All other superclasses are reduced to their non-:mod:`typing` counterparts: e.g., .. code-block:: python >>> from typing import TypeVar, Generic, Iterable, List >>> T = TypeVar('T') >>> class UserDefinedGeneric(List[T], Iterable[T], Generic[T]): pass # This is type erasure. >>> UserDefinedGeneric.__mro__ (list, collections.abc.Iterable, Generic) # This is type preservation -- except the original MRO is discarded. # So, it's not preservation; it's reduction! We take what we can get. >>> UserDefinedGeneric.__orig_bases__ (typing.List[T], typing.Iterable[T], typing.Generic[T]) # Guess which we prefer? So, we prefer the generally useful ``__orig_bases__`` dunder tuple over the generally useless ``__mro__`` dunder tuple. Note, however, that the latter *is* still occasionally useful and thus occasionally returned by this getter. For inexplicable reasons, **single-inherited protocols** (i.e., classes directly subclassing *only* the :pep:`544`-compliant :attr:`typing.Protocol` abstract base class (ABC)) are *not* subject to type erasure and thus constitute a notable exception to this heuristic: .. code-block:: python >>> from typing import Protocol >>> class UserDefinedProtocol(Protocol): pass >>> UserDefinedProtocol.__mro__ (__main__.UserDefinedProtocol, typing.Protocol, typing.Generic, object) >>> UserDefinedProtocol.__orig_bases__ AttributeError: type object 'UserDefinedProtocol' has no attribute '__orig_bases__' Welcome to :mod:`typing` hell, where even :mod:`typing` types lie broken and misshapen on the killing floor of overzealous theory-crafting purists. Parameters ---------- hint : object Generic type hint to be inspected. exception_cls : TypeException Type of exception to be raised. Defaults to :exc:`BeartypeDecorHintPep484585Exception`. exception_prefix : str, optional Human-readable substring prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- Tuple[object] Tuple of the one or more unerased pseudo-superclasses of this generic. Raises ------ exception_cls If this hint is either: * Neither a :pep:`484`- nor :pep:`585`-compliant generic. * A :pep:`484`- nor :pep:`585`-compliant generic subclassing *no* pseudo-superclasses. Examples -------- >>> import typing >>> from beartype._util.hint.pep.utilpepget import ( ... get_hint_pep484585_generic_bases_unerased) >>> T = typing.TypeVar('T') >>> class MuhIterable(typing.Iterable[T], typing.Container[T]): pass >>> get_hint_pep585_generic_bases_unerased(MuhIterable) (typing.Iterable[~T], typing.Container[~T]) >>> MuhIterable.__mro__ (MuhIterable, collections.abc.Iterable, collections.abc.Container, typing.Generic, object) ''' # Tuple of either... # # Note this implicitly raises a "BeartypeDecorHintPepException" if this # object is *NOT* a PEP-compliant generic. Ergo, we need not explicitly # validate that above. hint_pep_generic_bases_unerased = ( # If this is a PEP 585-compliant generic, all unerased # pseudo-superclasses of this PEP 585-compliant generic. # # Note that this unmemoized getter accepts keyword arguments. get_hint_pep585_generic_bases_unerased( hint=hint, exception_cls=exception_cls, exception_prefix=exception_prefix, ) if is_hint_pep585_generic(hint) else # Else, this *MUST* be a PEP 484-compliant generic. In this case, all # unerased pseudo-superclasses of this PEP 484-compliant generic. # # Note that this memoized getter prohibits keyword arguments. get_hint_pep484_generic_bases_unerased( hint, exception_cls, exception_prefix, ) ) # If this generic subclasses *NO* pseudo-superclass, raise an exception. # # Note this should have already been guaranteed on our behalf by: # * If this generic is PEP 484-compliant, the "typing" module. # * If this generic is PEP 585-compliant, CPython or PyPy itself. if not hint_pep_generic_bases_unerased: assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') raise exception_cls( f'{exception_prefix}PEP 484 or 585 generic {repr(hint)} ' f'subclasses no superclasses.' ) # Else, this generic subclasses one or more pseudo-superclasses. # Return this tuple of these pseudo-superclasses. return hint_pep_generic_bases_unerased # ....................{ GETTERS ~ type }.................... #FIXME: Unit test us up, please. def get_hint_pep484585_generic_type( # Mandatory parameters. hint: object, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintPep484585Exception, exception_prefix: str = '', ) -> type: ''' Either the passed :pep:`484`- or :pep:`585`-compliant **generic** (i.e., class superficially subclassing at least one PEP-compliant type hint that is possibly *not* an actual class) if **unsubscripted** (i.e., indexed by *no* arguments or type variables), the unsubscripted generic underlying this generic if **subscripted** (i.e., indexed by one or more child type hints and/or type variables), *or* raise an exception otherwise (i.e., if this hint is *not* a generic). Specifically, this getter returns (in order): * If this hint originates from an **origin type** (i.e., isinstanceable class such that *all* objects satisfying this hint are instances of that class), this type regardless of whether this hint is already a class. * Else if this hint is already a class, this hint as is. * Else, raise an exception. This getter is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Caveats ------- **This getter returns false positives in edge cases.** That is, this getter returns non-:data:`None` values for both generics and non-generics -- notably, non-generics defining the ``__origin__`` dunder attribute to an isinstanceable class. Callers *must* perform subsequent tests to distinguish these two cases. Parameters ---------- hint : object Generic type hint to be inspected. exception_cls : TypeException Type of exception to be raised. Defaults to :exc:`BeartypeDecorHintPep484585Exception`. exception_prefix : str, optional Human-readable substring prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- type Class originating this generic. Raises ------ exception_cls If this hint is *not* a generic. See Also -------- :func:`get_hint_pep484585_generic_type_or_none` Further details. ''' # Either this hint if this hint is an unsubscripted generic, the # unsubscripted generic underlying this hint if this hint is a subscripted # generic, *OR* "None" if this hint is not a generic. hint_generic_type = get_hint_pep484585_generic_type_or_none(hint) # If this hint is *NOT* a generic, raise an exception. if hint_generic_type is None: raise exception_cls( f'{exception_prefix}PEP 484 or 585 generic {repr(hint)} ' f'not generic (i.e., originates from no isinstanceable class).' ) # Else, this hint is a generic. # Return this class. return hint_generic_type def get_hint_pep484585_generic_type_or_none(hint: object) -> Optional[type]: ''' Either the passed :pep:`484`- or :pep:`585`-compliant **generic** (i.e., class superficially subclassing at least one PEP-compliant type hint that is possibly *not* an actual class) if **unsubscripted** (i.e., indexed by *no* arguments or type variables), the unsubscripted generic underlying this generic if **subscripted** (i.e., indexed by one or more child type hints and/or type variables), *or* :data:`None` otherwise (i.e., if this hint is *not* a generic). Specifically, this getter returns (in order): * If this hint originates from an **origin type** (i.e., isinstanceable class such that *all* objects satisfying this hint are instances of that class), this type regardless of whether this hint is already a class. * Else if this hint is already a class, this hint as is. * Else, :data:`None`. This getter is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Caveats ------- **This getter returns false positives in edge cases.** That is, this getter returns non-:data:`None`` values for both generics and non-generics -- notably, non-generics defining the ``__origin__`` dunder attribute to an isinstanceable class. Callers *must* perform subsequent tests to distinguish these two cases. Parameters ---------- hint : object Object to be inspected. Returns ------- Optional[type] Either: * If this hint is a generic, the class originating this generic. * Else, :data:`None`. See Also -------- :func:`get_hint_pep_origin_or_none` Further details. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.utilpepget import get_hint_pep_origin_or_none # Arbitrary object originating this hint if any *OR* "None" otherwise. hint_origin = get_hint_pep_origin_or_none(hint) # print(f'{repr(hint)} hint_origin: {repr(hint_origin)}') # If this origin is a type, this is the origin type originating this hint. # In this case, return this type. if isinstance(hint_origin, type): return hint_origin # Else, this origin is *NOT* a type. # # Else if this hint is already a type, this type is effectively already its # origin type. In this case, return this type as is. elif isinstance(hint, type): return hint # Else, this hint is *NOT* a type. In this case, this hint originates from # *NO* origin type. # Return the "None" singleton. return None # ....................{ FINDERS }.................... def find_hint_pep484585_generic_module_base_first( # Mandatory parameters. hint: object, module_name: str, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintPep484585Exception, exception_prefix: str = '', ) -> type: ''' Iteratively find and return the first **unerased superclass** (i.e., unerased pseudo-superclass that is an actual superclass) transitively defined under the third-party package or module with the passed name subclassed by the unsubscripted generic type underlying the passed :pep:`484`- or :pep:`585`-compliant **generic** (i.e., object that may *not* actually be a class despite subclassing at least one PEP-compliant type hint that also may *not* actually be a class). This finder is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator). Although doing so *would* dramatically improve the efficiency of this finder, doing so: * Would require all passed parameters be passed positionally, which becomes rather cumbersome given the number of requisite parameters. * Is (currently) unnecessary, as all callers of this function are themselves already memoized. Motivation ---------- This finder is typically called to reduce **descriptive generics** (i.e., generics defined in third-party packages intended to be used *only* as descriptive type hints rather than actually instantiated as objects as most generics are) to the isinstanceable classes those generics describe. Although the mere existence of descriptive generics should be considered to be a semantic (if not syntactic) violation of :pep:`484`, the widespread proliferation of descriptive generics leaves :mod:`beartype` with little choice but to grin wanly and bear the pain they subject us to. As example, this finder is currently called elsewhere to: * Reduce Pandera type hints (e.g., `pandera.typing.DataFrame[...]`) to the Pandas types they describe (e.g., `pandas.DataFrame`). * Reduce NumPy type hints (e.g., `numpy.typing.NDArray[...]`) to the NumPy types they describe (e.g., `numpy.ndarray`). See examples below for further discussion. Parameters ---------- hint : object Generic type hint to be inspected. module_name : str Fully-qualified name of the third-party package or module to find the first class in this generic type hint of. exception_cls : TypeException Type of exception to be raised. Defaults to :exc:`BeartypeDecorHintPep484585Exception`. exception_prefix : str, optional Human-readable substring prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- type First unerased superclass transitively defined under this package or module subclassed by the unsubscripted generic type underlying this generic type hint. Examples -------- >>> from beartype._util.hint.pep.proposal.pep484585.utilpep484585generic import ( ... find_hint_pep484585_generic_base_first_in_module) # Reduce a Pandera type hint to the Pandas type it describes. >>> from pandera import DataFrameModel >>> from pandera.typing import DataFrame >>> class MuhModel(DataFrameModel): pass >>> find_hint_pep484585_generic_base_first_in_module( ... hint=DataFrame[MuhModel], module_name='pandas', ...) ''' assert isinstance(module_name, str), f'{repr(module_name)} not string.' # Avoid circular import dependencies. from beartype._util.module.utilmodget import get_object_module_name_or_none # Either: # * If this generic is unsubscripted, this unsubscripted generic type as is. # * If this generic is subscripted, the unsubscripted generic type # underlying this subscripted generic (e.g., the type # "pandera.typing.pandas.DataFrame" given the type hint # "pandera.typing.DataFrame[...]"). hint_type = get_hint_pep484585_generic_type( hint=hint, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # Fully-qualified name of the module to be searched for suffixed by a "." # delimiter. This is a micro-optimization improving lookup speed below. module_name_prefix = f'{module_name}.' # Tuple of the one or more unerased pseudo-superclasses which this # unsubscripted generic type originally subclassed prior to type erasure. # # Note that we could also inspect the method-resolution order (MRO) of this # type via the "hint.__mro__" dunder tuple, but that doing so would only # needlessly reduce the efficiency of the following iteration by # substantially increasing the number of iterations required to find the # desired superclass and thus the worst-case complexity of that iteration. hint_type_bases = get_hint_pep484585_generic_bases_unerased( hint=hint, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # For each unerased pseudo-superclass of this unsubscripted generic type... for hint_base in hint_type_bases: # If this pseudo-superclass is *NOT* an actual superclass, silently # ignore this non-superclass and continue to the next pseudo-superclass. if not isinstance(hint_base, type): continue # Else, this pseudo-superclass is an actual superclass. # Fully-qualified name of the module declaring this superclass if any # *OR* "None" otherwise (i.e., if this type is only defined in-memory). hint_base_module_name = get_object_module_name_or_none(hint_base) # If this module exists *AND* either... if hint_base_module_name and ( # This module is the desired module itself *OR*... hint_base_module_name == module_name_prefix or # This module is a submodule of the desired module... hint_base_module_name.startswith(module_name_prefix) # Then return this superclass. ): # print(f'Found generic {repr(hint)} type {repr(hint_type)} "{module_name}" superclass {repr(hint_base)}!') return hint_base # Else, this is *NOT* the desired module. In this case, continue to the # next superclass. # Else, *NO* superclass of this generic resides in the desired module. # Raise an exception of the passed type. raise exception_cls( f'{exception_prefix}PEP 484 or 585 generic {repr(hint)} ' f'type {repr(hint_type)} subclasses no "{module_name}" type ' f'(i.e., type with module name prefixed by "{module_name}" not ' f'found in method resolution order (MRO) {repr(hint_type.__mro__)}).' ) # ....................{ ITERATORS }.................... #FIXME: Unit test us up, please. def iter_hint_pep484585_generic_bases_unerased_tree( # Mandatory parameters. hint: object, conf: BeartypeConf, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintPep484585Exception, exception_prefix: str = '', ) -> Iterable: ''' Breadth-first search (BFS) generator iteratively yielding the one or more unignorable unerased transitive pseudo-superclasses originally declared as superclasses prior to their type erasure of the passed :pep:`484`- or :pep:`585`-compliant generic. This generator yields the full tree of all pseudo-superclasses by transitively visiting both all direct pseudo-superclasses of this generic *and* all indirect pseudo-superclasses transitively superclassing all direct pseudo-superclasses of this generic. For efficiency, this generator is internally implemented with an efficient imperative First In First Out (FILO) queue rather than an inefficient (and dangerous, due to both unavoidable stack exhaustion and avoidable infinite recursion) tree of recursive function calls. Motivation ---------- Ideally, a BFS would *not* be necessary. Instead, pseudo-superclasses visited by this BFS should be visitable as is via whatever external parent BFS is currently iterating over the tree of all transitive type hints (e.g., our code generation algorithm implemented by the :func:`beartype._check.code.codemake.make_func_pith_code` function). That's how we transitively visit all other kinds of type hints, right? Sadly, that simple solution fails to scale to all possible edge cases that arise with generics. Why? Because our code generation algorithm sensibly requires that *only* unignorable hints may be enqueued onto its outer BFS. Generics confound that constraint. Some pseudo-superclasses are paradoxically: * Ignorable from the perspective of code generation. *No* type-checking code should be generated for these pseudo-superclasses. See reasons below. * Unignorable from the perspective of algorithm visitation. These pseudo-superclasses generate *no* code but may themselves subclass other pseudo-superclasses for which type-checking code should be generated and which must thus be visited by our outer BFS. Paradoxical pseudo-superclasses include: * User-defined :pep:`484`-compliant subgenerics (i.e., user-defined generics subclassing one or more parent user-defined generic superclasses). * User-defined :pep:`544`-compliant subprotocols (i.e., user-defined protocols subclassing one or more parent user-defined protocol superclasses). Consider this example :pep:`544`-compliant subprotocol: .. code-block:: pycon >>> import typing as t >>> class UserProtocol(t.Protocol[t.AnyStr]): pass >>> class UserSubprotocol(UserProtocol[str], t.Protocol): pass >>> UserSubprotocol.__orig_bases__ (UserProtocol[str], typing.Protocol) # <-- good >>> UserProtocolUnerased = UserSubprotocol.__orig_bases__[0] >>> UserProtocolUnerased is UserProtocol False >>> isinstance(UserProtocolUnerased, type) False # <-- bad :pep:`585`-compliant generics suffer no such issues: .. code-block:: pycon >>> from beartype._util.hint.pep.proposal.utilpep585 import is_hint_pep585_builtin_subscripted >>> class UserGeneric(list[int]): pass >>> class UserSubgeneric(UserGeneric[int]): pass >>> UserSubgeneric.__orig_bases__ (UserGeneric[int],) >>> UserGenericUnerased = UserSubgeneric.__orig_bases__[0] >>> isinstance(UserGenericUnerased, type) True # <-- good >>> UserGenericUnerased.__mro__ (UserGeneric, list, object) >>> is_hint_pep585_builtin_subscripted(UserGenericUnerased) True Iteratively walking up the unerased inheritance hierarchy for any such paradoxical generic or protocol subclass (e.g., ``UserSubprotocol`` but *not* ``UserSubgeneric`` above) would visit a user-defined generic or protocol pseudo-superclass subscripted by type variables. Due to poorly defined obscurities in the :mod:`typing` implementation, that pseudo-superclass is *not* actually a class but rather an instance of a private :mod:`typing` class (e.g., :class:`typing._SpecialForm`). This algorithm would then detect that pseudo-superclass as neither a generic nor a :mod:`typing` object and thus raise an exception. Fortunately, that pseudo-superclass conveys no meaningful intrinsic semantics with respect to type-checking; its only use is to register its own pseudo-superclasses (one or more of which could convey meaningful intrinsic semantics with respect to type-checking) for visitation by this BFS. Parameters ---------- hint : object Generic type hint to be inspected. conf : BeartypeConf **Beartype configuration** (i.e., self-caching dataclass encapsulating all settings configuring type-checking for the passed object). exception_cls : TypeException Type of exception to be raised. Defaults to :exc:`BeartypeDecorHintPep484585Exception`. exception_prefix : str, optional Human-readable substring prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- Iterable Breadth-first search (BFS) generator iteratively yielding the one or more unignorable unerased transitive pseudo-superclasses originally declared as superclasses prior to their type erasure of this generic. Raises ------ exception_cls If this hint is *not* a generic. See Also -------- :func:`get_hint_pep484585_generic_type_or_none` Further details. ''' # Avoid circular import dependencies. from beartype._check.convert.convsanify import ( sanify_hint_child_if_unignorable_or_none) from beartype._util.hint.pep.proposal.utilpep585 import ( is_hint_pep585_builtin_subscripted) from beartype._util.hint.pep.utilpepget import get_hint_pep_sign_or_none from beartype._util.hint.pep.utilpeptest import is_hint_pep_typing # Tuple of the one or more unerased pseudo-superclasses originally listed as # superclasses prior to their type erasure by this generic. hint_bases_direct = get_hint_pep484585_generic_bases_unerased( hint=hint, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # print(f'generic {hint} hint_bases_direct: {hint_bases_direct}') # Fixed list of the one or more unerased transitive pseudo-superclasses # originally listed as superclasses prior to their type erasure by this # generic that have yet to be visited by the breadth-first search (BFS) over # these superclasses performed below. hint_bases = acquire_fixed_list(size=FIXED_LIST_SIZE_MEDIUM) # 0-based index of the currently visited pseudo-superclass of this list. hint_bases_index_curr = 0 # 0-based index of one *PAST* the last pseudo-superclass of this list. hint_bases_index_past_last = len(hint_bases_direct) # Initialize this list to these direct pseudo-superclasses of this generic. hint_bases[0:hint_bases_index_past_last] = hint_bases_direct # print(f'generic pseudo-superclasses [initial]: {repr(hint_bases_direct}') # While the 0-based index of the next visited pseudo-superclass does *NOT* # exceed that of the last pseudo-superclass in this list, there remains one # or more pseudo-superclasses to be visited in this BFS. while hint_bases_index_curr < hint_bases_index_past_last: # Unignorable sane pseudo-superclass sanified from this possibly # ignorable insane pseudo-superclass *OR* "None" otherwise (i.e., # if this pseudo-superclass is ignorable). hint_base = sanify_hint_child_if_unignorable_or_none( hint=hint_bases[hint_bases_index_curr], conf=conf, #FIXME: Possibly also pass this, please. Ignorable for now. *shrug* # cls_stack=cls_stack, exception_prefix=exception_prefix, ) # print(f'generic {hint} base: {repr(hint_base)}') # If this pseudo-superclass is unignorable... if hint_base is not None: # Sign identifying this pseudo-superclass if any *OR* "None". hint_base_sign = get_hint_pep_sign_or_none(hint_base) #FIXME: Unit test up this branch, please. # If this pseudo-superclass is... if ( # A PEP-compliant generic *AND*... hint_base_sign is HintSignGeneric and # Is neither... not ( # A PEP 585-compliant superclass *NOR*... is_hint_pep585_builtin_subscripted(hint_base) and # A PEP 484- or 544-compliant superclass defined by the # "typing" module... is_hint_pep_typing(hint_base) ) ): # Then this pseudo-superclass is a user-defined PEP 484-compliant # generic or 544-compliant protocol. In this case, generate *NO* # type-checking code for this pseudo-superclass; we only enqueue all # parent pseudo-superclasses of this pseudo-superclasses for # visitation by later iteration of this inner BFS. # # See "hints_bases" for explanatory commentary. # Tuple of the one or more parent pseudo-superclasses of this # child pseudo-superclass. hint_base_bases = get_hint_pep484585_generic_bases_unerased( hint=hint_base, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # 0-based index of the last pseudo-superclass of this list # *BEFORE* adding onto this list. hint_bases_index_past_last_prev = hint_bases_index_past_last # 0-based index of the last pseudo-superclass of this list # *AFTER* adding onto this list. hint_bases_index_past_last += len(hint_base_bases) # Enqueue these superclasses onto this list. hint_bases[ hint_bases_index_past_last_prev: hint_bases_index_past_last ] = hint_base_bases # Else, this pseudo-superclass is neither an ignorable user-defined # PEP 484-compliant generic *NOR* an ignorable 544-compliant # protocol. # # If this pseudo-superclass is *NOT* an isinstanceable type # conveying *NO* meaningful semantics, this pseudo-superclass is # unignorable. Yield this unignorable pseudo-superclass. elif hint_base_sign is not None: yield hint_base # Else, this pseudo-superclass is an isinstanceable type conveying # *NO* meaningful semantics and is thus effectively ignorable. Why? # Because the caller already type-checks this pith against the # generic subclassing this superclass and thus this superclass as # well in an isinstance() call (e.g., in the # "CODE_PEP484585_GENERIC_PREFIX" snippet leveraged by the # "beartype._check.code.codemake" submodule). # Else, this pseudo-superclass is ignorable. # else: # print(f'Ignoring generic {repr(hint)} base {repr(hint_base)}...') # print(f'Is generic {hint} base {repr(hint_base)} type? {isinstance(hint_base, type)}') # Nullify the previously visited pseudo-superclass in this list. hint_bases[hint_bases_index_curr] = None # Increment the 0-based index of the next visited pseudo-superclass in # this list *BEFORE* visiting that pseudo-superclass but *AFTER* # performing all other logic for the current pseudo-superclass. hint_bases_index_curr += 1 # Release this list. Pray for salvation, for we find none here. release_fixed_list(hint_bases) beartype-0.18.5/beartype/_util/hint/pep/proposal/pep484585/utilpep484585ref.py000066400000000000000000000666331461113517100264730ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`484`- and :pep:`585`-compliant **forward reference type hint utilities** (i.e., callables generically applicable to both :pep:`484`- and :pep:`585`-compliant forward reference type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeDecorHintForwardRefException from beartype.typing import ( Optional, Tuple, ) from beartype._cave._cavemap import NoneTypeOr from beartype._data.cls.datacls import ( TYPE_BUILTIN_NAME_TO_TYPE, TYPES_PEP484585_REF, ) from beartype._data.hint.datahinttyping import ( Pep484585ForwardRef, TypeException, TypeStack, ) from beartype._data.module.datamodpy import BUILTINS_MODULE_NAME from beartype._util.module.utilmodget import get_object_module_name_or_none from beartype._util.module.utilmodtest import is_module from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_10 from beartype._util.text.utiltextlabel import label_callable from collections.abc import ( Callable, Sequence, ) # ....................{ VALIDATORS }.................... #FIXME: Validate that this forward reference string is *NOT* the empty string. #FIXME: Validate that this forward reference string is a syntactically valid #"."-delimited concatenation of Python identifiers. We already have logic #performing that validation somewhere, so let's reuse that here, please. #Right. So, we already have an is_identifier() tester; now, we just need to #define a new die_unless_identifier() validator. def die_unless_hint_pep484585_ref( # Mandatory parameters. hint: object, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintForwardRefException, exception_prefix: str = '', ) -> None: ''' Raise an exception unless the passed object is either a :pep:`484`- or :pep:`585`-compliant **forward reference type hint** (i.e., object referring to a user-defined class that typically has yet to be defined). Equivalently, this validator raises an exception if this object is neither: * A string whose value is the syntactically valid name of a class. * An instance of the :class:`typing.ForwardRef` class. The :mod:`typing` module implicitly replaces all strings subscripting :mod:`typing` objects (e.g., the ``MuhType`` in ``List['MuhType']``) with :class:`typing.ForwardRef` instances containing those strings as instance variables, for nebulous reasons that make little justifiable sense but what you gonna do 'cause this is 2020. *Fight me.* Parameters ---------- hint : object Object to be validated. exception_cls : Type[Exception] Type of exception to be raised in the event of a fatal error. Defaults to :exc:`.BeartypeDecorHintForwardRefException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Raises ------ exception_cls If this object is *not* a forward reference type hint. ''' # If this object is *NOT* a forward reference type hint, raise an exception. if not isinstance(hint, TYPES_PEP484585_REF): assert isinstance(exception_cls, type), ( f'{repr(exception_cls)} not exception subclass.') assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') raise exception_cls( f'{exception_prefix}type hint {repr(hint)} not forward reference ' f'(i.e., neither string nor "typing.ForwardRef" object).' ) # Else, this object is a forward reference type hint. # ....................{ GETTERS }.................... #FIXME: Unit test us up, please. def get_hint_pep484585_ref_names( # Mandatory parameters. hint: Pep484585ForwardRef, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintForwardRefException, exception_prefix: str = '', ) -> Tuple[Optional[str], str]: ''' Possibly undefined fully-qualified module name and possibly qualified classname referred to by the passed **forward reference type hint** (i.e., object indirectly referring to a user-defined class that typically has yet to be defined). This getter is intentionally *not* memoized (e.g., by the ``callable_cached`` decorator), as the implementation mostly reduces to an efficient one-liner. Caveats ------- **Callers are recommended to call the higher-level** :func:`.get_hint_pep484585_ref_names_relative_to` **getter rather than this lower-level getter,** which fails to guarantee canonicalization and is thus considerably less safe. Parameters ---------- hint : object Forward reference to be introspected. exception_cls : Type[Exception] Type of exception to be raised in the event of a fatal error. Defaults to :exc:`.BeartypeDecorHintForwardRefException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- Tuple[Optional[str], str] 2-tuple ``(ref_module_name, ref_name)`` where: * ``ref_module_name`` is the possibly undefined fully-qualified module name referred to by this forward reference, defined as either: * If this forward reference is a :class:`typing.ForwardRef` object passed a module name at instantiation time via the ``module`` parameter, this module name. * Else, :data:`None`. * ``ref_name`` is the possibly qualified classname referred to by this forward reference. Raises ------ exception_cls If either: * This forward reference is *not* actually a forward reference. * This forward reference is **relative** (i.e., contains *no* ``.`` delimiters) and either: * Neither the passed callable nor class define the ``__module__`` dunder attribute. * The passed callable and/or class define the ``__module__`` dunder attribute, but the values of those attributes all refer to unimportable modules that do *not* appear to physically exist. See Also -------- :func:`.get_hint_pep484585_ref_name` Lower-level getter returning possibly relative forward references. ''' # If this is *NOT* a forward reference, raise an exception. die_unless_hint_pep484585_ref(hint) # Else, this is a forward reference. # Possibly unqualified basename of the class to which reference refers. hint_name: str = None # type: ignore[assignment] # Fully-qualified name of the module to which this reference is relative if # this reference is relative to an importable module *OR* "None" otherwise # (i.e., if this reference is either absolute and thus not relative to a # module *OR* relative to an unimportable module). # # Note that, although *ALL* callables and classes should define the # "__module__" instance variable underlying the call to this getter, *SOME* # callables and classes do not. For this reason, we intentionally: # * Call the get_object_module_name_or_none() getter rather than # get_object_module_name(). # * Explicitly detect "None". # * Raise a human-readable exception. # # Doing so produces significantly more readable exceptions than merely # calling get_object_module_name(). Problematic objects include: # * Objects defined in Sphinx-specific "conf.py" configuration files. In all # likelihood, Sphinx is running these files in some sort of arcane and # non-standard manner (over which beartype has *NO* control). hint_module_name: Optional[str] = None # If this reference is a string, the classname of this reference is this # reference itself. if isinstance(hint, str): hint_name = hint # Else, this reference is *NOT* a string. By process of elimination, this # reference *MUST* be a "typing.ForwardRef" instance. In this case... else: # Forward reference classname referred to by this reference. hint_name = hint.__forward_arg__ # If the active Python interpreter targets >= Python 3.10, then this # "typing.ForwardRef" object defines an optional "__forward_module__: # Optional[str] = None" dunder attribute whose value is either: # * If Python passed the "module" parameter when instantiating this # "typing.ForwardRef" object, the value of that parameter -- which is # presumably the fully-qualified name of the module to which this # presumably relative forward reference is relative to. # * Else, "None". # # Note that: # * This requires violating privacy encapsulation by accessing dunder # attributes unique to "typing.ForwardRef" objects. Sad, yet true. # * This object defines a significant number of other # "__forward_"-prefixed dunder instance variables, which exist *ONLY* # to enable the blatantly useless typing.get_type_hints() function to # avoid repeatedly (and thus inefficiently) reevaluating the same # forward reference. *sigh* # * Technically, this dunder attribute has been defined since at least # Python >= 3.9.18. Sadly, one or more unknown earlier patch releases # of the Python 3.9 development cycle do *NOT* support this. This is # currently only safely usable under Python >= 3.10 -- all patch # releases of which are known to define this dunder attribute. # # In this case... if IS_PYTHON_AT_LEAST_3_10: # Fully-qualified name of the module to which this presumably # relative forward reference is relative to if any *OR* "None" # otherwise (i.e., if *NO* such name was passed at forward reference # instantiation time). hint_module_name = hint.__forward_module__ # Else, the active Python interpreter targets < Python 3.9 and thus # fails to define the "__forward_module__" dunder attribute. # Return metadata describing this forward reference relative to this module. return hint_module_name, hint_name def get_hint_pep484585_ref_names_relative_to( # Mandatory parameters. hint: Pep484585ForwardRef, # Optional parameters. cls_stack: TypeStack = None, func: Optional[Callable] = None, exception_cls: TypeException = BeartypeDecorHintForwardRefException, exception_prefix: str = '', ) -> Tuple[Optional[str], str]: ''' Possibly undefined fully-qualified module name and possibly qualified classname referred to by the passed **forward reference type hint** (i.e., object indirectly referring to a user-defined class that typically has yet to be defined), canonicalized relative to the module declaring the passed type stack and/or callable (in that order) if this classname is unqualified. This getter is intentionally *not* memoized (e.g., by the ``callable_cached`` decorator), as the implementation mostly reduces to an efficient one-liner. Caveats ------- This getter preferentially canonicalizes this forward reference if relative against the fully-qualified name of the module defining (in order): #. The passed class stack if *not* :data:`None`. #. The passed callable. This getter thus prioritizes classes over callables. Why? Because classes are more likely to define ``__module__`` dunder attributes referring to importable modules that physically exist. Why? Because dynamically synthesizing in-memory callables residing in imaginary and thus unimportable modules is trivial; dynamically synthesizing in-memory classes residing in imaginary and thus unimportable modules is less trivial. Consider the standard use case for :mod:`beartype`: beartype import hooks declared by the :mod:`beartype.claw` subpackage. Although hooks directly apply the :func:`beartype.beartype` decorator to classes and functions residing in importable modules that physically exist, that decorator then dynamically iterates over the methods of those classes. That iteration is dynamic and iterates over methods that both physically exist and only dynamically exist in-memory in unimportable modules. Does this edge case arise in real-world code? All too frequently. For unknown reasons, the :class:`typing.NamedTuple` superclass dynamically generates dunder methods (e.g., ``__new__``) whose ``__module__`` dunder attributes erroneously refer to imaginary and thus unimportable modules ``named_{subclass_name}`` for the unqualified basename ``{subclass_name}`` of the current user-defined class subclassing :class:`typing.NamedTuple` despite that user-defined class residing in an importable module: e.g., .. code-block:: pycon >>> from beartype import beartype >>> from typing import NamedTuple >>> @beartype ... class NamelessTupleIsBlameless(NamedTuple): ... forward_ref: 'UndefinedType' >>> NamelessTupleIsBlameless.__module__ '__main__' # <-- makes sense >>> NamelessTupleIsBlameless.__new__.__module__ 'named_NamelessTupleIsBlameless' # <-- lol wut If this getter erroneously prioritized callables over classes *and* blindly accepted imaginary modules as valid, this getter would erroneously resolve the relative forward reference ``'UndefinedType'`` to ``'named_NamelessTupleIsBlameless.UndefinedType'`` rather than to ``'__main__.UndefinedType'``. And... this is why @leycec is currently bald. Parameters ---------- hint : object Forward reference to be canonicalized. cls_stack : TypeStack Either: * If this forward reference annotates a method of a class, the corresponding **type stack** (i.e., tuple of the one or more :func:`beartype.beartype`-decorated classes lexically containing that method). If this forward reference is unqualified (i.e., relative), this getter then canonicalizes this reference against that class. * Else, :data:`None`. Defaults to :data:`None`. func : Optional[Callable] Either: * If this forward reference annotates a callable, that callable. If this forward reference is also unqualified (i.e., relative), this getter then canonicalizes this reference against that callable. * Else, :data:`None`. Defaults to :data:`None`. exception_cls : Type[Exception] Type of exception to be raised in the event of a fatal error. Defaults to :exc:`.BeartypeDecorHintForwardRefException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- Tuple[Optional[str], str] 2-tuple ``(ref_module_name, ref_name)`` where: * ``ref_module_name`` is the possibly undefined fully-qualified module name referred to by this forward reference, defined as either: * If this forward reference is a :class:`typing.ForwardRef` object passed a module name at instantiation time via the ``module`` parameter *or* a type stack or callable defining a module name via the ``__module`` dunder attribute are passed, this module name. * Else, :data:`None`. * ``ref_name`` is the possibly qualified classname referred to by this forward reference. Raises ------ exception_cls If either: * This forward reference is *not* actually a forward reference. * This forward reference is **relative** (i.e., contains *no* ``.`` delimiters) and either: * Neither the passed callable nor class define the ``__module__`` dunder attribute. * The passed callable and/or class define the ``__module__`` dunder attribute, but the values of those attributes all refer to unimportable modules that do *not* appear to physically exist. See Also -------- :func:`.get_hint_pep484585_ref_names` Lower-level getter returning possibly relative forward references. ''' # Possibly undefined fully-qualified module name and possibly unqualified # classname referred to by this forward reference. hint_module_name, hint_ref_name = get_hint_pep484585_ref_names( hint=hint, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # If either... if ( # This reference was instantiated with a module name *OR*... hint_module_name or # This classname contains one or more "." characters and is thus already # (...hopefully) fully-qualified... '.' in hint_ref_name # Then this classname is either absolute *OR* relative to some module. In # either case, the class referred to by this reference can now be # dynamically imported at a later time. In this case... ): # Return this metadata as is. return hint_module_name, hint_ref_name # Else, this classname is relative to a module whose fully-qualified name # has yet to be decided. # # If this reference does *NOT* annotate a callable, then this reference also # does *NOT* annotate a method of a class (i.e., "cls is None"). Why? # Because a method is a callable. In this case... elif not func: # If a builtin type with this classname exists, assume this reference # refers to this builtin type exposed by the standard "builtins" module. if hint_ref_name in TYPE_BUILTIN_NAME_TO_TYPE: hint_module_name = BUILTINS_MODULE_NAME # Else, this reference does *NOT* refer to a builtin type. In this # case, there exists *NO* owner module against which to canonicalize # this relative reference. This edge case occurs when this getter is # transitively called by a high-level "beartype.door" runtime # type-checker (e.g., is_bearable(), die_if_unbearable()). In this case, # raise an exception. else: raise exception_cls( f'{exception_prefix}type hint relative forward reference ' f'"{hint_ref_name}" currently only type-checkable in ' f'type hints annotating ' f'@beartype-decorated callables and classes. ' f'For your own safety and those of the codebases you love, ' f'consider canonicalizing this ' f'relative forward reference into an ' f'absolute forward reference ' f'(e.g., replace "{hint_ref_name}" with ' f'"{{your_package}}.{{your_submodule}}.{hint_ref_name}").' ) # Else, this reference annotates a callable and is thus relative to the # module defining this callable. # Validate sanity. assert isinstance(cls_stack, NoneTypeOr[Sequence]), ( f'{repr(cls_stack)} neither sequence nor "None".') assert isinstance(func, NoneTypeOr[Callable]), ( f'{repr(func)} neither callable nor "None".') # If this reference annotates a method of a class... if cls_stack: # Class currently being decorated by @beartype. cls = cls_stack[-1] # Fully-qualified name of the module defining that class. hint_module_name = get_object_module_name_or_none(cls) # Else, this reference does *NOT* annotate a method of a class. # If it is *NOT* the case that... if not ( # That class is declared by a module *AND*... hint_module_name and # That module is importable... is_module(hint_module_name) # Fallback to... ): # Fully-qualified name of the callable annotated by this reference. hint_module_name = get_object_module_name_or_none(func) # If it is *NOT* the case that... if not ( # That callable is declared by a module *AND*... hint_module_name and # That module is importable... is_module(hint_module_name) # Fallback to... ): # If a builtin type with this name exists, assume this reference # refers to this builtin type exposed by the "builtins" module. # # Note that this edge case is shockingly common *AND* distinct from # the above similar edge case. Why? The "typing.NamedTuple" # superclass, which dynamically generates subclass dunder methods # (e.g., __new__()) residing in unimportable fake modules with the # name "named_{subclass_name}". if hint_ref_name in TYPE_BUILTIN_NAME_TO_TYPE: hint_module_name = BUILTINS_MODULE_NAME # Else, this reference does *NOT* refer to a builtin type. In this # case, there exists *NO* owner module against which to canonicalize # this relative reference. Raise an exception, please. else: # Exception message to be raised. exception_message = ( f'{exception_prefix}type hint relative forward reference ' f'"{hint_ref_name}" unresolvable relative to' ) # If this reference annotates a method of a class... if cls_stack: # Fully-qualified name of the module defining that class. type_module_name = get_object_module_name_or_none(cls) # pyright: ignore # Append this message by... exception_message += ( # Substring describing this class *AND*... f':\n* {repr(cls)} ' + # pyright: ignore ( # If that class defines a "__module__" dunder # attribute, substring describing that module. f'in unimportable module "{type_module_name}".' if type_module_name else # Else, that class defines *NO* such attribute. In # this case, a substring describing that failure. 'with undefined "__module__" attribute.' ) + # Substring suffixing this item and prefixing the next # item. '\n* ' ) # Else, this reference annotates a global or local function. In # this case, append a substring prefixing the next item. else: exception_message += ' ' # Append this message by an appropriate substring defined as # the dynamic concatenation of... exception_message += ( # Substring describing this callable if callable is actually # callable or falling back to its representation otherwise # *AND*... ( f'{label_callable(func)} ' if callable(func) else f'{repr(func)} ' ) + # Substring describing this module. ( f'in unimportable module "{hint_module_name}".' if hint_module_name else 'with undefined "__module__" attribute.' ) ) # Raise this exception. raise exception_cls(exception_message) # Else, that function is declared by an importable module. # Else, that class is declared by an importable module. # # In either case, at least one of either that class or function is declared # by an importable module. # Return metadata describing this forward reference relative to this module. return hint_module_name, hint_ref_name # ....................{ IMPORTERS }.................... #FIXME: Unit test us up, please. def import_pep484585_ref_type( # Mandatory parameters. hint: Pep484585ForwardRef, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintForwardRefException, exception_prefix: str = '', **kwargs ) -> type: ''' Class referred to by the passed :pep:`484` or :pep:`585`-compliant **forward reference type hint** (i.e., object indirectly referring to a user-defined class that typically has yet to be defined) canonicalized if this hint is unqualified relative to the module declaring the first of whichever of the passed owner type and/or callable is *not* :data:`None`. This getter is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the passed object is typically a :func:`beartype.beartype`-decorated callable passed exactly once to this function. Parameters ---------- hint : Pep484585ForwardRef Forward reference type hint to be resolved. exception_cls : Type[Exception] Type of exception to be raised in the event of a fatal error. Defaults to :exc:`.BeartypeDecorHintForwardRefException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. All remaining keyword parameters are passed as is to the lower-level :func:`.get_hint_pep484585_ref_names_relative_to` getter. Returns ------- type Class referred to by this forward reference. Raises ------ exception_cls If either: * This forward reference is *not* actually a forward reference. * This forward reference is **relative** (i.e., contains *no* ``.`` delimiters) and either: * Neither the passed callable nor class define the ``__module__`` dunder attribute. * The passed callable and/or class define the ``__module__`` dunder attribute, but the values of those attributes all refer to unimportable modules that do *not* appear to physically exist. * The object referred to by this forward reference is either: * Undefined. * Defined but not a class. See Also -------- :func:`.get_hint_pep484585_ref_names_relative_to` Further details. ''' # Avoid circular import dependencies. from beartype._check.forward.reference.fwdrefmake import ( make_forwardref_indexable_subtype) # Possibly undefined fully-qualified module name and possibly unqualified # classname referred to by this forward reference relative to this type # stack and callable. hint_module_name, hint_ref_name = get_hint_pep484585_ref_names_relative_to( hint=hint, exception_cls=exception_cls, exception_prefix=exception_prefix, **kwargs ) # Forward reference proxy referring to this class. hint_ref = make_forwardref_indexable_subtype( hint_module_name, hint_ref_name) # Return the class dynamically imported from this proxy. return hint_ref.__type_beartype__ beartype-0.18.5/beartype/_util/hint/pep/proposal/pep484585/utilpep484585type.py000066400000000000000000000242401461113517100266640ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`484`- and :pep:`585`-compliant **dual type hint utilities** (i.e., callables generically applicable to both :pep:`484`- and :pep:`585`-compliant type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeDecorHintPep484585Exception from beartype.typing import ( Tuple, TypeVar, Union, ) from beartype._data.hint.pep.sign.datapepsigns import ( HintSignForwardRef, HintSignType, HintSignUnion, ) from beartype._data.hint.datahinttyping import Pep484585ForwardRef from beartype._util.cls.pep.utilpep3119 import ( die_unless_type_issubclassable, die_unless_object_issubclassable, ) from beartype._util.hint.pep.proposal.pep484585.utilpep484585 import ( get_hint_pep484585_args) from typing import ( Type as typing_Type, # <-- intentional to distinguish from "type" below ) # ....................{ HINTS ~ private }.................... _HINT_PEP484585_SUBCLASS_ARGS_1_UNION = Union[ type, Tuple[type], TypeVar, Pep484585ForwardRef,] ''' Union of the types of all permissible :pep:`484`- or :pep:`585`-compliant **subclass type hint arguments** (i.e., PEP-compliant child type hints subscripting (indexing) a subclass type hint). ''' # ....................{ GETTERS }.................... def get_hint_pep484585_type_superclass( hint: object, exception_prefix: str, ) -> _HINT_PEP484585_SUBCLASS_ARGS_1_UNION: ''' **Issubclassable superclass(es)** (i.e., class whose metaclass does *not* define a ``__subclasscheck__()`` dunder method that raises an exception, tuple of such classes, or forward reference to such a class) subscripting the passed :pep:`484`- or :pep:`585`-compliant **subclass type hint** (i.e., hint constraining objects to subclass that superclass). This getter is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object Object to be inspected. exception_prefix : str Human-readable label prefixing the representation of this object in the exception message. Returns ------- _HINT_PEP484585_SUBCLASS_ARGS_1_UNION Argument subscripting this subclass type hint, guaranteed to be either: * An issubclassable class. * A tuple of issubclassable classes. * A :pep:`484`-compliant forward reference to an issubclassable class that typically has yet to be declared (i.e., :class:`typing.ForwardRef` instance). * A :pep:`484`-compliant type variable constrained to classes (i.e., :class:`typing.TypeVar` instance). * A :pep:`585`-compliant union of two or more issubclassable classes. * A :pep:`484`-compliant type variable constrained to classes (i.e., :class:`typing.TypeVar` instance). Raises ------ BeartypeDecorHintPep3119Exception If this superclass subscripting this type hint is *not* **issubclassable** (i.e., class whose metaclass defines a ``__subclasscheck__()`` dunder method raising an exception). BeartypeDecorHintPep484585Exception If this hint is either: * Neither a :pep:`484`- nor :pep:`585`-compliant subclass type hint. * A :pep:`484`- or :pep:`585`-compliant subclass type hint subscripted by one argument that is neither a class, union of classes, nor forward reference to a class. BeartypeDecorHintPep585Exception If this hint is either: * A :pep:`585`-compliant subclass type hint subscripted by either: * *No* arguments. * Two or more arguments. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.utilpepget import ( get_hint_pep_args, get_hint_pep_sign, get_hint_pep_sign_or_none, ) # If this is neither a PEP 484- *NOR* PEP 585-compliant subclass type hint, # raise an exception. if get_hint_pep_sign(hint) is not HintSignType: raise BeartypeDecorHintPep484585Exception( f'{exception_prefix}{repr(hint)} ' f'neither PEP 484 nor 585 subclass type hint.' ) # Else, this is a subclass type hint. # Superclass subscripting this hint. hint_superclass = get_hint_pep484585_args( hint=hint, args_len=1, exception_prefix=exception_prefix) # Sign identifying this superclass. hint_superclass_sign = get_hint_pep_sign_or_none(hint_superclass) # If this superclass is actually a union of superclasses... if hint_superclass_sign is HintSignUnion: # Efficiently reduce this superclass to the tuple of superclasses # subscripting and thus underlying this union. hint_superclass = get_hint_pep_args(hint_superclass) # If any item of this tuple is *NOT* an issubclassable class, raise an # exception. # print(f'hint_superclass union arg: {hint_superclass}') die_unless_object_issubclassable( obj=hint_superclass, exception_prefix=exception_prefix) # type: ignore[arg-type] # Else, this superclass is *NOT* a union of superclasses. # # If this superclass is actually a forward reference to a superclass, # silently accept this reference as is. This conditional exists only to # avoid raising a subsequent exception. elif hint_superclass_sign is HintSignForwardRef: pass # Else, this superclass is *NOT* a forward reference to a superclass. # # If this superclass is a class... elif isinstance(hint_superclass, type): die_unless_type_issubclassable( cls=hint_superclass, exception_prefix=exception_prefix) # Else, this superclass is issubclassable. # Else, this superclass is of an unexpected type. In this case, raise an # exception. # # Note that PEP 585-compliant subclass type hints infrequently trigger this # edge case. Although the "typing" module explicitly validates the # arguments subscripting PEP 484-compliant type hints, the CPython # interpreter applies *NO* such validation to PEP 585-compliant subclass # type hints. For example, PEP 585-compliant subclass type hints are # subscriptable by the empty tuple, which is technically an argument: # >>> type[()].__args__ # () # <---- thanks fer nuthin else: raise BeartypeDecorHintPep484585Exception( f'{exception_prefix}PEP 484 or 585 subclass type hint ' f'{repr(hint)} child type hint {repr(hint_superclass)} neither ' f'class, union of classes, nor forward reference to class.' ) # Return this superclass. return hint_superclass # type: ignore[return-value] # ....................{ REDUCERS }.................... #FIXME: Unit test us up. def reduce_hint_pep484585_type( hint: object, exception_prefix: str, *args, **kwargs) -> object: ''' Reduce the passed :pep:`484`- or :pep:`585`-compliant **subclass type hint** (i.e., hint constraining objects to subclass that superclass) to the :class:`type` superclass if that hint is subscripted by an ignorable child type hint (e.g., :attr:`typing.Any`, :class:`type`) *or* preserve this hint as is otherwise (i.e., if that hint is *not* subscripted by an ignorable child type hint). This reducer is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object Subclass type hint to be reduced. exception_prefix : str Human-readable label prefixing the representation of this object in the exception message. All remaining passed arguments are silently ignored. Raises ------ BeartypeDecorHintPep484585Exception If this hint is neither a :pep:`484`- nor :pep:`585`-compliant subclass type hint. ''' # Avoid circular import dependencies. from beartype._util.hint.utilhinttest import is_hint_ignorable # If this hint is the unsubscripted PEP 484-compliant subclass type hint, # immediately reduce this hint to the "type" superclass. # # Note that this is *NOT* merely a nonsensical optimization. The # implementation of the unsubscripted PEP 484-compliant subclass type hint # significantly differs across Python versions. Under some but *NOT* all # supported Python versions (notably, Python 3.7 and 3.8), the "typing" # module subversively subscripts this hint by a type variable; under all # others, this hint remains unsubscripted. In the latter case, passing this # hint to the subsequent get_hint_pep484585_args() call would erroneously # raise an exception. if hint is typing_Type: return type # Else, this hint is *NOT* the unsubscripted PEP 484-compliant subclass # type hint. # Superclass subscripting this hint. # # Note that we intentionally do *NOT* call the high-level # get_hint_pep484585_type_superclass() getter here, as the # validation performed by that function would raise exceptions for # various child type hints that are otherwise permissible (e.g., # "typing.Any"). hint_superclass = get_hint_pep484585_args( hint=hint, args_len=1, exception_prefix=exception_prefix) # If this argument is either... if ( # An ignorable type hint (e.g., "typing.Any") *OR*... is_hint_ignorable(hint_superclass) or # The "type" superclass, which is effectively ignorable in this # context of subclasses, as *ALL* classes necessarily subclass # that superclass. hint_superclass is type ): # Reduce this subclass type hint to the "type" superclass. hint = type # Else, this argument is unignorable and thus irreducible. # Return this possibly reduced type hint. return hint beartype-0.18.5/beartype/_util/hint/pep/proposal/utilpep544.py000066400000000000000000000623731461113517100242400ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`544`-compliant type hint utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from abc import abstractmethod from beartype.roar import BeartypeDecorHintPep544Exception from beartype.typing import ( Any, BinaryIO, Dict, IO, Optional, TextIO, ) from beartype._data.module.datamodtyping import TYPING_MODULE_NAMES from beartype._util.cls.utilclstest import is_type_builtin_or_fake from typing import Protocol as typing_Protocol # <-- unoptimized protocol # ....................{ TESTERS }.................... def is_hint_pep544_ignorable(hint: object) -> bool: ''' :data:`True` only if the passed :pep:`484`-compliant type hint is ignorable. Specifically, this tester returns :data:`True` only if this hint is a parametrization of the :class:`typing.Protocol` abstract base class (ABC) by one or more type variables. As the name implies, this ABC is generic and thus fails to impose any meaningful constraints. Since a type variable in and of itself also fails to impose any meaningful constraints, these parametrizations are safely ignorable in all possible contexts: e.g., .. code-block:: python from typing import Protocol, TypeVar T = TypeVar('T') def noop(param_hint_ignorable: Protocol[T]) -> T: pass This tester is intentionally *not* memoized (e.g., by the ``callable_cached`` decorator), as this tester is only safely callable by the memoized parent :func:`beartype._util.hint.utilhinttest.is_hint_ignorable` tester. Parameters ---------- hint : object Type hint to be inspected. Returns ------- bool :data:`True` only if this :pep:`544`-compliant type hint is ignorable. ''' # Avoid circular import dependencies. from beartype._util.hint.utilhintget import get_hint_repr # Machine-readable representation of this hint. hint_repr = get_hint_repr(hint) # If this representation contains a relevant substring suggesting that this # hint might be the "Protocol" superclass directly parametrized by type # variables (e.g., "typing.Protocol[S, T]")... if 'Protocol[' in hint_repr: # For the fully-qualified name of each typing module... for typing_module_name in TYPING_MODULE_NAMES: # If this hint is the "Protocol" superclass defined by this module # directly parametrized by one or more type variables (e.g., # "typing.Protocol[S, T]"), ignore this superclass by returning # true. This superclass can *ONLY* be parametrized by type # variables; a string test thus suffices. # # For unknown and uninteresting reasons, *ALL* possible objects # satisfy the "Protocol" superclass. Ergo, this superclass and *ALL* # parametrizations of this superclass are synonymous with the # "object" root superclass. if hint_repr.startswith(f'{typing_module_name}.Protocol['): return True # Else, this hint is *NOT* such a "Protocol" superclass. In this # case, continue to the next typing module. # Else, this hint is *NOT* the "Protocol" superclass directly # parametrized by one or more type variables. # Else, this representation contains such *NO* such substring. # Return false, as *ALL* other "Protocol" subclasses are unignorable. return False def is_hint_pep484_generic_io(hint: object) -> bool: ''' :data:`True` only if the passed object is a functionally useless :pep:`484`-compliant :mod:`typing` **IO generic superclass** (i.e., either :class:`typing.IO` itself *or* a subclass of :class:`typing.IO` defined by the :mod:`typing` module effectively unusable at runtime due to botched implementation details) that is losslessly replaceable with a useful :pep:`544`-compliant :mod:`beartype` **IO protocol** (i.e., either :class:`_Pep544IO` itself *or* a subclass of that class defined by this submodule intentionally designed to be usable at runtime). This tester is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object Object to be inspected. Returns ------- bool :data:`True` only if this object is a :pep:`484`-compliant IO generic base class. See Also -------- :class:`_Pep544IO` Further commentary. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.utilpepget import ( get_hint_pep_origin_or_none) # Return true only if this hint is either... return ( # An unsubscripted PEP 484-compliant IO generic base class # (e.g., "typing.IO") *OR*.... (isinstance(hint, type) and hint in _HINTS_PEP484_IO_GENERIC) or # A subscripted PEP 484-compliant IO generic base class # (e.g., "typing.IO[str]") *OR*.... get_hint_pep_origin_or_none(hint) in _HINTS_PEP484_IO_GENERIC ) def is_hint_pep544_protocol(hint: object) -> bool: ''' :data:`True` only if the passed object is a :pep:`544`-compliant **protocol** (i.e., subclass of the :class:`typing.Protocol` superclass). This tester is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object Object to be inspected. Returns ------- bool :data:`True` only if this object is a :pep:`544`-compliant protocol. ''' # Return true only if this hint is... return ( # A type *AND*... isinstance(hint, type) and # A PEP 544-compliant protocol *AND*... issubclass(hint, typing_Protocol) and # type: ignore[arg-type] # *NOT* a builtin type. For unknown reasons, some but *NOT* all # builtin types erroneously present themselves to be PEP # 544-compliant protocols under Python >= 3.8: e.g., # >>> from typing import Protocol # >>> issubclass(str, Protocol) # False # <--- this makes sense # >>> issubclass(int, Protocol) # True # <--- this makes no sense whatsoever # # Since builtin types are obviously *NOT* PEP 544-compliant # protocols, explicitly exclude all such types. Why, Guido? Why? # # Do *NOT* ignore fake builtins for the purposes of this test. Why? # Because even fake builtins (e.g., "type(None)") erroneously # masquerade as PEP 544-compliant protocols! :o not is_type_builtin_or_fake(hint) # pyright: ignore ) # ....................{ REDUCERS }.................... def reduce_hint_pep484_generic_io_to_pep544_protocol( hint: Any, exception_prefix: str) -> Any: ''' :pep:`544`-compliant :mod:`beartype` **IO protocol** (i.e., either :class:`._Pep544IO` itself *or* a subclass of that class defined by this submodule intentionally designed to be usable at runtime) corresponding to the passed :pep:`484`-compliant :mod:`typing` **IO generic base class** (i.e., either :class:`typing.IO` itself *or* a subclass of :class:`typing.IO` defined by the :mod:`typing` module effectively unusable at runtime due to botched implementation details). This reducer is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner thanks to caching internally performed by this reducer. Parameters ---------- hint : type :pep:`484`-compliant :mod:`typing` IO generic base class to be replaced by the corresponding :pep:`544`-compliant :mod:`beartype` IO protocol. exception_prefix : str Human-readable label prefixing the representation of this object in the exception message. Returns ------- Protocol :pep:`544`-compliant :mod:`beartype` IO protocol corresponding to this :pep:`484`-compliant :mod:`typing` IO generic base class. Raises ------ BeartypeDecorHintPep544Exception If this object is *not* a :pep:`484`-compliant IO generic base class. ''' # If this object is *NOT* a PEP 484-compliant "typing" IO generic, # raise an exception. if not is_hint_pep484_generic_io(hint): raise BeartypeDecorHintPep544Exception( f'{exception_prefix}type hint {repr(hint)} not ' f'PEP 484 IO generic base class ' f'(i.e., "typing.IO", "typing.BinaryIO", or "typing.TextIO").' ) # Else, this object is *NOT* a PEP 484-compliant "typing" IO generic. # # If this dictionary has yet to be initialized, this submodule has yet to be # initialized. In this case, do so. # # Note that this initialization is intentionally deferred until required. # Why? Because this initialization performs somewhat space- and # time-intensive work -- including importation of the "beartype.vale" # subpackage, which we strictly prohibit importing from global scope. elif not _HINT_PEP484_IO_GENERIC_TO_PEP544_PROTOCOL: _init() # In any case, this dictionary is now initialized. # PEP 544-compliant IO protocol implementing this PEP 484-compliant IO # generic if any *OR* "None" otherwise. pep544_protocol = _HINT_PEP484_IO_GENERIC_TO_PEP544_PROTOCOL.get(hint) # If *NO* PEP 544-compliant IO protocol implements this generic... if pep544_protocol is None: # Avoid circular import dependencies. from beartype._util.hint.pep.utilpepget import ( get_hint_pep_origin_or_none, get_hint_pep_typevars, ) # Tuple of zero or more type variables parametrizing this hint. hint_typevars = get_hint_pep_typevars(hint) #FIXME: Unit test us up, please. # If this hint is unparametrized, raise an exception. if not hint_typevars: raise BeartypeDecorHintPep544Exception( f'{exception_prefix}PEP 484 IO generic base class ' f'{repr(hint)} invalid (i.e., not subscripted (indexed) by ' f'either "str", "bytes", "typing.Any", or "typing.AnyStr").' ) # Else, this hint is parametrized and thus defines the "__origin__" # dunder attribute whose value is the type originating this hint. #FIXME: Attempt to actually handle this type variable, please. # Reduce this parametrized hint (e.g., "typing.IO[typing.AnyStr]") to # the equivalent unparametrized hint (e.g., "typing.IO"), effectively # ignoring the type variable parametrizing this hint. hint_unparametrized: type = get_hint_pep_origin_or_none(hint) # type: ignore[assignment] # PEP 544-compliant IO protocol implementing this unparametrized PEP # 484-compliant IO generic. For efficiency, we additionally cache this # mapping under the original parametrized hint to minimize the cost of # similar reductions under subsequent annotations. pep544_protocol = \ _HINT_PEP484_IO_GENERIC_TO_PEP544_PROTOCOL[hint] = \ _HINT_PEP484_IO_GENERIC_TO_PEP544_PROTOCOL[hint_unparametrized] # Else, some PEP 544-compliant IO protocol implements this generic. # Return this protocol. return pep544_protocol # ....................{ PRIVATE ~ mappings }.................... _HINTS_PEP484_IO_GENERIC = frozenset((IO, BinaryIO, TextIO,)) ''' Frozen set of all :mod:`typing` **IO generic base class** (i.e., either :class:`typing.IO` itself *or* a subclass of :class:`typing.IO` defined by the :mod:`typing` module). ''' # Conditionally initialized by the _init() function below. _HINT_PEP484_IO_GENERIC_TO_PEP544_PROTOCOL: Dict[type, Any] = {} ''' Dictionary mapping from each :mod:`typing` **IO generic base class** (i.e., either :class:`typing.IO` itself *or* a subclass of :class:`typing.IO` defined by the :mod:`typing` module) to the associated :mod:`beartype` **IO protocol** (i.e., either :class:`_Pep544IO` itself *or* a subclass of :class:`_Pep544IO` defined by this submodule). ''' # ....................{ PRIVATE ~ classes }.................... # Conditionally initialized by the _init() function below. _Pep544IO: Any = None # type: ignore[assignment] ''' :pep:`544`-compliant protocol base class for :class:`_Pep544TextIO` and :class:`_Pep544BinaryIO`. This is an abstract, generic version of the return of open(). NOTE: This does not distinguish between the different possible classes (text vs. binary, read vs. write vs. read/write, append-only, unbuffered). The TextIO and BinaryIO subclasses below capture the distinctions between text vs. binary, which is pervasive in the interface; however we currently do not offer a way to track the other distinctions in the type system. Design ------ This base class intentionally duplicates the contents of the existing :class:`typing.IO` generic base class by substituting the useless :class:`typing.Generic` superclass of the latter with the useful :class:`typing.Protocol` superclass of the former. Why? Because *no* stdlib classes excluding those defined by the :mod:`typing` module itself subclass :class:`typing.IO`. However, :class:`typing.IO` leverages neither the :class:`abc.ABCMeta` metaclass *nor* the :class:`typing.Protocol` superclass needed to support structural subtyping. Therefore, *no* stdlib objects (including those returned by the :func:`open` builtin) satisfy either :class:`typing.IO` itself or any subclasses of :class:`typing.IO` (e.g., :class:`typing.BinaryIO`, :class:`typing.TextIO`). Therefore, :class:`typing.IO` and all subclasses thereof are functionally useless for all practical intents. The conventional excuse `given by Python maintainers to justify this abhorrent nonsensicality is as follows `__: There are a lot of "file-like" classes, and the typing IO classes are meant as "protocols" for general files, but they cannot actually be protocols because the file protocol isn't very well defined—there are lots of methods that exist on some but not all filelike classes. Like most :mod:`typing`-oriented confabulation, that, of course, is bollocks. Refactoring the family of :mod:`typing` IO classes from inveterate generics into pragmatic protocols is both technically trivial and semantically useful, because that is exactly what :mod:`beartype` does. It works. It necessitates modifying three lines of existing code. It preserves backward compatibility. In short, it should have been done a decade ago. If the file protocol "isn't very well defined," the solution is to define that protocol with a rigorous type hierarchy satisfying all possible edge cases. The solution is *not* to pretend that no solutions exist, that the existing non-solution suffices, and instead do nothing. Welcome to :mod:`typing`, where no one cares that nothing works as advertised (or at all)... *and no one ever will.* .. _typeshed: https://github.com/python/typeshed/issues/3225#issuecomment-529277448 ''' # Conditionally initialized by the _init() function below. _Pep544BinaryIO: Any = None # type: ignore[assignment] ''' Typed version of the return of :func:`open` in binary mode. ''' # Conditionally initialized by the _init() function below. _Pep544TextIO: Any = None # type: ignore[assignment] ''' Typed version of the return of :func:`open` in text mode. ''' # ....................{ INITIALIZERS }.................... def _init() -> None: ''' Initialize this submodule. ''' # ..................{ IMPORTS }.................. # Defer Python version-specific imports. from beartype._util.api.utilapityping import import_typing_attr_or_none from beartype.typing import ( AnyStr, List, Protocol, ) # ..................{ GLOBALS }.................. # Global attributes to be redefined below. global \ _Pep544BinaryIO, \ _Pep544IO, \ _Pep544TextIO # ..................{ PROTOCOLS ~ protocol }.................. # Note that these classes are intentionally *NOT* declared at global scope; # instead, these classes are declared *ONLY* if the active Python # interpreter targets Python >= 3.8. # PEP-compliant type hint matching file handles opened in either text or # binary mode. # @runtime_checkable class _Pep544IO(Protocol[AnyStr]): # The body of this class is copied wholesale from the existing # non-functional "typing.IO" class. __slots__: tuple = () @property @abstractmethod def mode(self) -> str: pass @property @abstractmethod def name(self) -> str: pass @abstractmethod def close(self) -> None: pass @property @abstractmethod def closed(self) -> bool: pass @abstractmethod def fileno(self) -> int: pass @abstractmethod def flush(self) -> None: pass @abstractmethod def isatty(self) -> bool: pass @abstractmethod def read(self, n: int = -1) -> AnyStr: pass @abstractmethod def readable(self) -> bool: pass @abstractmethod def readline(self, limit: int = -1) -> AnyStr: pass @abstractmethod def readlines(self, hint: int = -1) -> List[AnyStr]: pass @abstractmethod def seek(self, offset: int, whence: int = 0) -> int: pass @abstractmethod def seekable(self) -> bool: pass @abstractmethod def tell(self) -> int: pass @abstractmethod def truncate(self, size: Optional[int] = None) -> int: pass @abstractmethod def writable(self) -> bool: pass @abstractmethod def write(self, s: AnyStr) -> int: pass @abstractmethod def writelines(self, lines: List[AnyStr]) -> None: pass @abstractmethod def __enter__(self) -> '_Pep544IO[AnyStr]': # pyright: ignore pass @abstractmethod def __exit__(self, cls, value, traceback) -> None: pass # PEP-compliant type hint matching file handles opened in text rather than # binary mode. # # Note that PEP 544 explicitly requires *ALL* protocols (including # protocols subclassing protocols) to explicitly subclass the "Protocol" # superclass, in violation of both sanity and usability. (Thanks, guys.) # @runtime_checkable class _Pep544TextIO(_Pep544IO[str], Protocol): # The body of this class is copied wholesale from the existing # non-functional "typing.TextIO" class. __slots__: tuple = () @property @abstractmethod def buffer(self) -> _Pep544BinaryIO: # pyright: ignore pass @property @abstractmethod def encoding(self) -> str: pass @property @abstractmethod def errors(self) -> Optional[str]: pass @property @abstractmethod def line_buffering(self) -> bool: pass @property @abstractmethod def newlines(self) -> Any: pass @abstractmethod def __enter__(self) -> '_Pep544TextIO': # pyright: ignore pass # ..................{ PROTOCOLS ~ validator }.................. # PEP-compliant type hint matching file handles opened in binary rather # than text mode. # # If PEP 593 (e.g., "typing.Annotated") and thus beartype validators are # unusable, this hint falls back to ambiguously matching the abstract # "typing.IO" protocol ABC. This will yield false positives (i.e., fail to # raise exceptions) for @beartype-decorated callables annotated as # accepting binary file handles erroneously passed text file handles, which # is non-ideal but certainly preferable to raising exceptions at decoration # time on each such callable. # # If PEP 593 (e.g., "typing.Annotated") and thus beartype validators are # usable, this hint matches the abstract "typing.IO" protocol ABC but *NOT* # the concrete "typing.TextIO" subprotocol subclassing that ABC. Whereas # the concrete "typing.TextIO" subprotocol unambiguously matches *ONLY* # file handles opened in text mode, the concrete "typing.BinaryIO" # subprotocol ambiguously matches file handles opened in both text *AND* # binary mode. As the following hypothetical "_Pep544BinaryIO" subclass # demonstrates, the "typing.IO" and "typing.BinaryIO" APIs are identical # except for method annotations: # class _Pep544BinaryIO(_Pep544IO[bytes], Protocol): # # The body of this class is copied wholesale from the existing # # non-functional "typing.BinaryIO" class. # # __slots__: tuple = () # # @abstractmethod # def write(self, s: Union[bytes, bytearray]) -> int: # pass # # @abstractmethod # def __enter__(self) -> '_Pep544BinaryIO': # pass # # Sadly, the method annotations that differ between these APIs are # insufficient to disambiguate file handles at runtime. Why? Because most # file handles are C-based and thus lack *ANY* annotations whatsoever. With # respect to C-based file handles, these APIs are therefore identical. # Ergo, the "typing.BinaryIO" subprotocol is mostly useless at runtime. # # Note, however, that file handles are necessarily *ALWAYS* opened in # either text or binary mode. This strict dichotomy implies that any file # handle (i.e., object matching the "typing.IO" protocol) *NOT* opened in # text mode (i.e., not matching the "typing.TextIO" protocol) must # necessarily be opened in binary mode instead. _Pep544BinaryIO = _Pep544IO #FIXME: Safely replace this with "from typing import Annotated" after #dropping Python 3.8 support. # "typing.Annotated" type hint factory safely imported from whichever of # the "typing" or "typing_extensions" modules declares this attribute if # one or more do *OR* "None" otherwise (i.e., if none do). typing_annotated = import_typing_attr_or_none('Annotated') # If this factory is importable. if typing_annotated is not None: # Defer heavyweight imports. from beartype.vale import IsInstance # Expand this hint to unambiguously match binary file handles by # subscripting this factory with a beartype validator doing so. _Pep544BinaryIO = typing_annotated[ _Pep544IO, ~IsInstance[_Pep544TextIO]] # Else, this factory is unimportable. In this case, accept this hint's # default ambiguously matching both binary and text files. # ..................{ MAPPINGS }.................. # Dictionary mapping from each "typing" IO generic base class to the # associated IO protocol defined above. # # Note this global is intentionally modified in-place rather than # reassigned to a new dictionary. Why? Because the higher-level # reduce_hint_pep484_generic_io_to_pep544_protocol() function calling this # lower-level initializer has already imported this global. _HINT_PEP484_IO_GENERIC_TO_PEP544_PROTOCOL.update({ # Unsubscripted mappings. IO: _Pep544IO, BinaryIO: _Pep544BinaryIO, TextIO: _Pep544TextIO, # Subscripted mappings, leveraging the useful observation that these # classes all self-cache by design: e.g., # >>> import typing # >>> typing.IO[str] is typing.IO[str] # True # # Note that we intentionally map: # * "IO[Any]" to the unsubscripted "_Pep544IO" rather than the # subscripted "_Pep544IO[Any]". Although the two are semantically # equivalent, the latter is marginally more space- and time-efficient # to generate code for and thus preferable. # * "IO[bytes]" to the unsubscripted "_Pep544Binary" rather than the # subscripted "_Pep544IO[bytes]". Why? Because the former applies # meaningful runtime constraints, whereas the latter does *NOT*. # * "IO[str]" to the unsubscripted "_Pep544Text" rather than the # subscripted "_Pep544IO[str]" -- for the same reason. # # Note that we intentionally avoid mapping parametrizations of "IO" by # type variables. Since there exist a countably infinite number of # such parametrizations, the parent # reduce_hint_pep484_generic_io_to_pep544_protocol() function calling # this function handles such parametrizations mostly intelligently. IO[Any]: _Pep544IO, IO[bytes]: _Pep544BinaryIO, IO[str]: _Pep544TextIO, }) beartype-0.18.5/beartype/_util/hint/pep/proposal/utilpep557.py000066400000000000000000000104571461113517100242400ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`557`-compliant type hint utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeDecorHintPep557Exception from beartype._data.hint.datahinttyping import TypeException from beartype._data.hint.pep.sign.datapepsigns import HintSignPep557DataclassInitVar # ....................{ GETTERS }.................... def get_hint_pep557_initvar_arg( # Mandatory parameters. hint: object, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintPep557Exception, exception_prefix: str = '', ) -> object: ''' PEP-compliant child type hint subscripting the passed :pep:`557`-compliant **dataclass initialization-only instance variable type hint** (i.e., subscription of the :class:`dataclasses.InitVar` type hint factory). This getter is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object Type hint to be inspected. exception_cls : TypeException, optional Type of exception to be raised in the event of a fatal error. Defaults to :exc:`.BeartypeDecorHintPep557Exception`. exception_prefix : str, optional Human-readable substring prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ---------- object PEP-compliant child type hint subscripting this parent type hint. Raises ---------- BeartypeDecorHintPep557Exception If this object is *not* a dataclass initialization-only instance variable type hint. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.utilpepget import get_hint_pep_sign_or_none # Sign uniquely identifying this hint if this hint is identifiable *OR* # "None" otherwise. hint_sign = get_hint_pep_sign_or_none(hint) # If this hint is *NOT* a dataclass initialization-only instance variable # type hint, raise an exception. if hint_sign is not HintSignPep557DataclassInitVar: assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') raise exception_cls( f'{exception_prefix}type hint {repr(hint)} not ' f'PEP 557-compliant "dataclasses.TypeVar" instance.' ) # Else, this hint is such a hint. # Return the child type hint subscripting this parent type hint. Yes, this # hint exposes this child via a non-standard instance variable rather than # the "__args__" dunder tuple standardized by PEP 484. return hint.type # type: ignore[attr-defined] # ....................{ REDUCERS }.................... def reduce_hint_pep557_initvar( hint: object, exception_prefix: str, *args, **kwargs) -> object: ''' Reduce the passed :pep:`557`-compliant **dataclass initialization-only instance variable type hint** (i.e., subscription of the :class:`dataclasses.InitVar` type hint factory) to the child type hint subscripting this parent hint -- which is otherwise functionally useless from the admittedly narrow perspective of runtime type-checking. This reducer is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object Type variable to be reduced. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. All remaining passed arguments are silently ignored. Returns ---------- object Lower-level type hint currently supported by :mod:`beartype`. ''' # Reduce this "typing.InitVar[{hint}]" type hint to merely "{hint}". return get_hint_pep557_initvar_arg( hint=hint, exception_prefix=exception_prefix) beartype-0.18.5/beartype/_util/hint/pep/proposal/utilpep585.py000066400000000000000000000371421461113517100242410ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`585`-compliant type hint utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeDecorHintPep585Exception from beartype.typing import ( Any, Set, ) from beartype._cave._cavefast import HintGenericSubscriptedType from beartype._data.hint.datahinttyping import TypeException from beartype._util.cache.utilcachecall import callable_cached from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_9 from beartype._util.utilobject import Iota from beartype._data.hint.datahinttyping import TupleTypes # ....................{ HINTS }.................... HINT_PEP585_TUPLE_EMPTY = ( tuple[()] if IS_PYTHON_AT_LEAST_3_9 else Iota()) # type: ignore[misc] ''' :pep:`585`-compliant empty fixed-length tuple type hint if the active Python interpreter supports at least Python 3.9 and thus :pep:`585` *or* a unique placeholder object otherwise to guarantee failure when comparing arbitrary objects against this object via equality tests. ''' # ....................{ RAISERS }.................... def die_unless_hint_pep585_generic( # Mandatory parameters. hint: object, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintPep585Exception, exception_prefix: str = '', ) -> None: ''' Raise an exception unless the passed object is a :pep:`585`-compliant **generic** (i.e., class superficially subclassing at least one subscripted :pep:`585`-compliant pseudo-superclass). Parameters ---------- hint : object Object to be validated. exception_cls : TypeException Type of exception to be raised. Defaults to :exc:`BeartypeDecorHintPep585Exception`. exception_prefix : str, optional Human-readable substring prefixing the representation of this object in the exception message. Defaults to the empty string. Raises ------ exception_cls If this object is *not* a :pep:`585`-compliant generic. ''' # If this object is *NOT* a PEP 585-compliant generic, raise an exception. if not is_hint_pep585_generic(hint): raise exception_cls( f'{exception_prefix}type hint {repr(hint)} not PEP 585 generic.') # Else, this object is a PEP 585-compliant generic. # ....................{ TESTERS }.................... # If the active Python interpreter targets at least Python >= 3.9 and thus # supports PEP 585, correctly declare this function. if IS_PYTHON_AT_LEAST_3_9: def is_hint_pep585_builtin_subscripted(hint: object) -> bool: # Avoid circular import dependencies. from beartype._util.hint.pep.proposal.pep484585.utilpep484585generic import ( is_hint_pep484585_generic) # Return true only if this hint... return ( # Is either a PEP 484- or -585-compliant subscripted generic or # PEP 585-compliant builtin *AND*... isinstance(hint, HintGenericSubscriptedType) and # Is *NOT* a PEP 484- or -585-compliant subscripted generic. not is_hint_pep484585_generic(hint) ) @callable_cached def is_hint_pep585_generic(hint: object) -> bool: # pyright: ignore[reportGeneralTypeIssues] # Avoid circular import dependencies. from beartype._util.hint.pep.proposal.pep484585.utilpep484585generic import ( get_hint_pep484585_generic_type_or_none) # If this hint is *NOT* a type, reduce this hint to the object # originating this hint if any. See the comparable # is_hint_pep484_generic() tester for further details. hint = get_hint_pep484585_generic_type_or_none(hint) # Tuple of all pseudo-superclasses originally subclassed by the passed # hint if this hint is a generic *OR* false otherwise. hint_bases_erased = getattr(hint, '__orig_bases__', False) # If this hint subclasses *NO* pseudo-superclasses, this hint *CANNOT* # be a generic. In this case, immediately return false. if not hint_bases_erased: return False # Else, this hint subclasses one or more pseudo-superclasses. # For each such pseudo-superclass... # # Unsurprisingly, PEP 585-compliant generics have absolutely *NO* # commonality with PEP 484-compliant generics. While the latter are # trivially detectable as subclassing "typing.Generic" after type # erasure, the former are *NOT*. The only means of deterministically # deciding whether or not a hint is a PEP 585-compliant generic is if: # * That class defines both the __class_getitem__() dunder method *AND* # the "__orig_bases__" instance variable. Note that this condition in # and of itself is insufficient to decide PEP 585-compliance as a # generic. Why? Because these dunder attributes have been standardized # under various PEPs and may thus be implemented by *ANY* arbitrary # classes. # * The "__orig_bases__" instance variable is a non-empty tuple. # * One or more objects listed in that tuple are PEP 585-compliant # C-based subscripted generics (e.g., "list[str]"). # # Note we could technically also test that this hint defines the # __class_getitem__() dunder method. Since this condition suffices to # ensure that this hint is a PEP 585-compliant generic, however, there # exists little benefit to doing so. for hint_base_erased in hint_bases_erased: # type: ignore[union-attr] # If this pseudo-superclass is itself a PEP 585-compliant C-based # subscripted generic (e.g., "list[str]"), return true. if is_hint_pep585_builtin_subscripted(hint_base_erased): return True # Else, this pseudo-superclass is *NOT* PEP 585-compliant. In this # case, continue to the next pseudo-superclass. # Since *NO* such pseudo-superclasses are PEP 585-compliant, this hint # is *NOT* a PEP 585-compliant generic. In this case, return false. return False # Else, the active Python interpreter targets at most Python < 3.9 and thus # fails to support PEP 585. In this case, fallback to declaring this function # to unconditionally return False. else: def is_hint_pep585_builtin_subscripted(hint: object) -> bool: return False def is_hint_pep585_generic(hint: object) -> bool: return False # ....................{ TESTERS ~ doc }.................... # Docstring for this function regardless of implementation details. is_hint_pep585_builtin_subscripted.__doc__ = ''' :data:`True` only if the passed object is a :pep:`585`-compliant **subscripted builtin type hint** (i.e., C-based type hint instantiated by subscripting either a concrete builtin container class like :class:`list` or :class:`tuple` *or* an abstract base class (ABC) declared by the :mod:`collections.abc` submodule like :class:`collections.abc.Iterable` or :class:`collections.abc.Sequence`). This tester additionally returns :data:`True` for third-party type hints whose types subclass the :class:`types.GenericAlias` superclass, including: * ``numpy.typing.NDArray[...]`` type hints. This tester is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Caveats ------- **This tester returns** :data:`False` for :pep:`585`-compliant generics, which fail to satisfy the same API as all other :pep:`585`-compliant type hints. Why? Because :pep:`560`-type erasure erases the low-level superclass detected by this tester on :pep:`585`-compliant generics immediately after those generics are declared, preventing their subsequent detection as :pep:`585`-compliant. Instead, :pep:`585`-compliant generics are only detectable by calling either: * The high-level PEP-agnostic :func:`beartype._util.hint.pep.utilpeptest.is_hint_pep484585_generic` tester. * The low-level :pep:`585`-specific :func:`.is_hint_pep585_generic` tester. Parameters ---------- hint : object Object to be inspected. Returns ------- bool :data:`True` only if this object is a :pep:`585`-compliant type hint. ''' is_hint_pep585_generic.__doc__ = ''' :data:`True` only if the passed object is a :pep:`585`-compliant **generic** (i.e., object that may *not* actually be a class originally subclassing at least one subscripted :pep:`585`-compliant pseudo-superclass). This tester is memoized for efficiency. Parameters ---------- hint : object Object to be inspected. Returns ------- bool :data:`True` only if this object is a :pep:`585`-compliant generic. ''' # ....................{ GETTERS }.................... def get_hint_pep585_generic_bases_unerased( # Mandatory parameters. hint: Any, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintPep585Exception, exception_prefix: str = '', ) -> tuple: ''' Tuple of all unerased :pep:`585`-compliant **pseudo-superclasses** (i.e., :mod:`typing` objects originally listed as superclasses prior to their implicit type erasure under :pep:`560`) of the passed :pep:`585`-compliant **generic** (i.e., class subclassing at least one non-class :pep:`585`-compliant object). This getter is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object Object to be inspected. exception_cls : TypeException Type of exception to be raised. Defaults to :exc:`BeartypeDecorHintPep585Exception`. exception_prefix : str, optional Human-readable substring prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- Tuple[object] Tuple of the one or more unerased pseudo-superclasses of this :pep:`585`-compliant generic. Raises ------ exception_cls If this hint is *not* a :pep:`585`-compliant generic. See Also -------- :func:`beartype._util.hint.pep.proposal.pep484585.utilpep484585generic.get_hint_pep484585_generic_bases_unerased` Further details. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.proposal.pep484585.utilpep484585generic import ( get_hint_pep484585_generic_type_or_none) # If this hint is *NOT* a class, reduce this hint to the object originating # this hint if any. See the is_hint_pep484_generic() tester for details. hint = get_hint_pep484585_generic_type_or_none(hint) # If this hint is *NOT* a PEP 585-compliant generic, raise an exception. die_unless_hint_pep585_generic( hint=hint, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # Return the tuple of all unerased pseudo-superclasses of this generic. # While the "__orig_bases__" dunder instance variable is *NOT* guaranteed # to exist for PEP 484-compliant generic types, this variable is guaranteed # to exist for PEP 585-compliant generic types. Thanks for small favours. return hint.__orig_bases__ @callable_cached def get_hint_pep585_generic_typevars(hint: object) -> TupleTypes: ''' Tuple of all **unique type variables** (i.e., subscripted :class:`TypeVar` instances of the passed :pep:`585`-compliant generic listed by the caller at hint declaration time ignoring duplicates) if any *or* the empty tuple otherwise. This getter is memoized for efficiency. Motivation ---------- The current implementation of :pep:`585` under at least Python 3.9 is fundamentally broken with respect to parametrized generics. While `PEP 484`_-compliant generics properly propagate type variables from pseudo-superclasses to subclasses, :pep:`585` fails to do so. This function "fills in the gaps" by recovering these type variables from parametrized :pep:`585`-compliant generics by iteratively constructing a new tuple from the type variables parametrizing all pseudo-superclasses of this generic. Parameters ---------- hint : object Object to be inspected. Returns ------- Tuple[TypeVar, ...] Either: * If this :pep:`585`-compliant generic defines a ``__parameters__`` dunder attribute, the value of that attribute. * Else, the empty tuple. Raises ------ BeartypeDecorHintPep585Exception If this hint is *not* a :pep:`585`-compliant generic. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.utilpepget import get_hint_pep_typevars # Tuple of all pseudo-superclasses of this PEP 585-compliant generic. hint_bases = get_hint_pep585_generic_bases_unerased(hint) # Set of all type variables parametrizing these pseudo-superclasses. # # Note the following inefficient iteration *CANNOT* be reduced to an # efficient set comprehension, as each get_hint_pep_typevars() call returns # a tuple of type variables rather than single type variable to be added to # this set. hint_typevars: Set[type] = set() # For each such pseudo-superclass, add all type variables parametrizing # this pseudo-superclass to this set. for hint_base in hint_bases: # print(f'hint_base_typevars: {hint_base} [{get_hint_pep_typevars(hint_base)}]') hint_typevars.update(get_hint_pep_typevars(hint_base)) # Return this set coerced into a tuple. return tuple(hint_typevars) # ....................{ REDUCERS }.................... #FIXME: Unit test us up, please. def reduce_hint_pep585_builtin_subscripted_unknown( hint: object, *args, **kwargs) -> type: ''' Reduce the passed :pep:`585`-compliant **unrecognized subscripted builtin type hints** (i.e., C-based type hints that are *not* isinstanceable types, instantiated by subscripting pure-Python origin classes subclassing the C-based :class:`types.GenericAlias` superclass such that those classes are unrecognized by :mod:`beartype` and thus *not* type-checkable as is) to their unsubscripted origin classes (which are almost always pure-Python isinstanceable types and thus type-checkable as is). This reducer is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object Type hint to be reduced. All remaining passed arguments are silently ignored. Returns ------- type Unsubscripted origin class originating this unrecognized subscripted builtin type hint. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.utilpepget import get_hint_pep_origin_type # Pure-Python origin class originating this unrecognized subscripted builtin # type hint if this hint originates from such a class *OR* raise an # exception otherwise (i.e., if this hint originates from *NO* such class). origin_type = get_hint_pep_origin_type(hint) # Return this origin. return origin_type beartype-0.18.5/beartype/_util/hint/pep/proposal/utilpep586.py000066400000000000000000000203761461113517100242430ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`586`-compliant type hint utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeDecorHintPep586Exception from beartype.typing import Any from beartype._data.cls.datacls import TYPES_PEP586_ARG from beartype._data.hint.datahinttyping import TypeException from beartype._data.hint.pep.sign.datapepsigns import HintSignLiteral from beartype._util.text.utiltextjoin import join_delimited_disjunction_types # ....................{ VALIDATORS }.................... def die_unless_hint_pep586( # Mandatory parameters. hint: Any, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintPep586Exception, exception_prefix: str = '', ) -> None: ''' Raise an exception of the passed type unless the passed object is a :pep:`586`-compliant type hint (i.e., subscription of either the :attr:`typing.Literal` or :attr:`typing_extensions.Literal` type hint factories). Ideally, the :attr:`typing.Literal` singleton would internally validate the literal objects subscripting that singleton at subscription time (i.e., in the body of the ``__class_getitem__()`` dunder method). Whereas *all* other :mod:`typing` attributes do just that, :attr:`typing.Literal` permissively accepts all possible arguments like a post-modern philosopher hopped up on too much tenure. For inexplicable reasons, :pep:`586` explicitly requires third-party type checkers (that's us) to validate these hints rather than standardizing that validation in the :mod:`typing` module. Weep, Guido! Caveats ------- **This function is slow** and should thus be called only once per visitation of a :pep:`586`-compliant type hint. Specifically, this function is O(n) for n the number of arguments subscripting this hint. Parameters ---------- hint : object Object to be inspected. exception_cls : TypeException Type of exception to be raised. Defaults to :exc:`BeartypeDecorHintPep586Exception`. exception_prefix : str, optional Human-readable substring prefixing the representation of this object in the exception message. Defaults to the empty string. Raises ------ exception_cls If this object either: * Is *not* a subscription of either the :attr:`typing.Literal` or :attr:`typing_extensions.Literal` type hint factories. * Subscripts either factory with zero arguments via the empty tuple, which these factories sadly fails to guard against. * Subscripts either factory with one or more arguments that are *not* **valid literals**, defined as the set of all: * Booleans. * Byte strings. * Integers. * Unicode strings. * :class:`enum.Enum` members. * The :data:`None` singleton. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.utilpepget import ( get_hint_pep_args, get_hint_pep_sign, ) # If this hint is *NOT* PEP 586-compliant, raise an exception. if get_hint_pep_sign(hint) is not HintSignLiteral: raise exception_cls( f'{exception_prefix}type hint {repr(hint)} not PEP 586-compliant ' f'(e.g., "typing.Literal[...]", "typing_extensions.Literal[...]").' ) # Else, this hint is PEP 586-compliant. # Tuple of zero or more literal objects subscripting this hint. hint_literals = get_hint_pep_args(hint) # If this hint is unsubscripted... if not hint_literals: # Exception message to be raised. exception_message = f'{exception_prefix}PEP 586 type hint {repr(hint)} ' # If this hint defines the standard "__args__" dunder attribute, this # hint *MUST* have been subscripted by the the empty tuple. Ideally, the # "typing.Literal" factory would guard against this itself. It does not; # thus, we do. Construct an appropriate message. if hasattr(hint_literals, '__args__'): exception_message += ( 'subscripted by empty tuple, ' 'which is not a valid literal object.' ) # Else, this hint fails to define the standard "__args__" dunder # attribute. In this case, this hint *MUST* be the unsubscripted # "typing.Literal" factory -- which conveys *NO* meaningful semantics # and is thus invalid as a type hint. Construct an appropriate message. else: exception_message += ( 'unsubscripted (i.e., subscripted by no literal objects).') # Raise this exception. raise exception_cls(exception_message) # If this hint is subscripted by one or more literal objects. # For each argument subscripting this hint... # # Sadly, despite PEP 586 imposing strict restrictions on the types of # objects permissible as arguments subscripting the "typing.Literal" # singleton, PEP 586 explicitly offloads the odious chore of enforcing those # restrictions onto third-party type checkers by intentionally implementing # that singleton to permissively accept *ALL* possible objects when # subscripted: # Although the set of parameters Literal[...] may contain at type check # time is very small, the actual implementation of typing.Literal will # not perform any checks at runtime. for hint_literal_index, hint_literal in enumerate(hint_literals): # If this argument is invalid as a literal argument... if not isinstance(hint_literal, TYPES_PEP586_ARG): # Human-readable concatenation of the types of all valid literal # arguments, delimited by commas and/or "or". hint_literal_types = join_delimited_disjunction_types( TYPES_PEP586_ARG) # Raise an exception. raise exception_cls( f'{exception_prefix}PEP 586 type hint {repr(hint)} ' f'argument {hint_literal_index} ' f'{repr(hint_literal)} not {hint_literal_types}.' ) # Else, this argument is valid as a literal argument. # ....................{ GETTERS }.................... #FIXME: Unit test us up, please. def get_hint_pep586_literals( # Mandatory parameters. hint: Any, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintPep586Exception, exception_prefix: str = '', ) -> tuple: ''' Tuple of zero or more literal objects subscripting the passed :pep:`586`-compliant type hint (i.e., subscription of either the :attr:`typing.Literal` or :attr:`typing_extensions.Literal` type hint factories). This getter is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Caveats ------- **This low-level getter performs no validation of the contents of this tuple.** Consider calling the high-level :func:`die_unless_hint_pep586` validator to do so before leveraging this tuple elsewhere. Parameters ---------- hint : object :pep:`586`-compliant type hint to be inspected. exception_cls : TypeException Type of exception to be raised. Defaults to :exc:`BeartypeDecorHintPep586Exception`. exception_prefix : str, optional Human-readable substring prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- tuple Tuple of zero or more literal objects subscripting this hint. Raises ------ exception_cls If this object is *not* a :pep:`586`-compliant type hint. ''' # If this hint is *NOT* PEP 586-compliant, raise an exception. die_unless_hint_pep586(hint=hint, exception_prefix=exception_prefix) # Else, this hint is PEP 586-compliant. # Return the standard tuple of all literals subscripting this hint. return hint.__args__ beartype-0.18.5/beartype/_util/hint/pep/proposal/utilpep589.py000066400000000000000000000207101461113517100242360ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`589`-compliant **typed dictionary** (i.e., :class:`typing.TypedDict` subclass) utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._data.hint.datahinttyping import MappingStrToAny from beartype._util.cls.utilclstest import is_type_subclass from beartype._util.py.utilpyversion import IS_PYTHON_3_8 # ....................{ TESTERS }.................... # The implementation of the "typing.TypedDict" attribute substantially varies # across Python interpreter *AND* "typing" implementation. Specifically: # * The "typing.TypedDict" attribute under Python >= 3.9 is *NOT* actually a # superclass but instead a factory function masquerading as a superclass by # setting the subversive "__mro_entries__" dunder attribute to a tuple # containing a private "typing._TypedDict" superclass. This superclass # necessarily defines the three requisite dunder attributes. # * The "typing_extensions.TypedDict" attribute under Python < 3.8 is actually a # superclass also necessarily defining the three requisite dunder attributes. # * The "typing.TypedDict" attribute under *ONLY* Python 3.8 is also actually a # superclass that *ONLY* defines the requisite "__annotations__" dunder # attribute. The two remaining dunder attributes are only conditionally # defined and thus *CANNOT* be unconditionally assumed to exist. # # In all three cases, passing the passed hint and that superclass to the # issubclass() builtin fails, as the metaclass of that superclass prohibits # issubclass() checks. I am throwing up in my mouth as I write this. # # Unfortunately, all of the above complications are further complicated by the # "dict" type under Python >= 3.10. For unknown reasons, Python >= 3.10 adds # spurious "__annotations__" dunder attributes to "dict" subclasses -- even if # those subclasses annotate *NO* class or instance variables. While a likely # bug, we have little choice but to at least temporarily support this insanity. def is_hint_pep589(hint: object) -> bool: ''' :data:`True` only if the passed object is a :pep:`589`-compliant **typed dictionary** (i.e., :class:`typing.TypedDict` subclass). This getter is intentionally *not* memoized (e.g., by the ``callable_cached`` decorator). Although the implementation inefficiently performs three calls to the :func:`hasattr` builtin (which inefficiently calls the :func:`getattr` builtin and catches the :exc:`AttributeError` exception to detect false cases), callers are expected to instead (in order): #. Call the memoized :func:`beartype._util.hint.pep.utilpepget.get_hint_pep_sign_or_none` getter, which internally calls this unmemoized tester. #. Compare the object returned by that getter against the :attr:`beartype._util.data.hint.pep.sign.datapepsigns.HintSignTypedDict` sign. Parameters ---------- hint : object Object to be tested. Returns ------- bool :data:`True` only if this object is a typed dictionary. ''' # If this hint is *NOT* a "dict" subclass, this hint *CANNOT* be a typed # dictionary. By definition, typed dictionaries are "dict" subclasses. # # Note that PEP 589 actually lies about the type of typed dictionaries: # Methods are not allowed, since the runtime type of a TypedDict object # will always be just dict (it is never a subclass of dict). # # This is *ABSOLUTELY* untrue. PEP 589 authors plainly forgot to implement # this constraint. Contrary to the above: # * All typed dictionaries are subclasses of "dict". # * The type of typed dictionaries is the private "typing._TypedDictMeta" # metaclass across all Python versions (as of this comment). # # This is where we generously and repeatedly facepalm ourselves. if not is_type_subclass(hint, dict): return False # Else, this hint is a "dict" subclass and thus *MIGHT* be a typed # dictionary. # Return true *ONLY* if this "dict" subclass defines all three dunder # attributes guaranteed to be defined by all typed dictionaries. Although # slow, this is still faster than the MRO-based approach delineated above. # # Note that *ONLY* the Python 3.8-specific implementation of # "typing.TypedDict" fails to unconditionally define the # "__required_keys__" and "__optional_keys__" dunder attributes. Ergo, if # the active Python interpreter targets exactly Python 3.8, we relax this # test to *ONLY* test for the "__annotations__" dunder attribute. # Specifically, we return true only if... # # Technically, this test can also be performed by inefficiently violating # privacy encapsulation. Specifically, this test could perform an O(k) walk # up the class inheritance tree of the passed class (for k the number of # superclasses of that class), iteratively comparing each such superclass # for against the "typing.TypeDict" superclass. That is, this tester could # crazily reimplement the issubclass() builtin in pure-Python. Since the # implementation of typed dictionaries varies substantially across Python # versions, doing so would require version-specific tests in addition to # unsafely violating privacy encapsulation and inefficiently violating # constant-time guarantees. # # Technically, the current implementation of this test is susceptible to # false positives in unlikely edge cases. Specifically, this test queries # for dunder attributes and thus erroneously returns true for user-defined # "dict" subclasses *NOT* subclassing the "typing.TypedDict" superclass but # nonetheless declaring the same dunder attributes declared by that # superclass. Since the likelihood of any user-defined "dict" subclass # accidentally defining these attributes is vanishingly small *AND* since # "typing.TypedDict" usage is largely discouraged in the typing community, # this error is unlikely to meaningfully arise in real-world use cases. # Ergo, it is preferable to implement this test portably, safely, and # efficiently rather than accommodate this error. # # In short, the current approach of is strongly preferable. return ( # This "dict" subclass defines these "TypedDict" attributes *AND*... hasattr(hint, '__annotations__') and hasattr(hint, '__total__') and # Either... ( # The active Python interpreter targets exactly Python 3.8 and # thus fails to unconditionally define the remaining attributes # *OR*... IS_PYTHON_3_8 or # The active Python interpreter targets any other Python version # and thus unconditionally defines the remaining attributes. ( hasattr(hint, '__required_keys__') and hasattr(hint, '__optional_keys__') ) ) ) # ....................{ REDUCERS }.................... #FIXME: Remove *AFTER* deeply type-checking typed dictionaries. For now, #shallowly type-checking such hints by reduction to untyped dictionaries #remains the sanest temporary work-around. def reduce_hint_pep589( hint: object, exception_prefix: str, *args, **kwargs) -> object: ''' Reduce the passed :pep:`589`-compliant **typed dictionary** (i.e., :class:`typing.TypedDict` subclass) to a lower-level type hint currently supported by :mod:`beartype`. This reducer is intentionally *not* memoized (e.g., by the ``callable_cached`` decorator), as reducers cannot be memoized. Parameters ---------- hint : object Typed dictionary to be reduced. exception_prefix : str, optional Substring prefixing exception messages raised by this reducer. All remaining passed arguments are silently ignored. Returns ------- object Lower-level type hint currently supported by :mod:`beartype`. ''' # Silently ignore all child type hints annotating this dictionary by # reducing this hint to a "Mapping" type hint. Yes, "Mapping" rather than # "dict". By PEP 589 edict: # First, any TypedDict type is consistent with Mapping[str, object]. return MappingStrToAny beartype-0.18.5/beartype/_util/hint/pep/proposal/utilpep591.py000066400000000000000000000100241461113517100242240ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`591`-compliant **type hint** (i.e., objects created by subscripting the :obj:`typing.Final` type hint factory) utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeDecorHintPep591Exception # from beartype._util.py.utilpyversion import IS_PYTHON_3_8 # ....................{ REDUCERS }.................... #FIXME: Remove *AFTER* deeply type-checking "Final[...]" type hints. For now, #shallowly type-checking such hints by reduction to their subscripted arguments #remains the sanest temporary work-around. def reduce_hint_pep591( hint: object, exception_prefix: str, *args, **kwargs) -> object: ''' Reduce the passed :pep:`591`-compliant **final type hint** (i.e., subscription of the :obj:`typing.Final` type hint factory) to a lower-level type hint currently supported by :mod:`beartype`. This reducer is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object Final type hint to be reduced. exception_prefix : str, optional Human-readable substring prefixing exception messages raised by this function. All remaining passed arguments are silently ignored. Returns ---------- object Lower-level type hint currently supported by :mod:`beartype`. Raises ---------- BeartypeDecorHintPep591Exception If this hint is subscripted by two or more child type hints. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.utilpepget import get_hint_pep_args # Tuple of zero or more child type hints subscripting this type hint. hint_args = get_hint_pep_args(hint) # Number of child type hints subscripting this type hint. hint_args_len = len(hint_args) # If this hint is unsubscripted, reduce this hint to the ignorable type hint # "object". # # Note that PEP 591 bizarrely permits the "typing.Final" type hint factory # to remain unsubscripted: # * With no type annotation. Example: # ID: Final = 1 # The typechecker should apply its usual type inference mechanisms to # determine the type of ID (here, likely, int). Note that unlike for # generic classes this is not the same as Final[Any]. # # Since runtime type-checkers *NEVER* infer types, this permissiveness # substantially reduces the usability of this edge case at runtime. # Nevertheless, this is a valid edge case. Technically, we could emit a # non-fatal warning to recommend the user explicitly type each unsubscripted # "typing.Final" type hint. Pragmatically, doing so would only harass large # codebases attempting to migrate to @beartype. Doing nothing is preferable. if hint_args_len == 0: hint = object # If, this hint is subscripted by exactly one child type hint, reduce this # hint to that child hint. elif hint_args_len == 1: hint = hint_args[0] # Else, this hint is subscripted by two or more child type hints. In this # case, raise an exception. # # Note that "typing.Final" already prohibits subscription by two or more # arguments. Ergo, this should *NEVER* happen: e.g., # >>> import typing # >>> typing.Final[int, float] # TypeError: typing.Final accepts only single type. Got (, # ). else: raise BeartypeDecorHintPep591Exception( f'{exception_prefix}PEP 591 type hint {repr(hint)} ' f'erroneously subscripted by {hint_args_len} child type hints.' ) # Return this reduced hint. return hint beartype-0.18.5/beartype/_util/hint/pep/proposal/utilpep593.py000066400000000000000000000300421461113517100242300ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`593`-compliant type hint utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeDecorHintPep593Exception from beartype.typing import ( Any, Optional, Tuple, ) from beartype._data.hint.pep.sign.datapepsigncls import HintSign from beartype._data.hint.pep.sign.datapepsigns import HintSignAnnotated from beartype._data.hint.datahinttyping import TypeException # ....................{ RAISERS }.................... #FIXME: Pass "exception_prefix" to all calls of this validator. def die_unless_hint_pep593( # Mandatory parameters. hint: object, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintPep593Exception, exception_prefix: str = '', ) -> None: ''' Raise an exception of the passed type unless the passed object is a :pep:`593`-compliant **type metahint** (i.e., subscription of either the :attr:`typing.Annotated` or :attr:`typing_extensions.Annotated` type hint factories). Parameters ---------- hint : object Type hint to be inspected. exception_cls : TypeException Type of exception to be raised. Defaults to :exc:`BeartypeDecorHintPep593Exception`. exception_prefix : str, optional Human-readable substring prefixing the representation of this object in the exception message. Defaults to the empty string. Raises ------ BeartypeDecorHintPep593Exception If this object is *not* a :pep:`593`-compliant type metahint. ''' # If this hint is *NOT* PEP 593-compliant, raise an exception. if not is_hint_pep593(hint): assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') raise exception_cls( f'{exception_prefix}type hint {repr(hint)} not PEP 593-compliant ' f'(e.g., "typing.Annotated[...]", ' f'"typing_extensions.Annotated[...]").' ) # ....................{ TESTERS }.................... #FIXME: Unit test us up. def is_hint_pep593(hint: Any) -> bool: ''' :data:`True` only if the passed object is a :pep:`593`-compliant **type metahint** (i.e., subscription of either the :attr:`typing.Annotated` or :attr:`typing_extensions.Annotated` type hint factories). Parameters ---------- hint : Any Type hint to be inspected. Returns ------- bool :data:`True` only if this object is a :pep:`593`-compliant type metahint. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.utilpepget import get_hint_pep_sign_or_none # Return true only if this hint is PEP 593-compliant. return get_hint_pep_sign_or_none(hint) is HintSignAnnotated def is_hint_pep593_ignorable(hint: object) -> bool: ''' :data:`True` only if the passed :pep:`593`-compliant type hint is ignorable. Specifically, this tester returns :data:`True` only if either: * The first subscripted argument of this hint is an ignorable type hint (e.g., :obj:`typing.Any`). * The second subscripted argument is *not* a beartype validator (e.g., ``typing.Annotated[typing.Any, bool]``). This tester is intentionally *not* memoized (e.g., by the ``callable_cached`` decorator), as this tester is only safely callable by the memoized parent :func:`beartype._util.hint.utilhinttest.is_hint_ignorable` tester. Parameters ---------- hint : object Type hint to be inspected. Returns ------- bool :data:`True` only if this :pep:`593`-compliant type hint is ignorable. ''' # Avoid circular import dependencies. from beartype._util.hint.utilhinttest import is_hint_ignorable # print(f'!!!!!!!Received 593 hint: {repr(hint)} [{repr(hint_sign)}]') # Return true only if... return ( # The first argument subscripting this annotated type hint is ignorable # (e.g., the "Any" in "Annotated[Any, 50, False]") *AND*... is_hint_ignorable(get_hint_pep593_metahint(hint)) and # The second argument subscripting this annotated type hint is *NOT* a # beartype validator and thus also ignorable (e.g., the "50" in # "Annotated[Any, 50, False]"). not is_hint_pep593_beartype(hint) ) # ....................{ TESTERS ~ beartype }.................... def is_hint_pep593_beartype(hint: Any) -> bool: ''' :data:`True` only if the second argument subscripting the passed :pep:`593`-compliant :attr:`typing.Annotated` type hint is :mod:`beartype`-specific (e.g., instance of the :class:`BeartypeValidator` class produced by subscripting (indexing) the :class:`Is` class). Parameters ---------- hint : Any :pep:`593`-compliant type hint to be inspected. Returns ------- bool :data:`True` only if the first argument subscripting this hint is :mod:`beartype`-specific. Raises ------ BeartypeDecorHintPep593Exception If this object is *not* a :pep:`593`-compliant type metahint. ''' # Defer heavyweight imports. from beartype.vale._core._valecore import BeartypeValidator # If this object is *NOT* a PEP 593-compliant type metahint, raise an # exception. die_unless_hint_pep593(hint) # Else, this object is a PEP 593-compliant type metahint. # Attempt to... try: # Tuple of one or more arbitrary objects annotating this metahint. hint_metadata = get_hint_pep593_metadata(hint) # Return true only if the first such object is a beartype validator. # Note this object is guaranteed to exist by PEP 593 design. # print(f'Checking first PEP 593 type hint {repr(hint)} arg {repr(hint_metadata[0])}...') return isinstance(hint_metadata[0], BeartypeValidator) # If the metaclass of the first argument subscripting this hint overrides # the __isinstancecheck__() dunder method to raise an exception, silently # ignore this exception by returning false instead. except: return False # ....................{ GETTERS }.................... #FIXME: Unit test us up, please. def get_hint_pep593_metadata( hint: Any, exception_prefix: str = '') -> Tuple[Any, ...]: ''' Tuple of one or more arbitrary objects annotating the passed :pep:`593`-compliant **type metahint** (i.e., subscription of the :attr:`typing.Annotated` singleton). Specifically, this getter returns *all* arguments subscripting this metahint excluding the first, which conveys its own semantics and is thus returned by the :func:`get_hint_pep593_metahint` getter. This getter is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object `PEP 593`-compliant type metahint to be inspected. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- type Tuple of one or more arbitrary objects annotating this metahint. Raises ------ BeartypeDecorHintPep593Exception If this object is *not* a :pep:`593`-compliant type metahint. See Also -------- :func:`get_hint_pep593_metahint` Related getter. ''' # If this object is *NOT* a metahint, raise an exception. die_unless_hint_pep593(hint=hint, exception_prefix=exception_prefix) # Else, this object is a metahint. # Return the tuple of one or more objects annotating this metahint. By # design, this tuple is guaranteed to be non-empty: e.g., # >>> from typing import Annotated # >>> Annotated[int] # TypeError: Annotated[...] should be used with at least two # arguments (a type and an annotation). return hint.__metadata__ #FIXME: Unit test us up, please. def get_hint_pep593_metahint(hint: Any, exception_prefix: str = '') -> Any: ''' PEP-compliant type hint annotated by the passed :pep:`593`-compliant **type metahint** (i.e., subscription of the :attr:`typing.Annotated` singleton). Specifically, this getter returns the first argument subscripting this metahint. By design, this argument is guaranteed to be a PEP-compliant type hint. Note that although that hint *may* be a standard class, this is *not* necessarily the case. This getter is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object :pep:`593`-compliant type metahint to be inspected. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- Any PEP-compliant type hint annotated by this metahint. Raises ---------- BeartypeDecorHintPep593Exception If this object is *not* a :pep:`593`-compliant type metahint. See Also ---------- :func:`get_hint_pep593_metadata` Related getter. ''' # If this object is *NOT* a metahint, raise an exception. die_unless_hint_pep593(hint=hint, exception_prefix=exception_prefix) # Else, this object is a metahint. # Return the PEP-compliant type hint annotated by this metahint. # # Note that most edge-case PEP-compliant type hints store their data in # hint-specific dunder attributes (e.g., "__supertype__" for new type # aliases, "__forward_arg__" for forward references). Some, however, # coopt and misuse standard dunder attributes commonly used for # entirely different purposes. PEP 593-compliant type metahints are the # latter sort, preferring to store their class in the standard # "__origin__" attribute commonly used to store the origin type of type # hints originating from a standard class rather than in a # metahint-specific dunder attribute. return hint.__origin__ # ....................{ REDUCERS }.................... def reduce_hint_pep593( hint: object, exception_prefix: str, *args, **kwargs) -> object: ''' Reduce the passed :pep:`593`-compliant **type metahint** (i.e., subscription of either the :attr:`typing.Annotated` or :attr:`typing_extensions.Annotated` type hint factories) to a lower-level type hint if this metahint contains *no* **beartype validators** (i.e., subscriptions of :mod:`beartype.vale` factories) and is thus ignorable. This reducer is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object Type hint to be reduced. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. All remaining passed arguments are silently ignored. Returns ------- object Lower-level type hint currently supported by :mod:`beartype`. ''' # print(f'Reducing non-beartype PEP 593 type hint {repr(hint)}...') # Return either... return ( # If this metahint is beartype-specific, preserve this hint as is for # subsequent handling elsewhere; hint if is_hint_pep593_beartype(hint) else # Else, this metahint is beartype-agnostic and thus irrelevant to us. In # this case, ignore all annotations on this hint by reducing this hint # to the lower-level hint it annotates. get_hint_pep593_metahint(hint=hint, exception_prefix=exception_prefix) ) beartype-0.18.5/beartype/_util/hint/pep/proposal/utilpep604.py000066400000000000000000000204231461113517100242230ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`604`-compliant type hint utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_10 # ....................{ VERSIONS }.................... # If the active Python interpreter targets Python >= 3.10 and thus supports PEP # 604, define testers requiring this level of support... if IS_PYTHON_AT_LEAST_3_10: # ....................{ IMPORTS }.................... # Defer version-specific imports. from beartype.roar import BeartypeDecorHintPep604Exception from beartype._cave._cavefast import ( HintPep604Type, HintPep604Types, ) # ....................{ RAISERS }.................... #FIXME: Unit test us up, please. def die_if_hint_pep604_inconsistent(hint: object) -> None: # Avoid circular import dependencies. from beartype._util.hint.utilhintget import get_hint_repr # If this hint is invalid as an item of a PEP 604-compliant new union, # silently reduce to a noop. if not isinstance(hint, HintPep604Types): return # Else, this hint is valid as an item of a PEP 604-compliant new union. # Machine-readable representation of this hint. hint_repr = get_hint_repr(hint) # If this representation is prefixed by the "<" character, this # representation is assumed to be (at least *SOMEWHAT*) standardized and # thus internally consistent. This includes: # * Standard classes (e.g., ""). # * @beartype-specific forward reference subclasses (e.g., ""). # # This is *NOT* simply an optimization. Standardized representations # *MUST* be excluded from consideration, as the representations of new # unions containing these hints is *NOT* prefixed by "<": e.g., # >>> repr(bool) # # >>> bool | None # bool | None if hint_repr[0] == '<': return # Else, this representation is *NOT* prefixed by the "<" character. # Arbitrary PEP 604-compliant new union defined as the conjunction # of this hint with an arbitrary builtin type guaranteed to exist. # # Note that order is significant. hint_pep604 = hint | int # type: ignore[operator] # Machine-readable representation of this new union. hint_pep604_repr = get_hint_repr(hint_pep604) # If the representation of this new union is *NOT* prefixed by the # representation of this hint, raise an exception. if not hint_pep604_repr.startswith(hint_repr): raise BeartypeDecorHintPep604Exception( f'Type hint {hint_repr} inconsistent with respect to ' f'repr() strings. Since @beartype requires consistency ' f'between type hints and repr() strings, this hint is ' f'unsupported by @beartype. Consider reporting this issue ' f'to the third-party developer implementing this hint: e.g.,\n' f'\t>>> repr({hint_repr})\n' f'\t{hint_repr} # <-- this is fine\n' f'\t>>> repr({hint_repr} | int)\n' f'\t{hint_pep604_repr} # <-- *THIS IS REALLY SUPER BAD*\n' f'\n' f'\t# Ideally, that output should instead resemble:\n' f'\t>>> repr({hint_repr} | int)\n' f'\t{hint_repr} | int # <-- what @beartype wants!' ) # Else, the representation of this new union is prefixed by the # representation of this hint as expected. # ....................{ TESTERS }.................... def is_hint_pep604(hint: object) -> bool: # Release the werecars, Bender! return isinstance(hint, HintPep604Type) # Else, the active Python interpreter targets Python < 3.10 and thus fails to # support PEP 604. In this case, define fallback functions. # # Tonight, we howl at the moon. Tomorrow, the one-liner! else: def die_if_hint_pep604_inconsistent(hint: object) -> None: pass def is_hint_pep604(hint: object) -> bool: return False die_if_hint_pep604_inconsistent.__doc__ = ( ''' Raise an exception if the passed object is a :pep:`604`-compliant **inconsistent type hint** (i.e., object permissible as an item of a :pep:`604`-compliant new union whose machine-readable representation is *not* the machine-readable representation of this hint in new unions). Motivation ---------- This raiser protects the :mod:`beartype` codebase against inconsistencies in poorly implemented third-party type hints whose **machine-readable representations** (i.e., strings returned by passing those hints to the :func:`repr` builtin) differ from their representations in new unions containing those hints. Ideally, *no* such inconsistencies would ever exist. For unknown reasons, some third-party type hints induce such inconsistencies. :mod:`nptyping` is the canonical example. Type hint factories published by :mod:`nptyping` dynamically create in-memory classes all sharing the same fully-qualified names despite being distinct classes. Ye who code, grok and weep: e.g., .. code-block:: pycon # Define two typical "nptyping" type hints. >>> from nptyping import Float64, NDArray, Shape >>> foo = NDArray[Shape["N, N"], Float64] >>> bar = NDArray[Shape["*"], Float64] >>> repr(foo) NDArray[Shape["N, N"], Float64] # <-- this is sane >>> repr(bar) NDArray[Shape["*"], Float64] # <-- this is sane, too >>> foo == bar False # <-- still sane >>> foo.__name__ NDArray # <-- this is insane >>> bar.__name__ NDArray # <-- still insane after all these years >>> foo | None == bar | None False # <-- back to sane >>> repr(foo | None) NDArray | None # <-- big yikes >>> repr(bar | None) NDArray | None # <-- yikes intensifies ``foo`` and ``bar`` are distinct type hints matching different NumPy arrays; their representations are likewise distinct. The new unions of those hints with :data:`None` are also distinct type hints; nonetheless, the representations of those new unions **are the exact same.** This raiser detects this inconsistency by raising an exception from the :func:`beartype.beartype` decorator. If we failed to do so, then :func:`beartype.beartype` would behave non-deterministically when presented with such hints. Consider the following decoration: .. code-block:: python @beartype def bad_func(first_array: foo | None, second_array: bar | None) -> None: ... Given that decoration, :func:`beartype.beartype` would (in order): #. Cache the first new union ``foo | None`` under the string representation ``"NDArray | None"``. #. Erroneously replace the second new union ``bar | None`` with the previously cached new union ``foo | None``. Why? Because those two new unions share the same representations. From the limited perspective of :mod:`beartype`, those two new unions are effectively the same new union and thus can be safely de-duplicated. *This is why we facepalm.* Parameters ---------- hint : object Type hint to be inspected. Raises ------ bool :data:`True` only if this object is a :pep:`604`-compliant union. ''') is_hint_pep604.__doc__ = ( ''' :data:`True` only if the passed object is a :pep:`604`-compliant **union** (i.e., ``|``-delimited disjunction of two or more isinstanceable types). Parameters ---------- hint : object Type hint to be inspected. Returns ------- bool :data:`True` only if this object is a :pep:`604`-compliant union. ''') beartype-0.18.5/beartype/_util/hint/pep/proposal/utilpep613.py000066400000000000000000000110721461113517100242230ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`613`-compliant **type alias** (i.e., :obj:`typing.TypeAlias` type hint singleton) utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeDecorHintPep613DeprecationWarning from beartype._util.error.utilerrwarn import issue_warning # ....................{ REDUCERS }.................... def reduce_hint_pep613( hint: object, exception_prefix: str, *args, **kwargs) -> object: ''' Reduce the passed :pep:`613`-compliant **type alias** (i.e., :obj:`typing.TypeAlias` type hint singleton) to the ignorable :class:`object` superclass. This reducer effectively ignores *all* :obj:`typing.TypeAlias` type hint singleton, which convey *no* meaningful metadata or semantics. Frankly, it's unclear why :pep:`613` even exists. The CPython developer community felt similarly, which is why :pep:`695` type aliases deprecate :pep:`613`. This reducer is intentionally *not* memoized (e.g., by the ``callable_cached`` decorator), as reducers cannot be memoized. Parameters ---------- hint : object Typed dictionary to be reduced. exception_prefix : str, optional Substring prefixing exception messages raised by this reducer. All remaining passed arguments are silently ignored. Returns ------- object Lower-level type hint currently supported by :mod:`beartype`. Warns ----- BeartypeDecorHintPep613DeprecationWarning :pep:`613`-compliant type aliases have been officially deprecated by :pep:`695`-compliant type aliases. ''' # Emit a non-fatal deprecation warning. issue_warning( cls=BeartypeDecorHintPep613DeprecationWarning, message=( f'{exception_prefix}PEP 613 type hint {repr(hint)} ' f'deprecated by PEP 695. Consider either:\n' f'* Requiring Python >= 3.12 and refactoring PEP 613 type aliases ' f'into PEP 695 type aliases. Note that Python < 3.12 will hate you ' f'for this: e.g.,\n' f' # Instead of this...\n' f' from typing import TypeAlias\n' f' alias_name: TypeAlias = alias_value\n' f'\n' f' # ..."just" do this. Congrats. You destroyed your codebase.\n' f' type alias_name = alias_value\n' f'* Refactoring PEP 613 type aliases into PEP 484 ' f'"typing.NewType"-based type aliases. Note that static ' f'type-checkers (e.g., mypy, pyright, Pyre) will hate you for ' f'this: e.g.,\n' f' # Instead of this...\n' f' from typing import TypeAlias\n' f' alias_name: TypeAlias = alias_value\n' f'\n' f' # ..."just" do this. Congrats. You destroyed your codebase.\n' f' from typing import NewType\n' f' alias_name = NewType("alias_name", alias_value)\n' f'\n' f'Combine the above two approaches via The Ultimate Type Alias ' f'(TUTA), a hidden ninja technique that supports all Python ' f'versions and static type-checkers but may cause coworker heads ' f'to pop off like in that one Kingsman scene:\n' f' # Instead of this...\n' f' from typing import TypeAlias\n' f' alias_name: TypeAlias = alias_value\n' f'\n' f' # ..."just" do this. If you think this sucks, know that you are not alone.\n' f' from typing import TYPE_CHECKING, NewType, TypeAlias # <-- sus af\n' f' from sys import version_info # <-- code just got real\n' f' if TYPE_CHECKING: # <-- if static type-checking, then PEP 613\n' f' alias_name: TypeAlias = alias_value # <-- grimdark coding style\n' f' elif version_info >= (3, 12): # <-- if Python >= 3.12, then PEP 695\n' f' exec("type alias_name = alias_value") # <-- eldritch abomination\n' f' else: # <-- if Python < 3.12, then PEP 484\n' f' alias_name = NewType("alias_name", alias_value) # <-- coworker gives up here\n' ), ) # Reduce *ALL* PEP 613 type hints to an arbitrary ignorable type hint. return object beartype-0.18.5/beartype/_util/hint/pep/proposal/utilpep647.py000066400000000000000000000060721461113517100242360ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`647`-compliant **type hint** (i.e., objects created by subscripting the :obj:`typing.Final` type hint factory) utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeDecorHintPep647Exception from beartype.typing import ( Optional, Type, ) from beartype._data.func.datafuncarg import ARG_NAME_RETURN # ....................{ REDUCERS }.................... #FIXME: Unit test us up, please. def reduce_hint_pep647( hint: object, pith_name: Optional[str], exception_prefix: str, *args, **kwargs ) -> Type[bool]: ''' Reduce the passed :pep:`647`-compliant **type guard** (i.e., subscription of the :obj:`typing.TypeGuard` type hint factory) to the builtin :class:`bool` class as advised by :pep:`647` when performing runtime type-checking if this hint annotates the return of some callable (i.e., if ``pith_name`` is ``"return"``) *or* raise an exception otherwise (i.e., if this hint annotates the return of *no* callable). This reducer is intentionally *not* memoized (e.g., by the ``@callable_cached`` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object Final type hint to be reduced. pith_name : Optional[str] Either: * If this hint annotates a parameter of some callable, the name of that parameter. * If this hint annotates the return of some callable, ``"return"``. * Else, :data:`None`. exception_prefix : str Human-readable label prefixing the representation of this object in the exception message. All remaining passed arguments are silently ignored. Returns ------- Type[bool] Builtin :class:`bool` class. Raises ------ BeartypeDecorHintPep647Exception If this type guard does *not* annotate the return of some callable (i.e., if ``arg_kind`` is *not* :data:`True`). ''' # If this type guard annotates the return of some callable, reduce this type # guard to the builtin "bool" class. Sadly, type guards are useless at # runtime and exist exclusively as a means of superficially improving the # computational intelligence of (...wait for it) static type-checkers. if pith_name == ARG_NAME_RETURN: return bool # Else, this type guard does *NOT* annotate the return of some callable. # Raise an exception. Type guards are contextually valid *ONLY* as top-level # return annotations. raise BeartypeDecorHintPep647Exception( f'{exception_prefix}PEP 647 type hint {repr(hint)} ' f'invalid in this type hint context (i.e., ' f'{repr(hint)} valid only as non-nested return annotation).' ) beartype-0.18.5/beartype/_util/hint/pep/proposal/utilpep673.py000066400000000000000000000111161461113517100242300ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`673`-compliant **self type hint** (i.e., the :obj:`typing.Self` type hint singleton) utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeDecorHintPep673Exception from beartype._cave._cavemap import NoneTypeOr from beartype._data.hint.datahinttyping import TypeStack # ....................{ REDUCERS }.................... #FIXME: Unit test us up, please. def reduce_hint_pep673( hint: object, cls_stack: TypeStack, exception_prefix: str, *args, **kwargs ) -> type: ''' Reduce the passed :pep:`673`-compliant **self type hint** (i.e., the :obj:`typing.Self` type hint singleton) to the **currently decorated class** (i.e., the most deeply nested class on the passed type stack, signifying the class currently being decorated by :func:`beartype.beartype`) if any *or* raise an exception otherwise (i.e., if *no* class is currently being decorated). This reducer is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as reducers cannot be memoized. Parameters ---------- hint : object Self type hint to be reduced. cls_stack : TypeStack, optional **Type stack** (i.e., either tuple of zero or more arbitrary types *or* :data:`None`). Defaults to :data:`None`. See also the :func:`beartype._decor.decormain.beartype_object` decorator. exception_prefix : str, optional Human-readable substring prefixing exception messages raised by this reducer. All remaining passed arguments are silently ignored. Returns ------- type Most deeply nested class on this type stack. Raises ------ BeartypeDecorHintPep673Exception If either: * ``cls_stack`` is :data:`None`. * ``cls_stack`` is non-:data:`None` but empty. ''' assert isinstance(cls_stack, NoneTypeOr[tuple]), ( f'{repr(cls_stack)} neither tuple nor "None".') # If either no type stack *OR* an empty type stack was passed, *NO* class is # currently being decorated by @beartype. It follows that either: # * @beartype is currently decorating a function or method directly. # * A statement-level runtime type-checker (e.g., # beartype.door.is_bearable()) is currently being called. # # However, the "typing.Self" type hint *CANNOT* be reliably resolved outside # of a class context. Although @beartype could attempt to heuristically # differentiate functions from methods via the first passed argument, Python # itself does *NOT* require that argument of a method to be named "self"; # such a heuristic would catastrophically fail in common edge cases. Our # only recourse is to raise an exception encouraging the user to refactor # their code to decorate classes rather than methods. if not cls_stack: # We didn't make crazy. We only document it. raise BeartypeDecorHintPep673Exception( f'{exception_prefix}PEP 673 type hint "{repr(hint)}" ' f'invalid outside @beartype-decorated class. ' f'PEP 673 type hints are valid only inside classes decorated by ' f'@beartype. If this hint annotates a method decorated by ' f'@beartype, instead decorate the class declaring this method by ' f'@beartype: e.g.,\n' f'\n' f' # Instead of decorating methods by @beartype like this...\n' f' class BadClassIsBad(object):\n' f' @beartype\n' f' def awful_method_is_awful(self: Self) -> Self:\n' f' return self\n' f'\n' f' # ...decorate classes by @beartype instead - like this!\n' f' @beartype\n' f' class GoodClassIsGood(object):\n' f' def wonderful_method_is_wonderful(self: Self) -> Self:\n' f' return self\n' f'\n' f"This has been a message of the Bearhugger Broadcasting Service." ) # Else, a non-empty type stack was passed. # Reduce this hint to the currently decorated class (i.e., the most deeply # nested class on this type stack, signifying the class currently being # decorated by @beartype.beartype). return cls_stack[-1] beartype-0.18.5/beartype/_util/hint/pep/proposal/utilpep675.py000066400000000000000000000024571461113517100242420ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`675`-compliant **literal string type hint** (i.e., objects created by subscripting the :obj:`typing.Final` type hint factory) utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import Type # ....................{ REDUCERS }.................... #FIXME: Unit test us up, please. def reduce_hint_pep675(*args, **kwargs) -> Type[str]: ''' Reduce the passed :pep:`675`-compliant **literal string type hint** (i.e., :obj:`typing.LiteralString` singleton) to the builtin :class:`str` class as advised by :pep:`675` when performing runtime type-checking. This reducer is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as reducers cannot be memoized. Parameters ---------- All passed arguments are silently ignored. Returns ------- Type[str] Builtin :class:`str` class. ''' # Unconditionally reduce this hint to the builtin "str" class. return str beartype-0.18.5/beartype/_util/hint/pep/proposal/utilpep695.py000066400000000000000000000563151461113517100242460ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`695`-compliant **type alias** (i.e., objects created via the ``type`` statement under Python >= 3.12) utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... #FIXME: Consider generalizing @beartype's PEP 695 implementation to additionally #support local type aliases (i.e., defined in the local scope of a callable #rather than at global scope) containing one or more unquoted forward #references. Currently, @beartype intentionally fails to support this: e.g., # type global_alias = ... # die_if_unbearable('lolwut', global_alias) # <-- this is fine # # def muh_func(...) -> ...: # type local_alias = ... # die_if_unbearable('lolwut', local_alias) # <-- raises an exception # #The reasons are obscure. Very well. CPython's current implementation of local #type aliases is probably very buggy. An upstream issue describing this #bugginess should be submitted. When doing so, please publicly declare that PEP #695 appears to have been poorly tested. As evidence, note that PEP 695 itself #advises use of the following idiom: # # A type alias that includes a forward reference # type AnimalOrVegetable = Animal | "Vegetable" # #*THAT DOES NOT ACTUALLY WORK AT RUNTIME.* Nobody tested that. This is why I #facepalm. Notably, PEP 604-compliant new-style unions prohibit strings. They #probably shouldn't, but they've *ALWAYS* behaved that way, and nobody's updated #them to behave more intelligently -- probably because doing so would require #updating the isinstance() builtin (which also accepts PEP 604-compliant #new-style unions) to behave more intelligiently and ain't nobody goin' there: #e.g., # # $ python3.12 # >>> type AnimalOrVegetable = "Animal" | "Vegetable" # >>> AnimalOrVegetable.__value__ # Traceback (most recent call last): # Cell In[3], line 1 # AnimalOrVegetable.__value__ # Cell In[2], line 1 in AnimalOrVegetable # type AnimalOrVegetable = "Animal" | "Vegetable" # TypeError: unsupported operand type(s) for |: 'str' and 'str' # #For further details, see the comment below prefixed by: # # If that module fails to define this alias as a global variable, # #Since CPython is unlikely to resolve its bugginess anytime soon, it inevitably #falls to @beartype to resolve this. Thankfully, @beartype *CAN* resolve this. #Unthankfully, doing so will require @beartype to implement a new PEP #695-specific AST transform from the "beartype.claw" subpackage augmenting *ALL* #PEP 695-compliant local type aliases (so, probably *ALL* type aliases #regardless of scope for simplicity) as follows: # # "beartype.claw" should transform this... # type {alias_name} = {alias_value} # # # ...into this. # from beartype._util.hint.pep.proposal.utilpep695 import ( # iter_hint_pep695_forwardrefs as # __iter_hint_pep695_forwardref_beartype__ # ) # type {alias_name} = {alias_value} # for __hint_pep695_forwardref_beartype__ in ( # __iter_hint_pep695_forwardref_beartype__({alias_name})): # # If the current scope is module scope, prefer an efficient # # non-exec()-based solution. Note that this optimization does *NOT* # # generalize to other scopes, for obscure reasons delineated here: # # https://stackoverflow.com/a/8028772/2809027 # if globals() is locals(): # globals()[__hint_pep695_forwardref_beartype__.__name_beartype__] = # __hint_pep695_forwardref_beartype__) # # Else, the current scope is *NOT* module scope. In this case, # # fallback to an inefficient exec()-based solution. # else: # exec(f'{__hint_pep695_forwardref_beartype__.__name_beartype__} = __hint_pep695_forwardref_beartype__') # # #FIXME: Technically, this *ONLY* needs to be done if the # #iter_hint_pep695_forwardrefs() iterator returned something. *shrug* # # Intentionally redefine this alias. Although this appears to be an # # inefficient noop, this is in fact an essential operation. Why? # # Because the prior successful access of the "__value__" dunder # # variable silently cached and thus froze the value of this alias. # # However, alias values are *NOT* necessarily safely freezable at # # alias definition time. The canonical example of alias values that # # are *NOT* safely freezable at alias definition time are mutually # # recursive aliases (i.e., aliases whose values circularly refer to # # one another): e.g., # # type a = b # # type b = a # # # # PEP 695 provides no explicit means of uncaching alias values. # # Our only recourse is to repetitiously redefine this alias. # type {alias_name} = {alias_value} # #We're currently unclear whether anyone actually cares about this. Ergo, we #adopted the quick-and-dirty approach of raising exceptions instead. Yikes! # ....................{ IMPORTS }.................... from beartype.meta import URL_ISSUES from beartype.roar import BeartypeDecorHintPep695Exception from beartype.typing import ( Iterable, Optional, ) from beartype._cave._cavefast import HintPep695Type from beartype._check.forward.reference.fwdrefmeta import BeartypeForwardRefMeta from beartype._check.forward.reference.fwdrefmake import ( make_forwardref_indexable_subtype) from beartype._util.error.utilerrget import get_name_error_attr_name from beartype._util.module.utilmodget import get_module_imported_or_none # ....................{ TESTERS }.................... def is_hint_pep695_ignorable(hint: HintPep695Type) -> bool: ''' :data:`True` only if the passed :pep:`695`-compliant **type alias** (i.e., object created by a statement of the form ``type {alias_name} = {alias_value}``) is ignorable. Specifically, this tester ignores the passed type alias if the lower-level type hint aliased by this alias is itself deeply ignorable. This tester is intentionally *not* memoized (e.g., by the ``callable_cached`` decorator), as this tester is only safely callable by the memoized parent :func:`beartype._util.hint.utilhinttest.is_hint_ignorable` tester. Parameters ---------- hint : HintPep695Type Type alias to be inspected. Returns ------- bool :data:`True` only if this :pep:`695`-compliant type alias is ignorable. ''' # Avoid circular import dependencies. from beartype._util.hint.utilhinttest import is_hint_ignorable # Return true only if this hint aliases an ignorable child type hint. return is_hint_ignorable(get_hint_pep695_alias(hint)) # ....................{ GETTERS }.................... #FIXME: Unit test us up, please. def get_hint_pep695_alias( hint: HintPep695Type, exception_prefix: str = '') -> object: ''' **Non-alias type hint** (i.e., type hint that is *not* a :pep:`695`-compliant type alias) encapsulated by the passed :pep:`695`-compliant **type alias** (i.e., object created by a statement of the form ``type {alias_name} = {alias_value}``). This getter is intentionally *not* memoized (e.g., by the ``callable_cached`` decorator), for subtle reasons pertaining to unquoted forward references. Notably, memoizing this getter would prevent the external caller of the higher-level :func:`.iter_hint_pep695_forwardrefs` iterator calling this lower-level getter from externally modifying this type alias by forcefully injecting forward reference proxies into this alias. Parameters ---------- hint : HintPep695Type Type alias to be inspected. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- object Unaliased type hint encapsulated by this type alias. Raises ------ BeartypeDecorHintPep695Exception If this type hint is *not* a PEP 695-compliant type alias. NameError If this type alias contains one or more **unquoted forward references** to undefined types. ''' # If this hint is *NOT* a PEP 695-compliant type alias, raise an exception. if not isinstance(hint, HintPep695Type): raise BeartypeDecorHintPep695Exception( f'{exception_prefix}type hint {repr(hint)} ' f'not PEP 695 type alias.' ) # Else, this hint is a PEP 695-compliant type alias. # While the Universe continues infinitely expanding... while True: # Reduce this type alias to the type hint aliased by this alias, which # itself is possibly a nested type alias. Oh, it happens. # # Note that doing so implicitly raises a "NameError" if this alias # contains one or more unquoted forward references to undefined types. hint = hint.__value__ # type: ignore[attr-defined] # If this type hint is *NOT* a nested type alias, break this iteration. if not isinstance(hint, HintPep695Type): break # Else, this type hint is a nested type alias. In this case, continue # iteratively unwrapping this nested type alias. # Return this unaliased type alias. return hint # ....................{ ITERATORS }.................... def iter_hint_pep695_forwardrefs( # Mandatory parameters. hint: HintPep695Type, # Optional parameters. exception_prefix: str = '', ) -> Iterable[BeartypeForwardRefMeta]: ''' Iteratively create and yield one **forward reference proxy** (i.e., :class:`beartype._check.forward.reference.fwdrefabc.BeartypeForwardRefABC` subclass) for each unquoted relative forward reference in the passed :pep:`695`-compliant **type alias** (i.e., object created by a statement of the form ``type {alias_name} = {alias_value}``) to the underlying type hint lazily referred to by this type alias. This iterator is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as this iterator is intended to be called only once per type alias in userspace code dynamically instrumented by beartype import hook abstract syntax tree (AST) node transformers. Since those transformers are expected to replace *all* unquoted relative forward references in this type alias with corresponding forward reference proxies, calling this iterator again on any type alias instrumented in this way *should* silently reduce to a noop. Parameters ---------- hint : HintPep695Type Type alias to be iterated over. exception_prefix : str, optional Human-readable substring prefixing exception messages raised by this reducer. Defaults to the empty string. Yields ------ BeartypeForwardRefMeta Forward reference proxy encapsulating the next unquoted relative forward reference in this :pep:`695`-compliant type alias. Raises ------ BeartypeDecorHintPep695Exception If this type hint is *not* a PEP 695-compliant type alias. ''' # Unqualified basename of the previous undeclared attribute in this alias. hint_ref_name_prev: Optional[str] = None # While this type alias still contains one or more forward references to # attributes *NOT* defined by the module declaring this type alias... while True: # Attempt to... try: # print(f'type {hint.__name__} = {hint.__value__}') # Reduce this alias to the type hint it lazily refers to. If this # alias contains *NO* forward references to undeclared attributes, # this reduction *SHOULD* succeed. Let's pretend we mean that. # # Note that get_hint_pep695_alias() is memoized and thus # intentionally called with positional arguments. get_hint_pep695_alias(hint, exception_prefix) # This reduction raised *NO* exception and thus succeeded. In this # case, immediately halt iteration. break # If doing so raises a builtin "NameError" exception, this alias # contains one or more forward references to undeclared attributes. In # this case... except NameError as exception: # Unqualified basename of this alias (i.e., name of the global or # local variable assigned to by the left-hand side of this alias). hint_name = repr(hint) # Fully-qualified name of the external third-party module defining # this alias. hint_module_name = hint.__module__ # print(f'hint_module_name: {hint_module_name}') # If this alias defines *NO* module name, raise an exception. # # Note that this should *NEVER* happen. Nonetheless, static # type-checkers like mypy insist this can happen. It almost # certainly can't. Nonetheless, let's dot our i's and cross our t's. if not hint_module_name: raise BeartypeDecorHintPep695Exception( f'{exception_prefix}PEP 695 type alias "{hint_name}" ' f'module undefined (i.e., "__module__" attribute ' f'either "None" or the empty string).' ) from exception # Else, this alias defines a module name. # That module as its previously imported object. hint_module = get_module_imported_or_none(hint_module_name) # Unqualified basename of the next remaining undeclared attribute # contained in this alias relative to that module. hint_ref_name = get_name_error_attr_name(exception) # print(f'hint: {hint}; hint_ref_name: {hint_ref_name}') # If this attribute is the same as that of the prior iteration of # this "while" loop, then that iteration *MUST* have failed to # define this attribute as a global variable of that module. In this # case, raise an exception. # # Note that this should *NEVER* happen. Of course, this frequently # happens. Specifically, this happens whenever the caller defines a # callable defining type alias as a local variable containing one or # more unquoted relative forward reference to user-defined classes # that have yet to be defined. Why? Because CPython's low-level # C-based implementation of PEP 695-compliant type aliases currently # fails to properly resolve unquoted relative forward references # defined in a local rather than global scope: e.g., # >>> def foo(): # ... type bar = wut # ... globals()['wut'] = str # ... print(bar.__value__) # ... class wut(object): pass # <-- causes madness; WTF!?!? # >>> foo() # NameError: cannot access free variable 'wut' where it is not # associated with a value in enclosing scope # # Why does this matter? Because the abstract syntax tree (AST) # transformation implemented by "beartype.claw" import hooks # dynamically declares the objects that these forward references # refer. Due to deficiencies [read: bugs] in CPython's type alias # implementation, local type aliases remain unable to resolve either # global *OR* local referees that are defined dynamically. Ergo, we # have no recourse but to detect this edge case and raise a # human-readable exception advising the caller with recommendations. if hint_ref_name == hint_ref_name_prev: raise BeartypeDecorHintPep695Exception( f'{exception_prefix}PEP 695 local type alias "{hint_name}" ' f'unquoted relative forward reference "{hint_ref_name}" ' f"unsupported, due to severe deficiencies in CPython's " f'runtime implementation of PEP 695 local type aliases ' f"outside beartype's control. Consider either:\n" f'* Refactoring this local type alias into a ' f'global type alias:\n' f' # Instead of a local type alias ' f'defined in a callable like this...\n' f' def muh_func(...) -> ...:\n' f' type {hint_name} = ...\n' f'\n' f' # Prefer a global type alias defined at module scope.\n' f' type {hint_name} = ...\n' f'* Quoting this forward reference in this type alias:\n' f' # Instead of an unquoted forward reference ' f'like this...\n' f' type {hint_name} = ... {hint_ref_name} ...\n' f'\n' f' # Prefer a quoted forward reference.\n' f' type {hint_name} = ... "{hint_ref_name}" ...' ) from exception # Else, this attribute differs from that of the prior iteration of # this "while" loop. # # If that module paradoxically claims to already define this # attribute as a global variable, raise an exception. # # Note that this should *NEVER* happen. Of course, this will happen. elif hasattr(hint_module, hint_ref_name): raise BeartypeDecorHintPep695Exception( # pragma: no cover f'{exception_prefix}PEP 695 type alias "{hint_name}" ' f'unquoted relative forward reference "{hint_ref_name}" ' f'already defined in module "{hint_module_name}", ' f'despite purportedly being undefined. ' f'In theory, this should never happen. ' f'Of course, this happened. You suddenly feel the ' f'horrifying urge to report this grievous failure to the ' f'beartype issue tracker:\n\t{URL_ISSUES}' ) from exception # Else, that module does *NOT* yet define this attribute. # Forward reference proxy to this undeclared attribute. # # Note that: # * This call is intentionally passed only positional parameters to # satisfy the @callable_cached decorator memoizing this function. # * A full-blown forward reference proxy rather than a trivial # stringified forward reference (i.e., the relative name of the # undefined attribute being referred to, equivalent to # "hint_ref_name") is required here. Why? Subscription. A # stringified forward reference *CANNOT* be subscripted by # arbitrary child type hints; a forward reference proxy can be. hint_ref = make_forwardref_indexable_subtype( hint_module_name, hint_ref_name) # Yield this forward reference proxy to the caller. yield hint_ref # Store the unqualified basename of this previously undeclared # attribute for detection by the next iteration of this loop. hint_ref_name_prev = hint_ref_name # ....................{ REDUCERS }.................... def reduce_hint_pep695( hint: HintPep695Type, exception_prefix: str, *args, **kwargs ) -> object: ''' Reduce the passed :pep:`695`-compliant **type alias** (i.e., object created by a statement of the form ``type {alias_name} = {alias_value}``) to the underlying type hint lazily referred to by this type alias. This reducer is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as reducers cannot be memoized. Parameters ---------- hint : object Type alias to be reduced. exception_prefix : str Human-readable substring prefixing exception messages raised by this reducer. All remaining passed arguments are silently ignored. Returns ------- object Underlying type hint lazily referred to by this type alias. Raises ------ BeartypeDecorHintPep695Exception If this type alias contains one or more unquoted relative forward references to undefined attributes. Note that this *only* occurs when callers avoid beartype import hooks in favour of manually decorating callables and classes with the :func:`beartype.beartype` decorator. ''' # Underlying type hint to be returned. hint_aliased: object = None # Attempt to... try: # Reduce this alias to the type hint it lazily refers to. If this alias # contains *NO* forward references to undeclared attributes, this # reduction *SHOULD* succeed. Let's pretend we mean that. # # Note that get_hint_pep695_alias() is memoized and thus # intentionally called with positional arguments. hint_aliased = get_hint_pep695_alias(hint, exception_prefix) # If doing so raises a builtin "NameError" exception, this alias contains # one or more forward references to undeclared attributes. In this case... except NameError as exception: # Unqualified basename of this alias (i.e., name of the global or local # variable assigned to by the left-hand side of this alias). hint_name = repr(hint) # Fully-qualified name of the third-party module defining this alias. hint_module_name = hint.__module__ # print(f'hint_module_name: {hint_module_name}') # Unqualified basename of the next remaining undeclared attribute # contained in this alias relative to that module. hint_ref_name = get_name_error_attr_name(exception) # print(f'hint: {hint}; hint_ref_name: {hint_ref_name}') # Raise a human-readable exception describing this issue. raise BeartypeDecorHintPep695Exception( f'{exception_prefix}PEP 695 type alias "{hint_name}" ' f'unquoted relative forward reference {repr(hint_ref_name)} in ' f'module "{hint_module_name}" unsupported outside ' f'"beartype.claw" import hooks. Consider either:\n' f'* Quoting this forward reference in this type alias: e.g.,\n' f' # Instead of an unquoted forward reference...\n' f' type {hint_name} = ... {hint_ref_name} ...\n' f'\n' f' # Prefer a quoted forward reference.\n' f' type {hint_name} = ... "{hint_ref_name}" ...\n' f'* Applying "beartype.claw" import hooks to ' f'module "{hint_module_name}": e.g.,\n' f' # In your "this_package.__init__" submodule:\n' f' from beartype.claw import beartype_this_package\n' f' beartype_this_package()' ) from exception # Else, doing so raised *NO* exceptions, implying this alias contains *NO* # forward references to undeclared attributes. # Return this underlying type hint. return hint_aliased beartype-0.18.5/beartype/_util/hint/pep/utilpepget.py000066400000000000000000001333421461113517100226370ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **PEP-compliant type hint getter utilities** (i.e., callables querying arbitrary objects for attributes specific to PEP-compliant type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.meta import URL_ISSUES from beartype.roar import ( BeartypeDecorHintPepException, BeartypeDecorHintPepSignException, ) from beartype.roar._roarexc import _BeartypeUtilTypeException from beartype.typing import ( Any, Optional, # Union, ) from beartype._data.hint.datahinttyping import ( # HintSignTrie, TypeException, ) from beartype._data.hint.pep.datapeprepr import ( HINT_REPR_PREFIX_ARGS_0_OR_MORE_TO_SIGN, HINT_REPR_PREFIX_ARGS_1_OR_MORE_TO_SIGN, HINT_REPR_PREFIX_TRIE_ARGS_0_OR_MORE_TO_SIGN, HINT_TYPE_NAME_TO_SIGN, ) from beartype._data.hint.pep.sign.datapepsigncls import HintSign from beartype._data.hint.pep.sign.datapepsigns import ( HintSignGeneric, HintSignNewType, HintSignTypedDict, HintSignPep585BuiltinSubscriptedUnknown, ) from beartype._data.hint.pep.sign.datapepsignset import ( HINT_SIGNS_ORIGIN_ISINSTANCEABLE, ) from beartype._util.cache.utilcachecall import callable_cached from beartype._util.hint.pep.proposal.pep484.utilpep484newtype import ( is_hint_pep484_newtype_pre_python310) from beartype._util.hint.pep.proposal.pep484585.utilpep484585generic import ( is_hint_pep484585_generic) from beartype._util.hint.pep.proposal.utilpep585 import ( get_hint_pep585_generic_typevars, is_hint_pep585_builtin_subscripted, is_hint_pep585_generic, ) from beartype._util.hint.pep.proposal.utilpep589 import is_hint_pep589 from beartype._util.hint.pep.proposal.utilpep604 import ( die_if_hint_pep604_inconsistent) from beartype._util.py.utilpyversion import ( # IS_PYTHON_AT_LEAST_3_10, IS_PYTHON_AT_MOST_3_9, IS_PYTHON_AT_LEAST_3_9, ) from beartype._data.hint.datahinttyping import TupleTypes # ....................{ GETTERS ~ args }.................... # If the active Python interpreter targets Python >= 3.9, implement this # getter to directly access the "__args__" dunder attribute. if IS_PYTHON_AT_LEAST_3_9: def get_hint_pep_args(hint: object) -> tuple: # Return the value of the "__args__" dunder attribute if this hint # defines this attribute *OR* "None" otherwise. hint_args = getattr(hint, '__args__', None) # If this hint does *NOT* define this attribute, return the empty tuple. if hint_args is None: return () # Else, this hint defines this attribute. # # If this hint appears to be unsubscripted, then this hint *WAS* # actually subscripted by the empty tuple (e.g., "tuple[()]", # "typing.Tuple[()]"). Why? Because: # * Python 3.11 made the unfortunate decision of ambiguously conflating # unsubscripted type hints (e.g., "tuple", "typing.Tuple") with type # hints subscripted by the empty tuple, preventing downstream # consumers from reliably distinguishing these two orthogonal cases. # * Python 3.9 made a similar decision but constrained to only PEP # 585-compliant empty tuple type hints (i.e., "tuple[()]"). PEP # 484-compliant empty tuple type hints (i.e., "typing.Tuple[()]") # continued to correctly declare an "__args__" dunder attribute of # "((),)" until Python 3.11. # # Disambiguate these two cases on behalf of callers by returning a tuple # containing only the empty tuple rather than returning the empty tuple. elif not hint_args: return _HINT_ARGS_EMPTY_TUPLE # Else, this hint is either subscripted *OR* is unsubscripted but not # PEP 585-compliant. # In this case, return this tuple as is. return hint_args # Else, the active Python interpreter targets Python < 3.9. In this case, # implement this getter to directly access the "__args__" dunder attribute. else: def get_hint_pep_args(hint: object) -> tuple: # Under python < 3.9, unparametrized generics have the attribute # "_special" set to True despite the actual "__args__" typically being a # "TypeVar" instance. Because we want to differentiate between # unparametrized and parametrized generics, check whether the hint is # "_special" and if so, we return the empty tuple instead of that # "TypeVar" instance. if getattr(hint, '_special', False): return () # Return the value of the "__args__" dunder attribute if this hint # defines this attribute *OR* the empty tuple otherwise. return getattr(hint, '__args__', ()) # Document this function regardless of implementation details above. get_hint_pep_args.__doc__ = ''' Tuple of all **typing arguments** (i.e., subscripted objects of the passed PEP-compliant type hint listed by the caller at hint declaration time) if any *or* the empty tuple otherwise. This getter is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Caveats ------- **This getter should always be called in lieu of attempting to directly access the low-level** ``__args__`` **dunder attribute.** Various singleton objects defined by the :mod:`typing` module (e.g., :attr:`typing.Any`, :attr:`typing.NoReturn`) fail to define this attribute, guaranteeing :class:`AttributeError` exceptions from all general-purpose logic attempting to directly access this attribute. Thus this function, which "fills in the gaps" by implementing this oversight. **This getter never lies, unlike the comparable** :func:`get_hint_pep_typevars` **getter.** Whereas :func:`get_hint_pep_typevars` synthetically propagates type variables from child to parent type hints (rather than preserving the literal type variables subscripting this type hint), this getter preserves the literal arguments subscripting this type hint if any. Notable cases where the two differ include: * Generic classes subclassing pseudo-superclasses subscripted by one or more type variables (e.g., ``class MuhGeneric(Generic[S, T])``). * Unions subscripted by one or more child type hints subscripted by one or more type variables (e.g., ``Union[str, Iterable[Tuple[S, T]]]``). Parameters ---------- hint : object PEP-compliant type hint to be inspected. Returns ------- tuple Either: * If this hint defines an ``__args__`` dunder attribute, the value of that attribute. * Else, the empty tuple. Examples -------- >>> import typing >>> from beartype._util.hint.pep.utilpepget import ( ... get_hint_pep_args) >>> get_hint_pep_args(typing.Any) () >>> get_hint_pep_args(typing.List[int, str, typing.Dict[str, str]]) (int, str, typing.Dict[str, str]) ''' # ....................{ GETTERS ~ typevars }.................... # If the active Python interpreter targets Python >= 3.9, implement this # function to either directly access the "__parameters__" dunder attribute for # type hints that are not PEP 585-compliant generics *OR* to synthetically # reconstruct that attribute for PEP 585-compliant generics. *sigh* if IS_PYTHON_AT_LEAST_3_9: def get_hint_pep_typevars(hint: object) -> TupleTypes: # Value of the "__parameters__" dunder attribute on this object if this # object defines this attribute *OR* "None" otherwise. hint_pep_typevars = getattr(hint, '__parameters__', None) # If this object defines *NO* such attribute... if hint_pep_typevars is None: # Return either... return ( # If this hint is a PEP 585-compliant generic, the tuple of all # typevars declared on pseudo-superclasses of this generic. get_hint_pep585_generic_typevars(hint) if is_hint_pep585_generic(hint) else # Else, the empty tuple. () ) # Else, this object defines this attribute. # Return this attribute. return hint_pep_typevars # Else, the active Python interpreter targets Python < 3.9. In this case, # implement this function to directly access the "__parameters__" dunder # attribute. else: def get_hint_pep_typevars(hint: object) -> TupleTypes: # Value of the "__parameters__" dunder attribute on this object if this # object defines this attribute *OR* the empty tuple otherwise. Note: # * The "typing._GenericAlias.__parameters__" dunder attribute tested # here is defined by the typing._collect_type_vars() function at # subscription time. Yes, this is insane. Yes, this is PEP 484. # * This trivial test implicitly handles superclass parametrizations. # Thankfully, the "typing" module percolates the "__parameters__" # dunder attribute from "typing" pseudo-superclasses to user-defined # subclasses during PEP 560-style type erasure. Finally: they did # something slightly right. return getattr(hint, '__parameters__', ()) # Document this function regardless of implementation details above. get_hint_pep_typevars.__doc__ = ''' Tuple of all **unique type variables** (i.e., subscripted :class:`TypeVar` instances of the passed PEP-compliant type hint listed by the caller at hint declaration time ignoring duplicates) if any *or* the empty tuple otherwise. This getter is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Caveats ------- **This function should always be called in lieu of attempting to directly access the low-level** ``__parameters__`` **dunder attribute.** Various singleton objects defined by the :mod:`typing` module (e.g., :attr:`typing.Any`, :attr:`typing.NoReturn`) fail to define this attribute, guaranteeing :class:`AttributeError` exceptions from all general-purpose logic attempting to directly access this attribute. Thus this function, which "fills in the gaps" by implementing this oversight. Parameters ---------- hint : object Object to be inspected. Returns ------- Tuple[TypeVar, ...] Either: * If this object defines a ``__parameters__`` dunder attribute, the value of that attribute. * Else, the empty tuple. Examples -------- >>> import typing >>> from beartype._util.hint.pep.utilpepget import ( ... get_hint_pep_typevars) >>> S = typing.TypeVar('S') >>> T = typing.TypeVar('T') >>> get_hint_pep_typevars(typing.Any) () >>> get_hint_pep_typevars(typing.List[T, int, S, str, T]) (T, S) ''' # ....................{ GETTERS ~ sign }.................... def get_hint_pep_sign( # Mandatory parameters. hint: object, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintPepSignException, exception_prefix: str = '', ) -> HintSign: ''' **Sign** (i.e., :class:`HintSign` instance) uniquely identifying the passed PEP-compliant type hint if PEP-compliant *or* raise an exception otherwise (i.e., if this hint is *not* PEP-compliant). This getter is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object Type hint to be inspected. exception_cls : TypeException, optional Type of exception to be raised in the event of a fatal error. Defaults to :exc:`.BeartypeDecorHintPepSignException`. exception_prefix : str, optional Human-readable substring prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- dict Sign uniquely identifying this hint. Raises ------ exception_cls If this hint is either: * PEP-compliant but *not* uniquely identifiable by a sign. * PEP-noncompliant. * *Not* a hint (i.e., neither PEP-compliant nor -noncompliant). See Also -------- :func:`get_hint_pep_sign_or_none` Further details. ''' # Sign uniquely identifying this hint if recognized *OR* "None" otherwise. hint_sign = get_hint_pep_sign_or_none(hint) # If this hint is unrecognized... if hint_sign is None: assert isinstance(exception_cls, type), ( f'{exception_cls} not exception type.') assert isinstance(exception_prefix, str), ( f'{exception_prefix} not string.') # Avoid circular import dependencies. from beartype._util.hint.nonpep.utilnonpeptest import die_if_hint_nonpep # If this hint is PEP-noncompliant, raise an exception. die_if_hint_nonpep( hint=hint, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # Else, this hint is *NOT* PEP-noncompliant. Since this hint was # unrecognized, this hint *MUST* necessarily be a PEP-compliant type # hint currently unsupported by the @beartype decorator. # Raise an exception indicating this. # # Note that we intentionally avoid calling the # die_if_hint_pep_unsupported() function here, which calls the # is_hint_pep_supported() function, which calls this function. raise exception_cls( f'{exception_prefix}type hint {repr(hint)} ' f'currently unsupported by beartype. ' f'You suddenly feel encouraged to submit a feature request ' f'for this hint to our friendly issue tracker at:\n' f'\t{URL_ISSUES}' ) # Else, this hint is recognized. # Return the sign uniquely identifying this hint. return hint_sign #FIXME: Revise us up the docstring, most of which is now obsolete. @callable_cached def get_hint_pep_sign_or_none(hint: Any) -> Optional[HintSign]: ''' **Sign** (i.e., :class:`HintSign` instance) uniquely identifying the passed PEP-compliant type hint if PEP-compliant *or* ``None`` otherwise (i.e., if this hint is *not* PEP-compliant). This getter function associates the passed hint with a public attribute of the :mod:`typing` module effectively acting as a superclass of this hint and thus uniquely identifying the "type" of this hint in the broadest sense of the term "type". These attributes are typically *not* actual types, as most actual :mod:`typing` types are private, fragile, and prone to extreme violation (or even removal) between major Python versions. Nonetheless, these attributes are sufficiently unique to enable callers to distinguish between numerous broad categories of :mod:`typing` behaviour and logic. Specifically, if this hint is: * A :pep:`585`-compliant **builtin** (e.g., C-based type hint instantiated by subscripting either a concrete builtin container class like :class:`list` or :class:`tuple` *or* an abstract base class (ABC) declared by the :mod:`collections.abc` submodule like :class:`collections.abc.Iterable` or :class:`collections.abc.Sequence`), this function returns ::class:`beartype.cave.HintGenericSubscriptedType`. * A **generic** (i.e., subclass of the :class:`typing.Generic` abstract base class (ABC)), this function returns :class:`HintSignGeneric`. Note this includes :pep:`544`-compliant **protocols** (i.e., subclasses of the :class:`typing.Protocol` ABC), which implicitly subclass the :class:`typing.Generic` ABC as well. * A **forward reference** (i.e., string or instance of the concrete :class:`typing.ForwardRef` class), this function returns :class:`HintSignTypeVar`. * A **type variable** (i.e., instance of the concrete :class:`typing.TypeVar` class), this function returns :class:`HintSignTypeVar`. * Any other class, this function returns that class as is. * Anything else, this function returns the unsubscripted :mod:`typing` attribute dynamically retrieved by inspecting this hint's **object representation** (i.e., the non-human-readable string returned by the :func:`repr` builtin). This getter is memoized for efficiency. Motivation ---------- Both :pep:`484` and the :mod:`typing` module implementing :pep:`484` are functionally deficient with respect to their public APIs. Neither provide external callers any means of deciding the categories of arbitrary PEP-compliant type hints. For example, there exists no general-purpose means of identifying a parametrized subtype (e.g., ``typing.List[int]``) as a parametrization of its unparameterized base type (e.g., ``type.List``). Thus this function, which "fills in the gaps" by implementing this oversight. Parameters ---------- hint : object Type hint to be inspected. Returns ------- dict Sign uniquely identifying this hint. Raises ------ BeartypeDecorHintPepException If this hint is *not* PEP-compliant. Examples -------- >>> import typing >>> from beartype._util.hint.pep.utilpepget import ( ... get_hint_pep_sign_or_none) >>> get_hint_pep_sign_or_none(typing.Any) typing.Any >>> get_hint_pep_sign_or_none(typing.Union[str, typing.Sequence[int]]) typing.Union >>> T = typing.TypeVar('T') >>> get_hint_pep_sign_or_none(T) HintSignTypeVar >>> class Genericity(typing.Generic[T]): pass >>> get_hint_pep_sign_or_none(Genericity) HintSignGeneric >>> class Duplicity(typing.Iterable[T], typing.Container[T]): pass >>> get_hint_pep_sign_or_none(Duplicity) HintSignGeneric ''' # ..................{ IMPORTS }.................. # Avoid circular import dependencies. from beartype._util.hint.utilhintget import get_hint_repr # ..................{ SYNOPSIS }.................. # For efficiency, this tester identifies the sign of this type hint with # multiple phases performed in descending order of average time complexity # (i.e., expected efficiency). # # Note that we intentionally avoid validating this type hint to be # PEP-compliant (e.g., by calling the die_unless_hint_pep() validator). # Why? Because this getter is the lowest-level hint validation function # underlying all higher-level hint validation functions! Calling the latter # here would thus induce infinite recursion, which would be very bad. # ..................{ PHASE ~ classname }.................. # This phase attempts to map from the fully-qualified classname of this # hint to a sign identifying *ALL* hints that are instances of that class. # # Since the "object.__class__.__qualname__" attribute is both guaranteed to # exist and be efficiently accessible for all hints, this phase is the # fastest and thus performed first. Although this phase identifies only a # small subset of hints, those hints are extremely common. # # More importantly, some of these classes are implemented as maliciously # masquerading as other classes entirely -- including __repr__() methods # synthesizing erroneous machine-readable representations. To avoid false # positives, this phase *MUST* thus be performed before repr()-based tests # regardless of efficiency concerns: e.g., # # Under Python >= 3.10: # >>> import typing # >>> bad_guy_type_hint = typing.NewType('List', bool) # >>> bad_guy_type_hint.__module__ = 'typing' # >>> repr(bad_guy_type_hint) # typing.List # <---- this is genuine bollocks # # Likewise, some of these classes define __repr__() methods prefixed by the # machine-readable representations of their children. Again, to avoid false # positives, this phase *MUST* thus be performed before repr()-based tests # regardless of efficiency concerns: e.g., # # Under Python >= 3.10: # >>> repr(tuple[str, ...] | bool) # tuple[str, ...] | bool # <---- this is fine but *NOT* a tuple! # Class of this hint. hint_type = hint.__class__ #FIXME: Is this actually the case? Do non-physical classes dynamically #defined at runtime actually define these dunder attributes as well? # Fully-qualified name of this class. Note that *ALL* classes are # guaranteed to define the dunder attributes accessed here. hint_type_name = f'{hint_type.__module__}.{hint_type.__qualname__}' # Sign identifying this hint if this hint is identifiable by its classname # *OR* "None" otherwise. hint_sign = HINT_TYPE_NAME_TO_SIGN.get(hint_type_name) # If this hint is identifiable by its classname, return this sign. if hint_sign: return hint_sign # Else, this hint is *NOT* identifiable by its classname. # ..................{ PHASE ~ repr : str }.................. # This phase attempts to map from the unsubscripted machine-readable # representation of this hint to a sign identifying *ALL* hints of that # representation. # # Since doing so requires both calling the repr() builtin on this hint # *AND* munging the string returned by that builtin, this phase is # significantly slower than the prior phase and thus *NOT* performed first. # Although slow, this phase identifies the largest subset of hints. # Machine-readable representation of this hint. hint_repr = get_hint_repr(hint) # Parse this representation into: # * "hint_repr_prefix", the substring of this representation preceding the # first "[" delimiter if this representation contains that delimiter *OR* # this representation as is otherwise. # * "hint_repr_subscripted", the "[" delimiter if this representation # contains that delimiter *OR* the empty string otherwise. # # Note that the str.partition() method has been profiled to be the # optimally efficient means of parsing trivial prefixes like these. hint_repr_prefix, hint_repr_subscripted, _ = hint_repr.partition('[') # Sign identifying this possibly unsubscripted hint if this hint is # identifiable by its possibly unsubscripted representation *OR* "None". hint_sign = HINT_REPR_PREFIX_ARGS_0_OR_MORE_TO_SIGN.get(hint_repr_prefix) # If this hint is identifiable by its possibly unsubscripted # representation, return this sign. if hint_sign: return hint_sign # Else, this hint is *NOT* identifiable by its possibly unsubscripted # representation. # # If this representation (and thus this hint) is subscripted... elif hint_repr_subscripted: # Sign identifying this necessarily subscripted hint if this hint is # identifiable by its necessarily subscripted representation *OR* # "None" otherwise. hint_sign = HINT_REPR_PREFIX_ARGS_1_OR_MORE_TO_SIGN.get( hint_repr_prefix) # If this hint is identifiable by its necessarily subscripted # representation, return this sign. if hint_sign: return hint_sign # Else, this hint is *NOT* identifiable by its necessarily subscripted # representation. # # If this hint is inconsistent with respect to PEP 604-style new unions, # # raise an exception. Although awkward, this is ultimately the ideal # # location for this validation. Why? Because this validation: # # * *ONLY* applies to hints permissible as items of PEP 604-compliant # # new unions; this means classes and subscripted generics. If this # # hint is identifiable by its classname, this hint is neither a class # # *NOR* subscripted generic. Since this hint is *NOT* identifiable by # # its classname, however, this hint could still be either a class *OR* # # subscripted generic. It's best not to ask. # # * Does *NOT* apply to well-known type hints detected above (e.g., # # those produced by Python itself, the standard library, and # # well-known third-party type hint factories), which are all # # guaranteed to be consistent with respect to PEP 604. # die_if_hint_pep604_inconsistent(hint) # Else, this representation (and thus this hint) is unsubscripted. # ..................{ PHASE ~ repr : trie }.................. # This phase attempts to (in order): # # 1. Split the unsubscripted machine-readable representation of this hint on # the "." delimiter into an iterable of module names. # 2. Iteratively look up each such module name in a trie mapping from these # names to a sign identifying *ALL* hints in that module. # # Note that: # * This phase is principally intended to ignore PEP-noncompliant type hints # defined by third-party packages in an efficient and robust manner. Well, # reasonably efficient and robust anyway. *sigh* # * This phase must be performed *BEFORE* the subsequent phase that detects # generics. Generics defined in modules mapped by tries should # preferentially be identified as their module-specific signs rather than # as generics. (See the prior note.) # # Since doing so requires splitting a string and iterating over substrings, # this phase is significantly slower than prior phases and thus performed # almost last. Since this phase identifies an extremely small subset of # hints, efficiency is (mostly) incidental. # Iterable of module names split from the unsubscripted machine-readable # representation of this hint. For example, doing so splits # 'pandera.typing.DataFrame[DataFrameSchema()]' into # '("pandera", "typing", "DataFrame",)'. hint_repr_module_names = hint_repr_prefix.split('.') # Possibly nested trie describing the current module name in this iterable. hint_repr_module_name_trie = HINT_REPR_PREFIX_TRIE_ARGS_0_OR_MORE_TO_SIGN # For each module name in this iterable... for hint_repr_module_name in hint_repr_module_names: # Possibly nested trie describing this module name in this iterable. hint_repr_module_name_trie = hint_repr_module_name_trie.get( # type: ignore hint_repr_module_name) # If this trie does *NOT* exist, this module is *NOT* mapped to a sign. # In this case, immediately halt iteration. if hint_repr_module_name_trie is None: break # Else, this trie exists. In this case, this module *MIGHT* be mapped to # a sign. Further inspection is required, however. # # If this trie is actually a target sign, this module maps to this sign. # In this case, return this sign. elif hint_repr_module_name_trie.__class__ is HintSign: return hint_repr_module_name_trie # type: ignore[return-value] # Else, this trie is another nested trie. In this case, continue # iterating deeper into this trie. # Else, this hint is *NOT* identified by a trie of module names. # ..................{ PHASE ~ manual }.................. # This phase attempts to manually identify the signs of all hints *NOT* # efficiently identifiably by the prior phases. # # For minor efficiency gains, the following tests are intentionally ordered # in descending likelihood of a match. # If this hint is a PEP 484- or 585-compliant generic (i.e., user-defined # class superficially subclassing at least one PEP 484- or 585-compliant # type hint), return that sign. However, note that: # * Generics *CANNOT* be detected by the general-purpose logic performed # above, as the "typing.Generic" ABC does *NOT* define a __repr__() # dunder method returning a string prefixed by the "typing." substring. # Ergo, we necessarily detect generics with an explicit test instead. # * *ALL* PEP 484-compliant generics and PEP 544-compliant protocols are # guaranteed by the "typing" module to subclass this ABC regardless of # whether those generics originally did so explicitly. How? By type # erasure, the gift that keeps on giving: # >>> import typing as t # >>> class MuhList(t.List): pass # >>> MuhList.__orig_bases__ # (typing.List) # >>> MuhList.__mro__ # (__main__.MuhList, list, typing.Generic, object) # * *NO* PEP 585-compliant generics subclass this ABC unless those generics # are also either PEP 484- or 544-compliant. Indeed, PEP 585-compliant # generics subclass *NO* common superclass. # * Generics are *NOT* necessarily classes, despite originally being # declared as classes. Although *MOST* generics are classes, some are # shockingly *NOT*: e.g., # >>> from typing import Generic, TypeVar # >>> S = TypeVar('S') # >>> T = TypeVar('T') # >>> class MuhGeneric(Generic[S, T]): pass # >>> non_class_generic = MuhGeneric[S, T] # >>> isinstance(non_class_generic, type) # False # # Ergo, the "typing.Generic" ABC uniquely identifies many but *NOT* all # generics. While non-ideal, the failure of PEP 585-compliant generics to # subclass a common superclass leaves us with little alternative. if is_hint_pep484585_generic(hint): return HintSignGeneric # Else, this hint is *NOT* a PEP 484- or 585-compliant generic. # # If this hint is a PEP 589-compliant typed dictionary, return that sign. elif is_hint_pep589(hint): return HintSignTypedDict # Else, this hint is *NOT* a PEP 589-compliant typed dictionary. # # If the active Python interpreter targets Python < 3.10 (and thus defines # PEP 484-compliant "NewType" type hints as closures returned by that # function that are sufficiently dissimilar from all other type hints to # require unique detection) *AND* this hint is such a hint, return the # corresponding sign. # # Note that these hints *CANNOT* be detected by the general-purpose logic # performed above, as the __repr__() dunder methods of the closures created # and returned by the NewType() closure factory function return a standard # representation rather than a string prefixed by "typing.": e.g., # >>> import typing as t # >>> repr(t.NewType('FakeStr', str)) # '.new_type at 0x7fca39388050>' elif IS_PYTHON_AT_MOST_3_9 and is_hint_pep484_newtype_pre_python310(hint): return HintSignNewType # ..................{ ERROR }.................. # Else, this hint is unrecognized. In this case, this hint is of unknown # third-party origin and provenance. # If this hint is inconsistent with respect to PEP 604-style new unions, # raise an exception. Although awkward, this is ultimately the ideal context # for this validation. Why? Because this validation: # * *ONLY* applies to hints permissible as items of PEP 604-compliant new # unions; this means classes and subscripted builtins. If this hint is # identifiable by its classname, this hint is neither a class *NOR* a # subscripted builtin. Since this hint is *NOT* identifiable by its # classname, however, this hint could still be either a class *OR* # subscripted builtin. It's best not to ask. # * Does *NOT* apply to well-known type hints detected above (e.g., those # produced by Python itself, the standard library, and well-known # third-party type hint factories), which are all guaranteed to be # consistent with respect to PEP 604. die_if_hint_pep604_inconsistent(hint) # Else, this hint is consistent with respect to PEP 604-style new unions. #FIXME: Unit test us up, please. # If this hint is an unrecognized subscripted builtin type hint (i.e., # C-based type hint instantiated by subscripting a pure-Python origin class # unrecognized by @beartype and thus PEP-noncompliant), return this sign. # Examples include "os.PathLike[...]" and "weakref.weakref[...]" type hints. # # This is a last-ditch fallback preferable to merely returning "None", which # conveys substantially less semantics and would imply this object to be an # isinstanceable class, which subscripted builtin type hints are *NOT*. if is_hint_pep585_builtin_subscripted(hint): return HintSignPep585BuiltinSubscriptedUnknown # Else, this hint is *NOT* an unrecognized subscripted builtin type hint. # Return "None". return None # ....................{ GETTERS ~ origin }.................... def get_hint_pep_origin_or_none(hint: Any) -> Optional[Any]: ''' **Unsafe origin object** (i.e., arbitrary object originating the passed PEP-compliant type hint but *not* necessarily an isinstanceable class such that all objects satisfying this hint are instances of this class) originating this hint if this hint originates from an object *or* :data:`None` otherwise (i.e., if this hint originates from *no* such object). This getter is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Caveats ------- **The high-level** :func:`get_hint_pep_origin_type_isinstanceable` getter should always be called in lieu of this low-level function.** Whereas the former is guaranteed to return either an isinstanceable class or :data:`None`, this getter enjoys no such guarantees and instead returns an arbitrary object that may or may not actually be an instanceable class. If this getter *must* be called, **this getter should always be called in lieu of attempting to directly access the low-level** ``__origin__`` **dunder attribute.** Various :mod:`typing` objects either fail to define this attribute or define this attribute non-orthogonally, including objects: * Failing to define this attribute altogether (e.g., :attr:`typing.Any`, :attr:`typing.NoReturn`). * Defining this attribute to be their unsubscripted :mod:`typing` type hint factories (e.g., :attr:`typing.Optional`, :attr:`typing.Union`). * Defining this attribute to be their actual origin types. Since the :mod:`typing` module neither guarantees the existence of this attribute nor imposes a uniform semantic on this attribute when defined, that attribute is *not* safely directly accessible. Thus this getter, which "fills in the gaps" by implementing this oversight. Parameters ---------- hint : object Object to be inspected. Returns ------- Optional[Any] Either: * If this hint originates from an arbitrary object, that object. * Else, :data:`None`. Examples -------- .. code-block:: pycon >>> import typing >>> from beartype._util.hint.pep.utilpepget import ( ... get_hint_pep_origin_or_none) # This is sane. >>> get_hint_pep_origin_or_none(typing.List) list >>> get_hint_pep_origin_or_none(typing.List[int]) list >>> get_hint_pep_origin_or_none(typing.Union) None >>> get_hint_pep_origin_or_none(typing.Union[int]) None # This is insane. >>> get_hint_pep_origin_or_none(typing.Union[int, str]) Union # This is crazy. >>> typing.Union.__origin__ AttributeError: '_SpecialForm' object has no attribute '__origin__' # This is balls crazy. >>> typing.Union[int].__origin__ AttributeError: type object 'int' has no attribute '__origin__' # This is balls cray-cray -- the ultimate evolution of crazy. >>> typing.Union[int, str].__origin__ typing.Union ''' # Return this hint's origin object if any *OR* "None" otherwise. return getattr(hint, '__origin__', None) # ....................{ GETTERS ~ origin : type }.................... #FIXME: Unit test us up, please. def get_hint_pep_origin_type( # Mandatory parameters. hint: object, # Optional parameters. #FIXME: This should probably be a new "BeartypeDecorHintPepOriginException" #type, instead. But it's unclear whether users will even ever see this #exception. So, for now, laziness prevails. Huzzah! *sigh* exception_cls: TypeException = _BeartypeUtilTypeException, exception_prefix: str = '', ) -> type: ''' **Origin type** (i.e., class such that *all* objects satisfying the passed PEP-compliant type hint are instances of this class) originating this hint if this hint originates from such a type *or* raise an exception otherwise (i.e., if this hint does *not* originate from such a type). This getter is memoized for efficiency. Parameters ---------- hint : object Type hint to be inspected. exception_cls : TypeException, optional Type of exception to be raised in the event of a fatal error. Defaults to :exc:`._BeartypeUtilTypeException`. exception_prefix : str, optional Human-readable substring prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- type Type originating this hint. Raises ------- exception_cls If this hint either: * Does *not* originate from another object. * Originates from an object that is *not* a type. See Also -------- :func:`.get_hint_pep_origin_or_none` Further details. ''' # Origin type originating this hint if any *OR* "None" otherwise. hint_origin = get_hint_pep_origin_type_or_none(hint) # If *NO* origin type originates this hint... if hint_origin is None: assert isinstance(exception_cls, type), ( f'{exception_cls} not exception type.') assert isinstance(exception_prefix, str), ( f'{exception_prefix} not string.') # Origin non-type originating this hint if any *OR* "None" otherwise. hint_origin = get_hint_pep_origin_or_none(hint) # If this hint does *NOT* originate from another object, raise an # appropriate exception. if hint_origin is None: raise exception_cls( f'{exception_prefix}type hint {repr(hint)} ' f'originates from no other object.' ) # Else, this hint originates from another object. By definition, this # object *CANNOT* be a type. # Raise an appropriate exception. raise exception_cls( f'{exception_prefix}type hint {repr(hint)} ' f'originates from non-type {repr(hint_origin)}.' ) # Else, this origin type originates this hint. # Return this origin type. return hint_origin #FIXME: Unit test us up, please. @callable_cached def get_hint_pep_origin_type_or_none(hint: Any) -> Optional[type]: ''' **Origin type** (i.e., class such that *all* objects satisfying the passed PEP-compliant type hint are instances of this class) originating this hint if this hint originates from such a type *or* :data:`None` otherwise (i.e., if this hint does *not* originate from such a type). This getter is memoized for efficiency. Caveats ------- **This high-level getter should always be called in lieu of the low-level** :func:`get_hint_pep_origin_or_none` **getter on attempting to directly access the low-level** ``__origin__`` **dunder attribute.** Parameters ---------- hint : object Type hint to be inspected. Returns ------- Optional[type] Either: * If this hint originates from a type, that type. * Else, :data:`None`. See Also -------- :func:`.get_hint_pep_origin_or_none` Further details. ''' # Origin type originating this hint if any *OR* "None" otherwise, # initialized to the arbitrary object set as the "hint.__origin__" dunder # attribute if this hint defines that attribute. hint_origin: Optional[type] = get_hint_pep_origin_or_none(hint) # type: ignore[assignment] # If this origin is *NOT* a type... # # Ideally, this attribute would *ALWAYS* be a type for all possible # PEP-compliant type hints. For unknown reasons, type hint factories defined # by the standard "typing" module often set their origins to those same type # hint factories, despite those factories *NOT* being types. Why? Frankly, # we have no idea and neither does anyone else. Behold, true horror: # >>> import typing # >>> typing.Literal[1, 2].__origin__ # typing.Literal # <-- do you even know what you are doing, python? # >>> typing.Optional[int].__origin__ # typing.Union # <-- wut? this is insane, python. if not isinstance(hint_origin, type): # Default this origin type to either... hint_origin = ( # If this hint is itself a type, this hint could be euphemistically # said to originate from "itself." In this case, this hint. # # Look. Just go with it. We wave our hands in the air. hint if isinstance(hint, type) else # Else, this hint is *NOT* a type. Default to "None". No choice, yo! None ) # Else, this origin is a type. # Return this origin type. return hint_origin #FIXME: Is this even required or desired anymore? Can't we just replace all #calls to this frankly non-ideal getter with calls to a dramatically superior #get_hint_pep_origin_type() getter? Excise us up, please. def get_hint_pep_origin_type_isinstanceable(hint: object) -> type: ''' **Isinstanceable origin type** (i.e., class passable as the second argument to the :func:`isinstance` builtin such that *all* objects satisfying the passed PEP-compliant type hint are instances of this class) originating this hint if this hint originates from such a type *or* raise an exception otherwise (i.e., if this hint does *not* originate from such a type). This getter is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object Type hint to be inspected. Returns ------- type Standard origin type originating this hint. Raises ------ BeartypeDecorHintPepException If this hint does *not* originate from a standard origin type. See Also -------- :func:`get_hint_pep_origin_type_isinstanceable_or_none` Related getter. ''' # Origin type originating this object if any *OR* "None" otherwise. hint_origin_type = get_hint_pep_origin_type_isinstanceable_or_none(hint) # If this type does *NOT* exist, raise an exception. if hint_origin_type is None: raise BeartypeDecorHintPepException( f'Type hint {repr(hint)} not isinstanceable (i.e., does not ' f'originate from isinstanceable class).' ) # Else, this type exists. # Return this type. return hint_origin_type #FIXME: Is this even required or desired anymore? Can't we just replace all #calls to this frankly non-ideal getter with calls to the dramatically superior #get_hint_pep_origin_type_or_none() getter? Excise us up, please. def get_hint_pep_origin_type_isinstanceable_or_none( hint: Any) -> Optional[type]: ''' **Standard origin type** (i.e., isinstanceable class declared by Python's standard library such that *all* objects satisfying the passed PEP-compliant type hint are instances of this class) originating this hint if this hint originates from such a type *or* :data:`None` otherwise (i.e., if this hint does *not* originate from such a type). This getter is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Caveats ------- **This high-level getter should always be called in lieu of the low-level** :func:`get_hint_pep_origin_or_none` **getter or attempting to directly access the low-level** ``__origin__`` **dunder attribute.** Parameters ---------- hint : object Object to be inspected. Returns ------- Optional[type] Either: * If this hint originates from a standard origin type, that type. * Else, :data:`None`. See Also -------- :func:`get_hint_pep_origin_type_isinstanceable` Related getter. :func:`get_hint_pep_origin_or_none` Further details. ''' # Sign uniquely identifying this hint if any *OR* "None" otherwise. hint_sign = get_hint_pep_sign_or_none(hint) # Return either... return ( # If this sign originates from an origin type, that type; get_hint_pep_origin_or_none(hint) if hint_sign in HINT_SIGNS_ORIGIN_ISINSTANCEABLE else # Else, "None". None ) # ....................{ PRIVATE ~ args }.................... _HINT_ARGS_EMPTY_TUPLE = ((),) ''' Tuple containing only the empty tuple, to be returned from the :func:`get_hint_pep_args` getter when passed either: * A :pep:`585`-compliant type hint subscripted by the empty tuple (e.g., ``tuple[()]``). * A :pep:`484`-compliant type hint subscripted by the empty tuple (e.g., ``typing.Tuple[()]``) under Python >= 3.11, which applied the :pep:`585` approach throughout the :mod:`typing` module. ''' beartype-0.18.5/beartype/_util/hint/pep/utilpepreduce.py000066400000000000000000000072161461113517100233270ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **PEP-compliant type hint reducer** (i.e., callable converting a PEP-compliant type hint satisfying various constraints into another PEP-compliant type hint) utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... # from beartype._data.hint.datahinttyping import ( # Pep484TowerComplex, # Pep484TowerFloat, # ) from beartype._conf.confcls import BeartypeConf from beartype._util.hint.utilhinttest import die_unless_hint # ....................{ REDUCERS }.................... def reduce_hint_pep_unsigned( hint: object, conf: BeartypeConf, exception_prefix: str, **kwargs ) -> object: ''' Reduce the passed **unsigned PEP-compliant type hint** (i.e., type hint compliant with standards but identified by *no* sign, implying this hint to almost certainly be an isinstanceable type) if this hint satisfies various conditions to another (possibly signed) PEP-compliant type hint. Specifically: * If the passed configuration enables support for the :pep:`484`-compliant implicit numeric tower *and* this hint is: * The builtin :class:`float` type, this reducer expands this type to the ``float | int`` union of types. * The builtin :class:`complex` type, this reducer expands this type to the ``complex | float | int`` union of types. This reducer is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object Final type hint to be reduced. exception_prefix : str Human-readable label prefixing the representation of this object in the exception message. All remaining passed arguments are silently ignored. Returns ------- object PEP-compliant type hint reduced from this... PEP-compliant type hint. ''' # FIXME: Preserved in perpetuity. Although currently unused, this logic will # probably be desired again at some point. *shrug* # assert isinstance(conf, BeartypeConf), f'{repr(conf)} not configuration.' # # # If... # if ( # # This configuration enables support for the PEP 484-compliant # # implicit numeric tower *AND*... # conf.is_pep484_tower and # # This hint is either the builtin "float" or "complex" classes # # governed by this tower... # (hint is float or hint is complex) # # Then expand this hint to the corresponding numeric tower. # ): # # Expand this hint to match... # hint = ( # # If this hint is the builtin "float" class, both the builtin # # "float" and "int" classes; # Pep484TowerFloat # if hint is float else # # Else, this hint is the builtin "complex" class by the above # # condition; in this case, the builtin "complex", "float", and # # "int" classes. # Pep484TowerComplex # ) # # Else, this hint is truly unidentifiable. # else: # If this hint is *NOT* a valid type hint, raise an exception. # # Note this function call is effectively memoized and thus fast. die_unless_hint(hint=hint, exception_prefix=exception_prefix) # Else, this hint is a valid type hint. # Return this hint as is unmodified. return hint beartype-0.18.5/beartype/_util/hint/pep/utilpeptest.py000066400000000000000000001014601461113517100230330ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **PEP-compliant type hint tester** (i.e., callable validating an arbitrary object to be a PEP-compliant type hint) utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import ( BeartypeDecorHintPepException, BeartypeDecorHintPepUnsupportedException, BeartypeDecorHintPep484Exception, ) from beartype.typing import ( Dict, NoReturn, ) from beartype._data.hint.datahinttyping import TypeException from beartype._data.hint.pep.sign.datapepsigncls import HintSign from beartype._data.hint.pep.sign.datapepsigns import ( HintSignAnnotated, HintSignGeneric, HintSignOptional, HintSignNewType, HintSignPep695TypeAlias, HintSignProtocol, HintSignTypeVar, HintSignUnion, ) from beartype._data.hint.pep.sign.datapepsignset import ( HINT_SIGNS_SUPPORTED, HINT_SIGNS_TYPE_MIMIC, ) from beartype._data.module.datamodtyping import TYPING_MODULE_NAMES from beartype._util.cache.utilcachecall import callable_cached from beartype._util.hint.pep.proposal.pep484.utilpep484 import ( is_hint_pep484_typevar_ignorable, is_hint_pep484585_generic_ignorable, is_hint_pep484604_union_ignorable, ) from beartype._util.hint.pep.proposal.pep484.utilpep484newtype import ( is_hint_pep484_newtype_ignorable) from beartype._util.hint.pep.proposal.utilpep544 import is_hint_pep544_ignorable from beartype._util.hint.pep.proposal.utilpep593 import is_hint_pep593_ignorable from beartype._util.hint.pep.proposal.utilpep695 import is_hint_pep695_ignorable from beartype._util.module.utilmodget import get_object_module_name_or_none from beartype._util.utilobject import get_object_type_unless_type from collections.abc import Callable # ....................{ EXCEPTIONS }.................... def die_if_hint_pep( # Mandatory parameters. hint: object, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintPepException, exception_prefix: str = '', ) -> None: ''' Raise an exception of the passed type if the passed object is a **PEP-compliant type hint** (i.e., :mod:`beartype`-agnostic annotation compliant with annotation-centric PEPs). This validator is effectively (but technically *not*) memoized. See the :func:`beartype._util.hint.utilhinttest.die_unless_hint` validator. Parameters ---------- hint : object Object to be validated. exception_cls : Type[Exception], optional Type of the exception to be raised by this function. Defaults to :exc:`.BeartypeDecorHintPepException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Raises ------ exception_cls If this object is a PEP-compliant type hint. ''' # If this hint is PEP-compliant... if is_hint_pep(hint): assert isinstance(exception_cls, type), ( f'{repr(exception_cls)} not type.') assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') # Raise an exception of this class. raise exception_cls( f'{exception_prefix}type hint {repr(hint)} is PEP-compliant ' f'(e.g., rather than isinstanceable class).' ) def die_unless_hint_pep( # Mandatory parameters. hint: object, # Optional parameters. exception_cls: TypeException = BeartypeDecorHintPepException, exception_prefix: str = '', ) -> None: ''' Raise an exception unless the passed object is a **PEP-compliant type hint** (i.e., :mod:`beartype`-agnostic annotation compliant with annotation-centric PEPs). This validator is effectively (but technically *not*) memoized. See the :func:`beartype._util.hint.utilhinttest.die_unless_hint` validator. Parameters ---------- hint : object Object to be validated. exception_cls : Type[Exception], optional Type of the exception to be raised by this function. Defaults to :class:`.BeartypeDecorHintPepException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Raises ------ exception_cls If this object is *not* a PEP-compliant type hint. ''' # If this hint is *NOT* PEP-compliant, raise an exception. if not is_hint_pep(hint): assert isinstance(exception_cls, type), ( f'{repr(exception_cls)} not type.') assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') raise exception_cls( f'{exception_prefix}type hint {repr(hint)} not PEP-compliant.') # ....................{ EXCEPTIONS ~ supported }.................... #FIXME: *DANGER.* This function makes beartype more fragile. Instead, refactor #all or most calls to this function into calls to the #warn_if_hint_pep_unsupported() function; then, consider excising this as well #as exception classes (e.g., "BeartypeDecorHintPepUnsupportedException"). def die_if_hint_pep_unsupported( # Mandatory parameters. hint: object, # Optional parameters. exception_prefix: str = '', ) -> None: ''' Raise an exception if the passed object is a **PEP-compliant unsupported type hint** (i.e., :mod:`beartype`-agnostic annotation compliant with annotation-centric PEPs currently *not* supported by the :func:`beartype.beartype` decorator). This validator is effectively (but technically *not*) memoized. See the :func:`beartype._util.hint.utilhinttest.die_unless_hint` validator. Caveats ------- **This validator only shallowly validates this object.** If this object is a subscripted PEP-compliant type hint (e.g., ``Union[str, List[int]]``), this validator ignores all subscripted arguments (e.g., ``List[int]``) on this hint and may thus return false positives for hints that are directly supported but whose subscripted arguments are not. To deeply validate this object, iteratively call this validator during a recursive traversal (such as a breadth-first search) over each subscripted argument of this object. Parameters ---------- hint : object Object to be validated. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Raises ------ BeartypeDecorHintPepException If this object is *not* a PEP-compliant type hint. BeartypeDecorHintPepUnsupportedException If this object is a PEP-compliant type hint but is currently unsupported by the :func:`beartype.beartype` decorator. BeartypeDecorHintPep484Exception If this object is the PEP-compliant :attr:`typing.NoReturn` type hint, which is contextually valid in only a single use case and thus supported externally by the :mod:`beartype._decor.wrap.wrapmain` submodule rather than with general-purpose automation. ''' # If this object is a supported PEP-compliant type hint, reduce to a noop. # # Note that this memoized call is intentionally passed positional rather # than keyword parameters to maximize efficiency. if is_hint_pep_supported(hint): return # Else, this object is *NOT* a supported PEP-compliant type hint. In this # case, subsequent logic raises an exception specific to the passed # parameters. # If this hint is *NOT* PEP-compliant, raise an exception. die_unless_hint_pep(hint=hint, exception_prefix=exception_prefix) assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') # Else, this hint is PEP-compliant. # # If this is the PEP 484-compliant "typing.NoReturn" type hint permitted # *ONLY* as a return annotation, raise an exception specific to this hint. if hint is NoReturn: raise BeartypeDecorHintPep484Exception( f'{exception_prefix}PEP 484 type hint "{repr(hint)}" ' f'invalid in this type hint context (i.e., ' f'"{repr(hint)}" valid only as non-nested return annotation).' ) # Else, this is any PEP-compliant type hint other than "typing.NoReturn". # In this case, raise a general-purpose exception. # # Note that, by definition, the sign uniquely identifying this hint *SHOULD* # be in the "HINT_SIGNS_SUPPORTED" set. Regardless of whether it is or not, # we raise a similar exception in either case. Ergo, there is *NO* practical # benefit to validating that expectation here. raise BeartypeDecorHintPepUnsupportedException( f'{exception_prefix}type hint {repr(hint)} ' f'currently unsupported by @beartype.' ) # ....................{ WARNINGS }.................... #FIXME: Unit test us up. #FIXME: Actually use us in place of die_if_hint_pep_unsupported(). #FIXME: Actually, it's unclear whether we still require or desire this. See #"_pephint" commentary for further details. # def warn_if_hint_pep_unsupported( # # Mandatory parameters. # hint: object, # # # Optional parameters. # exception_prefix: str = 'Annotated', # ) -> bool: # ''' # Return ``True`` and emit a non-fatal warning only if the passed object is a # **PEP-compliant unsupported type hint** (i.e., :mod:`beartype`-agnostic # annotation compliant with annotation-centric PEPs currently *not* supported # by the :func:`beartype.beartype` decorator). # # This validator is effectively (but technically *not*) memoized. See the # :func:`beartype._util.hint.utilhinttest.die_unless_hint` validator. # # Parameters # ---------- # hint : object # Object to be validated. # exception_prefix : Optional[str] # Human-readable label prefixing this object's representation in the # warning message emitted by this function. Defaults to the empty string. # # Returns # ---------- # bool # ``True`` only if this PEP-compliant type hint is currently supported by # that decorator. # # Raises # ---------- # BeartypeDecorHintPepException # If this object is *not* a PEP-compliant type hint. # # Warnings # ---------- # BeartypeDecorHintPepUnsupportedWarning # If this object is a PEP-compliant type hint currently unsupported by # that decorator. # ''' # # # True only if this object is a supported PEP-compliant type hint. # # # # Note that this memoized call is intentionally passed positional rather # # than keyword parameters to maximize efficiency. # is_hint_pep_supported_test = is_hint_pep_supported(hint) # # # If this object is an unsupported PEP-compliant type hint... # if not is_hint_pep_supported_test: # assert isinstance(exception_prefix, str), f'{repr(exception_prefix)} not string.' # # # If this hint is *NOT* PEP-compliant, raise an exception. # die_unless_hint_pep(hint=hint, exception_prefix=exception_prefix) # # # Else, this hint is PEP-compliant. In this case, emit a warning. # warn( # ( # f'{exception_prefix}PEP type hint {repr(hint)} ' # f'currently unsupported by @beartype.' # ), # BeartypeDecorHintPepUnsupportedWarning # ) # # # Return true only if this object is a supported PEP-compliant type hint. # return is_hint_pep_supported_test # ....................{ TESTERS }.................... def is_hint_pep(hint: object) -> bool: ''' :data:`True` only if the passed object is a **PEP-compliant type hint** (i.e., object either directly defined by the :mod:`typing` module *or* whose type subclasses one or more classes directly defined by the :mod:`typing` module). This tester is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Motivation ---------- Standard Python types allow callers to test for compliance with protocols, interfaces, and abstract base classes by calling either the :func:`isinstance` or :func:`issubclass` builtins. This is the well-established Pythonic standard for deciding conformance to an API. Insanely, :pep:`484` *and* the :mod:`typing` module implementing :pep:`484` reject community standards by explicitly preventing callers from calling either the :func:`isinstance` or :func:`issubclass` builtins on most but *not* all :pep:`484` objects and types. Moreover, neither :pep:`484` nor :mod:`typing` implement public APIs for testing whether arbitrary objects comply with :pep:`484` or :mod:`typing`. Thus this function, which "fills in the gaps" by implementing this laughably critical oversight. Parameters ---------- hint : object Object to be inspected. Returns ------- bool :data:`True` only if this object is a PEP-compliant type hint. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.utilpepget import ( get_hint_pep_sign_or_none) # Sign uniquely identifying this hint if this hint is PEP-compliant *OR* # "None" otherwise (i.e., if this hint is *NOT* PEP-compliant). hint_sign = get_hint_pep_sign_or_none(hint) # print(f'hint: {repr(hint)}; sign: {repr(hint_sign)}') # Return true *ONLY* if this hint is uniquely identified by a sign and thus # PEP-compliant. return hint_sign is not None #FIXME: Currently unused but preserved for posterity. *shrug* # def is_hint_pep_deprecated(hint: object) -> bool: # ''' # :data:`True` only if the passed PEP-compliant type hint is **deprecated** # (i.e., obsoleted by an equivalent PEP-compliant type hint standardized by a # more recently released PEP). # # This tester is intentionally *not* memoized (e.g., by the # ``callable_cached`` decorator), as this tester is currently *only* called at # test time from our test suite. # # Parameters # ---------- # hint : object # PEP-compliant type hint to be inspected. # # Returns # ------- # bool # :data:`True` only if this PEP-compliant type hint is deprecated. # ''' # # # Avoid circular import dependencies. # from beartype._util.hint.pep.utilpepget import get_hint_pep_sign # # # Sign uniquely identifying this hint. # hint_sign = get_hint_pep_sign(hint) # # # Return true only if either... # return ( # # This sign is that of an unconditionally deprecated type hint *OR*... # hint_sign in HINT_SIGNS_DEPRECATED or # # This is a PEP 484-compliant type hint (e.g., "typing.List[str]") # # conditionally deprecated by an equivalent PEP 585-compliant type hint # # (e.g., "list[str]") under Python >= 3.9. # # # # Note that, in this case, the sign of this hint does *NOT* convey # # enough metadata to ascertain whether this hint is deprecated. Ergo, a # # non-trivial tester dedicated to this discernment is required: e.g., # # * "list[str]" has the sign "HintSignList" but is *NOT* deprecated. # # * "typing.List[str]" has the sign "HintSignList" but is deprecated. # is_hint_pep484_deprecated(hint) # ) def is_hint_pep_ignorable(hint: object) -> bool: ''' :data:`True` only if the passed PEP-compliant type hint is **deeply ignorable** (i.e., shown to be ignorable only after recursively inspecting the contents of this hint). This tester is intentionally *not* memoized (e.g., by the ``callable_cached`` decorator), as this tester is only safely callable by the memoized parent :func:`beartype._util.hint.utilhinttest.is_hint_ignorable` tester. Parameters ---------- hint : object PEP-compliant type hint to be inspected. Returns ------- bool :data:`True` only if this PEP-compliant type hint is deeply ignorable. Warns ----- BeartypeDecorHintPepIgnorableDeepWarning If this object is a deeply ignorable PEP-compliant type hint. Why? Because deeply ignorable PEP-compliant type hints convey *no* meaningful semantics but superficially appear to do so. Consider ``Union[str, List[int], NewType('MetaType', Annotated[object, 53])]``, for example; this PEP-compliant type hint effectively reduces to :obj:`typing.Any` and thus conveys *no* meaningful semantics despite superficially appearing to do so. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.utilpepget import get_hint_pep_sign # print(f'Testing PEP hint {repr(hint)} deep ignorability...') # Sign uniquely identifying this hint. hint_sign = get_hint_pep_sign(hint) # Ignorer (i.e., callable testing whether this hint is ignorable) if an # ignorer for hints of this sign was registered *OR* "None" otherwise (i.e., # if *NO* ignorer was registered, in which case this hint is unignorable). is_hint_ignorable = _HINT_SIGN_TO_IS_HINT_IGNORABLE.get(hint_sign) # Return either... return ( # If an ignorer for hints of this sign was registered, the boolean # returned when passing this ignorer this hint); is_hint_ignorable(hint) if is_hint_ignorable else # Else, *NO* ignorer for hints of this sign was registered, implying # this hint to be unignorable. Return false. False ) @callable_cached def is_hint_pep_supported(hint: object) -> bool: ''' :data:`True` only if the passed object is a **PEP-compliant supported type hint** (i.e., :mod:`beartype`-agnostic annotation compliant with annotation-centric PEPs currently supported by the :func:`beartype.beartype` decorator). This tester is memoized for efficiency. Caveats ------- **This tester only shallowly inspects this object.** If this object is a subscripted PEP-compliant type hint (e.g., ``Union[str, List[int]]``), this tester ignores all subscripted arguments (e.g., ``List[int]``) on this hint and may thus return false positives for hints that are directly supported but whose subscripted arguments are not. To deeply inspect this object, iteratively call this tester during a recursive traversal over each subscripted argument of this object. Parameters ---------- hint : object Object to be inspected. Returns ------- bool :data:`True` only if this object is a supported PEP-compliant type hint. ''' # If this hint is *NOT* PEP-compliant, immediately return false. if not is_hint_pep(hint): return False # Else, this hint is PEP-compliant. # Avoid circular import dependencies. from beartype._util.hint.pep.utilpepget import get_hint_pep_sign # Sign uniquely identifying this hint. hint_sign = get_hint_pep_sign(hint) # Return true only if this sign is supported. return hint_sign in HINT_SIGNS_SUPPORTED # ....................{ TESTERS ~ typing }.................... #FIXME: Replace all hardcoded "'typing" strings throughout the codebase with #access of "TYPING_MODULE_NAMES" instead. We only see one remaining in: #* beartype._util.hint.pep.proposal.pep484.utilpep484.py #Thankfully, nobody really cares about generalizing this one edge case to #"testing_extensions", so it's mostly fine for various definitions of fine. @callable_cached def is_hint_pep_typing(hint: object) -> bool: ''' :data:`True` only if the passed object is an attribute of a **typing module** (i.e., module officially declaring attributes usable for creating PEP-compliant type hints accepted by both static and runtime type checkers). This tester is memoized for efficiency. Parameters ---------- hint : object Object to be inspected. Returns ------- bool :data:`True` only if this object is an attribute of a typing module. ''' # print(f'is_hint_pep_typing({repr(hint)}') # Avoid circular import dependencies. from beartype._util.hint.pep.utilpepget import ( get_hint_pep_sign_or_none) # Return true only if this hint is either... return ( # Any PEP-compliant type hint defined by a typing module (except those # maliciously masquerading as another type entirely) *OR*... get_object_module_name_or_none(hint) in TYPING_MODULE_NAMES or # Any PEP-compliant type hint defined by a typing module maliciously # masquerading as another type entirely. get_hint_pep_sign_or_none(hint) in HINT_SIGNS_TYPE_MIMIC ) def is_hint_pep_type_typing(hint: object) -> bool: ''' :data:`True` only if either the passed object is defined by a **typing module** (i.e., module officially declaring attributes usable for creating PEP-compliant type hints accepted by both static and runtime type checkers) if this object is a class *or* the class of this object is defined by a typing module otherwise (i.e., if this object is *not* a class). This tester is intentionally *not* memoized (e.g., by the :func:`.callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Parameters ---------- hint : object Object to be inspected. Returns ------- bool :data:`True` only if either: * If this object is a class, this class is defined by a typing module. * Else, the class of this object is defined by a typing module. ''' # This hint if this hint is a class *OR* this hint's class otherwise. hint_type = get_object_type_unless_type(hint) # print(f'pep_type_typing({repr(hint)}): {get_object_module_name(hint_type)}') # Return true only if this type is defined by a typing module. # # Note that this implementation could probably be reduced to the # leading portion of the body of the get_hint_pep_sign_or_none() # function testing this object's representation. While certainly more # compact and convenient than the current approach, that refactored # approach would also be considerably more fragile, failure-prone, and # subject to whimsical "improvements" in the already overly hostile # "typing" API. Why? Because the get_hint_pep_sign_or_none() function: # * Parses the machine-readable string returned by the __repr__() # dunder method of "typing" types. Since that string is *NOT* # standardized by PEP 484 or any other PEP, "typing" authors remain # free to violate this pseudo-standard in any manner and at any time # of their choosing. # * Suffers common edge cases for "typing" types whose __repr__() # dunder methods fail to comply with the non-standard implemented by # their sibling types. This includes the common "TypeVar" type. # * Calls this tester function to decide whether the passed object is a # PEP-compliant type hint or not before subjecting that object to # further introspection, which would clearly complicate implementing # this tester function in terms of that getter function. # # In contrast, the current approach only tests the standard # "__module__" dunder attribute and is thus significantly more robust # against whimsical destruction by "typing" authors. Note that there # might exist an alternate means of deciding this boolean, documented # here merely for completeness: # try: # isinstance(obj, object) # return False # except TypeError as type_error: # return str(type_error).endswith( # 'cannot be used with isinstance()') # # The above effectively implements an Aikido throw by using the fact # that "typing" types prohibit isinstance() calls against those types. # While clever (and deliciously obnoxious), the above logic: # * Requires catching exceptions in the common case and is thus *MUCH* # less efficient than the preferable approach implemented here. # * Assumes that *ALL* "typing" types prohibit such calls. Sadly, only # a proper subset of these types prohibit such calls. # * Assumes that those "typing" types that do prohibit such calls raise # exceptions with reliable messages across *ALL* Python versions. # # In short, there is no general-purpose clever solution. *sigh* return hint_type.__module__ in TYPING_MODULE_NAMES # ....................{ TESTERS ~ args }.................... #FIXME: Overkill. Replace directly with a simple test, please. # #Note that the corresponding unit test should be preserved, as that test is #essential to ensuring sanity across type hints and Python versions. def is_hint_pep_args(hint: object) -> bool: ''' :data:`True` only if the passed object is a **subscripted PEP-compliant type hint** (i.e., PEP-compliant type hint directly indexed by one or more objects). This tester is intentionally *not* memoized (e.g., by the :func:`.callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Caveats ------- **Callers should not assume that the objects originally subscripting this hint are still accessible.** Although *most* hints preserve their subscripted objects over their lifetimes, a small subset of edge-case hints erase those objects at subscription time. This includes: * :pep:`585`-compliant empty tuple type hints (i.e., ``tuple[()]``), which despite being explicitly subscripted erroneously erase that subscription at subscription time. This does *not* extend to :pep:`484`-compliant empty tuple type hints (i.e., ``typing.Tuple[()]``), which correctly preserve that subscripted empty tuple. Parameters ---------- hint : object Object to be inspected. Returns ------- bool :data:`True` only if this object is a subscripted PEP-compliant type hint. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.utilpepget import get_hint_pep_args # Return true only if this hint is subscripted by one or more arguments. return bool(get_hint_pep_args(hint)) # ....................{ TESTERS ~ typevars }.................... #FIXME: Overkill. Replace directly with a simple test, please. # #Note that the corresponding unit test should be preserved, as that test is #essential to ensuring sanity across type hints and Python versions. def is_hint_pep_typevars(hint: object) -> bool: ''' :data:`True` only if the passed object is a PEP-compliant type hint parametrized by one or more **type variables** (i.e., instances of the :class:`TypeVar` class). This tester detects both: * **Direct parametrizations** (i.e., cases in which this object itself is directly parametrized by type variables). * **Superclass parametrizations** (i.e., cases in which this object is indirectly parametrized by one or more superclasses of its class being directly parametrized by type variables). This tester is intentionally *not* memoized (e.g., by the :func:`callable_cached` decorator), as the implementation trivially reduces to an efficient one-liner. Semantics --------- **Generics** (i.e., PEP-compliant type hints whose classes subclass one or more public :mod:`typing` pseudo-superclasses) are often but *not* always typevared. For example, consider the untypevared generic: .. code-block:: pycon >>> from typing import List >>> class UntypevaredGeneric(List[int]): pass >>> UntypevaredGeneric.__mro__ (__main__.UntypevaredGeneric, list, typing.Generic, object) >>> UntypevaredGeneric.__parameters__ () Likewise, typevared hints are often but *not* always generic. For example, consider the typevared non-generic: .. code-block:: pycon >>> from typing import List, TypeVar >>> TypevaredNongeneric = List[TypeVar('T')] >>> type(TypevaredNongeneric).__mro__ (typing._GenericAlias, typing._Final, object) >>> TypevaredNongeneric.__parameters__ (~T,) Parameters ---------- hint : object Object to be inspected. Returns ------- bool :data:`True` only if this object is a PEP-compliant type hint parametrized by one or more type variables. Examples -------- .. code-block:: pycon >>> import typing >>> from beartype._util.hint.pep.utilpeptest import ( ... is_hint_pep_typevars) >>> T = typing.TypeVar('T') >>> class UserList(typing.List[T]): pass # Unparametrized type hint. >>> is_hint_pep_typevars(typing.List[int]) False # Directly parametrized type hint. >>> is_hint_pep_typevars(typing.List[T]) True # Superclass-parametrized type hint. >>> is_hint_pep_typevars(UserList) True ''' # Avoid circular import dependencies. from beartype._util.hint.pep.utilpepget import get_hint_pep_typevars # Return true only if this hint is parametrized by one or more type # variables, trivially detected by testing whether the tuple of all type # variables parametrizing this hint is non-empty. return bool(get_hint_pep_typevars(hint)) # ....................{ PRIVATE ~ dicts }.................... # Note that this type hints would ideally be defined with the mypy-specific # "callback protocol" pseudostandard, documented here: # https://mypy.readthedocs.io/en/stable/protocols.html#callback-protocols # # Doing so would enable static type-checkers to type-check that the values of # this dictionary are valid ignorer functions. Sadly, that pseudostandard is # absurdly strict to the point of practical uselessness. Attempting to conform # to that pseudostandard would require refactoring *ALL* ignorer functions to # explicitly define the same signature. However, we have intentionally *NOT* # done that. Why? Doing so would substantially increase the fragility of this # API by preventing us from readily adding and removing infrequently required # parameters (e.g., "cls_stack", "pith_name"). Callback protocols suck, frankly. _HINT_SIGN_TO_IS_HINT_IGNORABLE: Dict[HintSign, Callable] = { # ..................{ PEP 484 }.................. # Ignore *ALL* PEP 484-compliant "NewType"-style type aliases aliasing # ignorable type hints. HintSignNewType: is_hint_pep484_newtype_ignorable, # Ignore *ALL* PEP 484-compliant type variables. HintSignTypeVar: is_hint_pep484_typevar_ignorable, # ..................{ PEP (484|585) }.................. # Ignore *ALL* PEP 484- and 585-compliant "Generic[...]" subscriptions. HintSignGeneric: is_hint_pep484585_generic_ignorable, # ..................{ PEP (484|604) }.................. # Ignore *ALL* PEP 484- and 604-compliant unions subscripted by one or more # ignorable type hints. HintSignOptional: is_hint_pep484604_union_ignorable, HintSignUnion: is_hint_pep484604_union_ignorable, # ..................{ PEP 544 }.................. # Ignore *ALL* PEP 544-compliant "typing.Protocol[...]" subscriptions. HintSignProtocol: is_hint_pep544_ignorable, # ..................{ PEP 593 }.................. # Ignore *ALL* PEP 593-compliant "typing.Annotated[...]" type hints except # those indexed by one or more beartype validators. HintSignAnnotated: is_hint_pep593_ignorable, # ..................{ PEP 695 }.................. # Ignore *ALL* PEP 695-compliant type aliases aliasing ignorable type hints. HintSignPep695TypeAlias: is_hint_pep695_ignorable, } ''' Dictionary mapping from each sign uniquely identifying PEP-compliant type hints to that sign's **ignorer** (i.e., low-level function testing whether the passed type hint identified by that sign is deeply ignorable). Each value of this dictionary is expected to have a signature resembling: .. code-block:: python def is_hint_pep{pep_number}_ignorable(hint: object) -> bool: ... Note that: * Ignorers do *not* need to validate the passed type hint as being of the expected sign. By design, an ignorer is only ever passed a type hint of the expected sign. * Ignorers should *not* be memoized (e.g., by the `callable_cached`` decorator). Since the higher-level :func:`.is_hint_pep_ignorable` function that is the sole entry point to calling all lower-level ignorers is itself effectively memoized, ignorers themselves neither require nor benefit from memoization. ''' beartype-0.18.5/beartype/_util/hint/utilhintfactory.py000066400000000000000000000144321461113517100231170ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **type hint factories** (i.e., low-level classes and callables dynamically creating and returning PEP-compliant type hints, typically as a runtime fallback when the currently installed versions of the standard :mod:`typing` module and third-party :mod:`typing_extensions` modules do *not* officially support those factories). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import ( Generic, Type, ) from beartype._data.hint.datahinttyping import T from beartype._util.cache.utilcachecall import callable_cached # ....................{ METACLASSES }.................... class _TypeHintTypeFactoryMeta(type): ''' **Type hint type factory metaclass** (i.e., the root :class:`type` metaclass augmented with caching to memoize singleton instances of the :class:`TypeHintTypeFactory` class declared below). This metaclass is superior to the usual approach of implementing the singleton design pattern: overriding the :meth:`__new__` method of a singleton class to conditionally create a new instance of that class only if an instance has *not* already been created. Why? Because that approach unavoidably re-calls the :meth:`__init__` method of a previously initialized singleton instance on each instantiation of that class. Doing so is generally considered harmful. This metaclass instead guarantees that the :meth:`__init__` method of a singleton instance is only called exactly once on the first instantiation of that class. ''' # ..................{ INITIALIZERS }.................. @callable_cached def __call__(cls, type_factory: type) -> 'TypeHintTypeFactory': # type: ignore[override] ''' Instantiate the passed singleton class with the passed arbitrary type. Parameters ---------- cls : Type['TypeHintTypeFactory'] :class:`TypeHintTypeFactory` class to be instantiated. type_factory : type Arbitrary type to instantiate that class with. ''' # Create and return a new memoized singleton instance of the # "TypeHintTypeFactory" class specific to this arbitrary type. return super().__call__(type_factory) # ....................{ CLASSES }.................... class TypeHintTypeFactory(Generic[T], metaclass=_TypeHintTypeFactoryMeta): ''' **Type hint type factory** (i.e., high-level object unconditionally returning an arbitrary type when subscripted by any arbitrary object). This factory is principally intended to serve as a graceful runtime fallback when the currently installed versions of the standard :mod:`typing` module and third-party :mod:`typing_extensions` modules do *not* declare the desired PEP-compliant type hint factory. See the examples below. Instances of this class are implicitly memoized as singletons as a negligible space and time optimization that costs us nothing and gains us a negligible something. Examples -------- Consider the :pep:`647`-compliant :attr:`typing.TypeGuard` type hint factory, which is only available under Python >= 3.10 or from :mod:`typing_extensions` if optionally installed; if neither of those two conditions apply, this factory may be trivially used as a fake ``TypeGuard`` stand-in returning the builtin :class:`bool` type when subscripted -- exactly as advised by :pep:`647` itself: e.g., .. code-block: from beartype.typing import TYPE_CHECKING from beartype._util.hint.utilhintfactory import TypeHintTypeFactory from beartype._util.api.utilapityping import ( import_typing_attr_or_fallback) if TYPE_CHECKING: from typing_extensions import TypeGuard else: TypeGuard = import_typing_attr_or_fallback( 'TypeGuard', TypeHintTypeFactory(bool)) # This signature gracefully reduces to the following at runtime under # Python <= 3.10 if "typing_extensions" is *NOT* installed: # def is_obj_list(obj: object) -> bool: def is_obj_list(obj: object) -> TypeGuard[list]: return isinstance(obj, list) Attributes ---------- _type_factory : Type[T] Arbitrary type to be returned from the :meth:`__getitem__` method. ''' # ..................{ CLASS VARIABLES }.................. # Slot all instance variables defined on this object to minimize the time # complexity of both reading and writing variables across frequently called # @beartype decorations. Slotting has been shown to reduce read and write # costs by approximately ~10%, which is non-trivial. __slots__ = ( '_type_factory', ) # ..................{ INITIALIZERS }.................. def __init__(self, type_factory: Type[T]) -> None: ''' Initialize this type hint type factory. Parameters ---------- type_factory : Type[T] Arbitrary type to be returned from the :meth:`__getitem__` method. ''' assert isinstance(type_factory, type), f'{repr(type_factory)} not type.' # Classify all passed parameters. self._type_factory = type_factory # ..................{ DUNDERS }.................. def __getitem__(self, index: object) -> Type[T]: ''' Return the arbitrary type against which this type hint type factory was originally initialized when subscripted by the passed arbitrary object. Parameters ---------- index : object Arbitrary object. Although this is typically a PEP-compliant type hint, this factory imposes *no* constraints on this object. Returns ------- Type[T] Arbitrary type previously passed to the :meth:`__init__` method. ''' # Return this type, silently ignoring the passed object entirely. Hah! return self._type_factory beartype-0.18.5/beartype/_util/hint/utilhintget.py000066400000000000000000000037241461113517100222310ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **PEP-agnostic type hint getter utilities** (i.e., callables validating querying type hints supported by :mod:`beartype`, regardless of whether those hints comply with PEP standards or not). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._data.hint.pep.datapeprepr import HINTS_REPR_IGNORABLE_SHALLOW from beartype._util.cache.utilcachecall import callable_cached from beartype._util.hint.nonpep.utilnonpeptest import ( die_unless_hint_nonpep, is_hint_nonpep, ) from beartype._util.hint.pep.utilpeptest import ( die_if_hint_pep_unsupported, is_hint_pep, is_hint_pep_supported, ) # ....................{ TESTERS }.................... #FIXME: Call this getter everywhere we currently call "repr(hint*)", please. @callable_cached def get_hint_repr(hint: object) -> str: ''' **Representation** (i.e., machine-readable string returned by the :func:`repr` builtin when passed this hint) of this PEP-agnostic type hint. This getter is memoized for efficiency. Indeed, since the implementation of this getter trivially reduces to just ``repr(hint)``, memoization is the entire reason for the existence of this getter. Motivation ---------- **This getter should always be called to efficiently obtain the representation of any type hint.** The comparatively less efficient :func:`repr` builtin should *never* be called to do so. Parameters ---------- hint : object Type hint to be represented. Returns ---------- str Representation of this hint. ''' # Return the machine-readable representation of this hint. return repr(hint) beartype-0.18.5/beartype/_util/hint/utilhinttest.py000066400000000000000000000335551461113517100224360ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **PEP-agnostic type hint tester utilities** (i.e., callables validating arbitrary objects to be type hints supported by :mod:`beartype`, regardless of whether those hints comply with PEP standards or not). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._data.hint.pep.datapeprepr import HINTS_REPR_IGNORABLE_SHALLOW from beartype._util.cache.utilcachecall import callable_cached from beartype._util.hint.nonpep.utilnonpeptest import ( die_unless_hint_nonpep, is_hint_nonpep, ) from beartype._util.hint.pep.utilpeptest import ( die_if_hint_pep_unsupported, is_hint_pep, is_hint_pep_supported, ) # ....................{ RAISERS }.................... def die_unless_hint( # Mandatory parameters. hint: object, # Optional parameters. exception_prefix: str = '', ) -> None: ''' Raise an exception unless the passed object is a **supported type hint** (i.e., object supported by the :func:`beartype.beartype` decorator as a valid type hint annotating callable parameters and return values). Specifically, this function raises an exception if this object is neither: * A **supported PEP-compliant type hint** (i.e., :mod:`beartype`-agnostic annotation compliant with annotation-centric PEPs currently supported by the :func:`beartype.beartype` decorator). * A **PEP-noncompliant type hint** (i.e., :mod:`beartype`-specific annotation intentionally *not* compliant with annotation-centric PEPs). Efficiency ---------- This validator is effectively (but technically *not*) memoized. The passed ``exception_prefix`` parameter is usually unique to each call to this validator; memoizing this validator would uselessly consume excess space *without* improving time efficiency. Instead, this validator first calls the memoized :func:`is_hint_pep` tester. If that tester returns ``True``, this validator immediately returns ``True`` and is thus effectively memoized; else, this validator inefficiently raises a human-readable exception without memoization. Since efficiency is mostly irrelevant in exception handling, this validator remains effectively memoized. Parameters ---------- hint : object Object to be validated. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Raises ------ BeartypeDecorHintPepUnsupportedException If this object is a PEP-compliant type hint currently unsupported by the :func:`beartype.beartype` decorator. BeartypeDecorHintNonpepException If this object is neither a: * Supported PEP-compliant type hint. * Supported PEP-noncompliant type hint. ''' # If this object is a supported type hint, reduce to a noop. if is_hint(hint): return # Else, this object is *NOT* a supported type hint. In this case, # subsequent logic raises an exception specific to the passed parameters. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # BEGIN: Synchronize changes here with is_hint() below. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # If this hint is PEP-compliant *AND* currently unsupported by @beartype, # raise an exception. if is_hint_pep(hint): die_if_hint_pep_unsupported( hint=hint, exception_prefix=exception_prefix) # Else, this hint is *NOT* PEP-compliant. In this case... # If this PEP-noncompliant hint but still currently unsupported by # @beartype, raise an exception. die_unless_hint_nonpep(hint=hint, exception_prefix=exception_prefix) # ....................{ TESTERS }.................... @callable_cached def is_hint(hint: object) -> bool: ''' :data:`True` only if the passed object is a **supported type hint** (i.e., object supported by the :func:`beartype.beartype` decorator as a valid type hint annotating callable parameters and return values). This tester is memoized for efficiency. Parameters ---------- hint : object Object to be validated. Returns ------- bool :data:`True` only if this object is either: * A **PEP-compliant type hint** (i.e., :mod:`beartype`-agnostic annotation compliant with annotation-centric PEPs). * A **PEP-noncompliant type hint** (i.e., :mod:`beartype`-specific annotation intentionally *not* compliant with annotation-centric PEPs). Raises ------ TypeError If this object is **unhashable** (i.e., *not* hashable by the builtin :func:`hash` function and thus unusable in hash-based containers like dictionaries and sets). All supported type hints are hashable. ''' #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # BEGIN: Synchronize changes here with die_unless_hint() above. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Return true only if... return ( # This is a PEP-compliant type hint supported by @beartype *OR*... is_hint_pep_supported(hint) if is_hint_pep(hint) else # This is a PEP-noncompliant type hint, which by definition is # necessarily supported by @beartype. is_hint_nonpep(hint=hint, is_str_valid=True) ) @callable_cached def is_hint_ignorable(hint: object) -> bool: ''' :data:`True` only if the passed type hint is **ignorable** (i.e., conveys *no* meaningful semantics despite superficially appearing to do so). This tester is memoized for efficiency. Parameters ---------- hint : object Type hint to be inspected. Returns ------- bool :data:`True` only if this type hint is ignorable. ''' # Avoid circular import dependencies. from beartype._util.hint.utilhintget import get_hint_repr # Machine-readable representation of this hint. hint_repr = get_hint_repr(hint) # If this hint is shallowly ignorable, return true. if hint_repr in HINTS_REPR_IGNORABLE_SHALLOW: return True # Else, this hint is *NOT* shallowly ignorable. # # If this hint is PEP-compliant... elif is_hint_pep(hint): # Avoid circular import dependencies. from beartype._util.hint.pep.utilpeptest import ( is_hint_pep_ignorable) # Defer to the function testing whether this hint is an ignorable # PEP-compliant type hint. return is_hint_pep_ignorable(hint) # Else, this hint is PEP-noncompliant and thus *NOT* deeply ignorable. # Since this hint is also *NOT* shallowly ignorable, this hint is # unignorable. In this case, return false. return False #FIXME: Unit test us up, please. def is_hint_uncached(hint: object) -> bool: ''' :data:`True` only if the passed type hint is **uncached** (i.e., hint *not* already internally cached by its parent class or module). Caveats ------- This function *cannot* be meaningfully memoized, since the passed type hint is *not* guaranteed to be cached somewhere. Only functions passed cached type hints can be meaningfully memoized. Since this high-level function internally defers to unmemoized low-level functions that are ``O(n)`` for ``n`` the size of the inheritance hierarchy of this hint, this function should be called sparingly. Parameters ---------- hint : object Type hint to be inspected. Returns ------- bool :data:`True` only if this type hint is uncached. See Also ---------- :func:`beartype._check.convert.convcoerce.coerce_hint_any` Further details. ''' # Avoid circular import dependencies. from beartype._util.hint.pep.proposal.utilpep585 import ( is_hint_pep585_builtin_subscripted) from beartype._util.hint.pep.proposal.utilpep604 import is_hint_pep604 #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Avoid detecting the kind of this hint by calling any of the # get_hint_pep_sign_*() family of memoized getters. Doing so would consume # excess time and space when this hint is uncached, as passing this hint to # any of those getters would then cache against a hint that it is # functionally useless to cache against. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Return true only if this hint is either... return ( # PEP 585-compliant (e.g., "list[str]"), this hint is *NOT* self-caching # (e.g., "list[str] is not list[str]"). # # Note that this additionally includes all third-party type hints that # derive from the "types.GenericAlias" superclass, including: # * "numpy.typing.NDArray[...]" type hints. is_hint_pep585_builtin_subscripted(hint) or # PEP 604-compliant (e.g., "int | str"), this hint is *NOT* self-caching # (e.g., "int | str is not int | str"). # # Note that this hint could also be implicitly cached by coercing this # non-self-caching PEP 604-compliant union into a self-caching PEP # 484-compliant union (e.g., from "int | str" to "Union[int, str]"). # Since doing so would consume substantially more time for *NO* tangible # gain, we strongly prefer the current trivial and efficient approach. is_hint_pep604(hint) ) # ....................{ TESTERS ~ needs }.................... @callable_cached def is_hint_needs_cls_stack(hint: object) -> bool: ''' :data:`True` only if the passed type hint is **type stack-dependent** (i.e., if :mod:`beartype` requires the tuple of all classes lexically declaring the class variables or methods annotated by this hint to generate code type-checking this hint). This tester returns :data:`False` for most hints; only a small subset of hints are type stack-dependent. This includes: * :pep:`673`-compliant self type hint (i.e., :obj:`typing.Self`), which is contextually valid *only* inside a lexical class declaration. This tester is memoized for efficiency. Motivation ---------- **This tester should only be called to decide whether memoized callables should be passed a type stack.** Passing memoized callables a type stack substantially reduces the likelihood of a cache hit and thus the average-case efficiency of calls to those callables. Notably: * Most type hints do *not* require a type stack. * Most type hints annotate class variables and methods of differing classes and thus do *not* share the same type stack. Ergo, passing memoized callables both a type hint *and* a type stack effectively unmemoizes those callables. Thankfully, callers can elide this inefficiency by calling this tester first; when this tester returns: * :data:`False`, the caller can safely pass memoized callables a type hint and :data:`None` for the type stack, thus preserving memoization. This is the common case and thus absolutely worth optimizing for. * :data:`True`, the caller has *no* choice but to pass memoized callables both a type hint *and* a type stack. Grr! Parameters ---------- hint : object Type hint to be inspected. Returns ------- bool :data:`True` only if this type hint is type stack-dependent. ''' # Avoid circular import dependencies. from beartype._util.hint.utilhintget import get_hint_repr # Machine-readable representation of this hint. hint_repr = get_hint_repr(hint) # Return true only if this representation embeds the representation of # either: # * A PEP 673-compliant self type hint (i.e., "typing.Self"). # # Note: # * The fully-qualified names of exact typing modules (e.g., "typing", # "typing_extensions") is intentionally ignored. Although we could # certainly explicitly test for both, doing so would only needlessly # reduce efficiency. By omitting explicit tests for both, this tester # intentionally returns false positives for extremely edge case hints # whose representations contain syntactically related but semantically # unrelated substrings (e.g., "typing.Literal['.Self']", which is clearly # *NOT* a PEP 673-compliant self type hint but erroneously matched as one # by this heuristic). This is non-ideal but thankfully ignorable. See the # "Motivation" section of the docstring for further commentary. # * This string is intentionally *NOT* preceded by a "." delimiter (e.g., as # ".Self" rather than "Self"). Previously, this string was intentionally # preceded by a "." delimiter; doing so satisfied most edge cases while # reducing the likelihood of a false positive. Sadly, doing so also failed # to match "Self" type hints stringified by PEP 563. Grrr! # * That the "in" operator is known to be the fastest means of performing # substring matching in Python. Indeed: # * "in" is faster than the str.find() method *SUBSTANTIALLY* faster than # the re.match() function, which is an entire of magnitude slower than # "in". # * re.match() only begins to catch up to "in" when concurrently testing # for more than 10 or so substrings (e.g., r'(0|1|2|3|4|5|6|7|8|9)'). # # See also the extensive timings documented at this StackOverflow question: # https://stackoverflow.com/questions/4901523/whats-a-faster-operation-re-match-search-or-str-find return 'Self' in hint_repr beartype-0.18.5/beartype/_util/kind/000077500000000000000000000000001461113517100172745ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/kind/__init__.py000066400000000000000000000000001461113517100213730ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/kind/map/000077500000000000000000000000001461113517100200515ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/kind/map/__init__.py000066400000000000000000000000001461113517100221500ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/kind/map/utilmapfrozen.py000066400000000000000000000205521461113517100233260ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **frozen dictionary class hierarchy** (i.e., private classes implementing immutable mappings, preserving :math:`O(1)` complexity while prohibiting modification). ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeKindFrozenDictException from beartype.typing import ( NoReturn, SupportsIndex, Tuple, ) from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_9 from beartype._util.utilobject import get_object_type_basename from collections.abc import Mapping # ....................{ CLASSES }.................... #FIXME: Unit test us up, please. class FrozenDict(dict): ''' **Frozen dictionary** (i.e., immutable mapping preserving :math:`O(1)` complexity while prohibiting modification). Instances of this dictionary are safely hashable and thus suitable for passing as parameters to memoized callables and classes (e.g., our core :class:`beartype.BeartypeConf` class). See Also -------- https://stackoverflow.com/q/1151658/2809027 StackOverflow question lightly inspiring this implementation. ''' # ..................{ CLASS VARIABLES }.................. # Slot all instance variables defined on this object to minimize the time # complexity of both reading and writing variables across frequently called # @beartype decorations. Slotting has been shown to reduce read and write # costs by approximately ~10%, which is non-trivial. __slots__ = ('_hash',) # ..................{ CLASS METHODS }.................. @classmethod def fromkeys(cls, *args, **kwargs) -> 'FrozenDict': # Create and return a new immutable dictionary encapsulating the mutable # dictionary created and returned by the superclass method. # # Note that this implementation intentionally calls the dict.fromkeys() # class method directly rather than calling super().fromkey(). While # seemingly equivalent, the latter implicitly calls the __setitem__() # dunder method of this subclass, which then raises an exception. return cls(dict.fromkeys(*args, **kwargs)) # ..................{ INITIALIZERS }.................. def __init__(self, *args, **kwargs) -> None: # Instantiate this immutable dictionary with all passed parameters. super().__init__(*args, **kwargs) # Precompute the hash for this immutable dictionary at instantiation # time for both efficiency and safety. frozen_items = frozenset(self.items()) # <-- clever stuff self._hash = hash(frozen_items) # <---- more clever stuff # ..................{ DUNDERS }.................. def __hash__(self) -> int: # type: ignore[override] ''' Hash of all key-value pairs in this immutable dictionary. Defining this method satisfies the :class:`collections.abc.Hashable` abstract base class (ABC), enabling this dictionary to be used as in hashable containers (e.g., dictionaries, sets). ''' # Return the precomputed hash for this immutable dictionary. return self._hash def __reduce_ex__(self, protocol: SupportsIndex) -> Tuple[type, object]: ''' Pickle this immutable dictionary. Parameters ---------- protocol : SupportsIndex Pickle protocol under which to pickle this immutable dictionary. Returns ------- Tuple[type, object] 2-tuple suitable for pickling this immutable dictionary. ''' # Dark magic is both dark and magical. return (type(self), (dict(self),)) def __repr__(self) -> str: ''' Machine-readable representation of this immutable dictionary. ''' # Standard "dict"-based representation of the mutable dictionary # encapsulated by this immutable dictionary. dict_repr = super().__repr__() # Fully-qualified name of the possible subclass of this dictionary. type_name = get_object_type_basename(self) # Return an appropriate representation of this immutable dictionary. return f'{type_name}({dict_repr})' def __or__(self, other: Mapping) -> 'FrozenDict': ''' Create and return a new immutable dictionary containing all key-value pairs contained in both the current and passed immutable dictionaries. Parameters ---------- other: Mapping Possibly mutable dictionary to be added to this immutable dictionary. Returns ------- FrozenDict Immutable dictionary adding the current and passed dictionaries. Raises ------ BeartypeKindFrozenDictException If the passed dictionary is *not* actually a dictionary. ''' # If the passed dictionary is *NOT* a dictionary, raise an exception. if not isinstance(other, Mapping): raise BeartypeKindFrozenDictException( f'Non-dictionary {repr(other)} not addable to ' f'immutable dictionary {repr(self)}.' ) # Else, the passed dictionary is a dictionary. # Type of immutable dictionary to be created and returned. cls = type(self) # Standard dictionary uniting this and the passed dictionaries. dict_united: dict = None # type: ignore[assignment] # If the active Python interpreter targets Python >= 3.9, the standard # "dict" class defines the __or__() dunder method. In this case... if IS_PYTHON_AT_LEAST_3_9: # Trivially defer to that method to implement this method. dict_united = super().__or__(dict(other)) # type: ignore[misc] # Else, the active Python interpreter targets Python 3.8. In this case, # implement this operation manually via a dictionary merger. else: # Mutable dictionary uniting these two immutable dictionaries, # initialized to the contents of the current immutable dictionary. dict_united = dict(self) # For each key-value pair in the passed immutable dictionary... for other_key, other_value in other.items(): # Add this key-value pair to this mutable dictionary. dict_united[other_key] = other_value # Create and return a new immutable dictionary wrapping this dictionary. return cls(dict_united) # ..................{ MUTATORS }.................. # Override all mutators (i.e., "dict" methods attempting to modify the # current immutable dictionary) to raise exceptions instead. def __setitem__(self, key, value) -> NoReturn: raise BeartypeKindFrozenDictException( f'Immutable dictionary {repr(self)} ' f'key {repr(key)} not settable to {repr(value)}.' ) def __delitem__(self, key) -> NoReturn: raise BeartypeKindFrozenDictException( f'Immutable dictionary {repr(self)} ' f'key {repr(key)} not deletable.' ) def clear(self) -> NoReturn: raise BeartypeKindFrozenDictException( f'Immutable dictionary {repr(self)} not clearable.') def pop(self, key, default = None) -> NoReturn: raise BeartypeKindFrozenDictException( f'Immutable dictionary {repr(self)} ' f'key {repr(key)} with default {repr(default)} not poppable.' ) def popitem(self) -> NoReturn: raise BeartypeKindFrozenDictException( f'Immutable dictionary {repr(self)} not poppable.') def setdefault(self, key, default = None) -> NoReturn: raise BeartypeKindFrozenDictException( f'Immutable dictionary {repr(self)} ' f'key {repr(key)} with default {repr(default)} not settable.' ) def update(self, *args, **kwargs) -> NoReturn: raise BeartypeKindFrozenDictException( f'Immutable dictionary {repr(self)} ' f'not updatable from positional arguments {repr(args)} ' f'and keyword arguments {repr(kwargs)}.' ) beartype-0.18.5/beartype/_util/kind/map/utilmapset.py000066400000000000000000000224431461113517100226170ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **dictionary mutators** (i.e., low-level callables modifying the contents of passed dictionaries in various general-purpose ways). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilMappingException from beartype.typing import Sequence from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_9 from beartype._util.text.utiltextrepr import represent_object from collections.abc import ( Sequence as SequenceABC, Mapping, MutableMapping, ) # ....................{ MERGERS }.................... def merge_mappings(*mappings: Mapping) -> Mapping: ''' Safely merge all passed mappings if these mappings contain no **key-value collisions** (i.e., if these mappings either contain different keys *or* share one or more key-value pairs) *or* raise an exception otherwise (i.e., if these mappings contain one or more key-value collisions). Since this function only safely merges mappings and thus *never* silently overrides any key-value pair of either mapping, order is insignificant; this function returns the same mapping regardless of the order in which these mappings are passed. Caveats ------- This function creates and returns a new mapping of the same type as that of the first mapping. That type *must* define an ``__init__()`` method with the same signature as the standard :class:`dict` type; if this is *not* the case, an exception is raised. Parameters ---------- mappings: tuple[Mapping] Tuple of two or more mappings to be safely merged. Returns ------- Mapping Mapping of the same type as that of the first mapping created by safely merging these mappings. Raises ------ _BeartypeUtilMappingException If either: * No mappings are passed. * Only one mappings are passed. * Two or more mappings are passed, but these mappings contain one or more key-value collisions. See Also -------- :func:`.die_if_mappings_two_items_collide` Further details. ''' # Return either... return ( # If only two mappings are passed, defer to a function optimized for # merging two mappings. merge_mappings_two(mappings[0], mappings[1]) if len(mappings) == 2 else # Else, three or more mappings are passed. In this case, defer to a # function optimized for merging three or more mappings. merge_mappings_two_or_more(mappings) ) def merge_mappings_two(mapping_a: Mapping, mapping_b: Mapping) -> Mapping: ''' Safely merge the two passed mappings if these mappings contain no key-value collisions *or* raise an exception otherwise. Parameters ---------- mapping_a: Mapping First mapping to be merged. mapping_b: Mapping Second mapping to be merged. Returns ------- Mapping Mapping of the same type as that of the first mapping created by safely merging these mappings. Raises ------ _BeartypeUtilMappingException If these mappings contain one or more key-value collisions. See Also -------- :func:`beartype._util.kind.map.utilmaptest.die_if_mappings_two_items_collide` Further details. ''' # If the first mapping is empty, return the second mapping as is. if not mapping_a: return mapping_b # Else, the first mapping is non-empty. # # If the second mapping is empty, return the first mapping as is. elif not mapping_b: return mapping_a # Else, both mappings are non-empty. # Avoid circular import dependencies. from beartype._util.kind.map.utilmaptest import ( die_if_mappings_two_items_collide) # If these mappings contain a key-value collision, raise an exception. die_if_mappings_two_items_collide(mapping_a, mapping_b) # Else, these mappings contain *NO* key-value collisions. # Merge these mappings. Since no unsafe collisions exist, the order in # which these mappings are merged is irrelevant. return ( # If the active Python interpreter targets Python >= 3.9 and thus # supports "PEP 584 -- Add Union Operators To dict", merge these # mappings with the faster and terser dict union operator. mapping_a | mapping_b # type: ignore[operator] if IS_PYTHON_AT_LEAST_3_9 else # Else, merge these mappings by creating and returning a new mapping of # the same type as that of the first mapping initialized from a slower # and more verbose dict unpacking operation. type(mapping_a)(mapping_a, **mapping_b) # type: ignore[call-arg] ) def merge_mappings_two_or_more(mappings: Sequence[Mapping]) -> Mapping: ''' Safely merge the one or more passed mappings if these mappings contain no key-value collisions *or* raise an exception otherwise. Parameters ---------- mappings: SequenceABC[Mapping] SequenceABC of two or more mappings to be safely merged. Returns ------- Mapping Mapping of the same type as that of the first mapping created by safely merging these mappings. Raises ------ _BeartypeUtilMappingException If these mappings contain one or more key-value collisions. See Also -------- :func:`beartype._util.kind.map.utilmaptest.die_if_mappings_two_items_collide` Further details. ''' assert isinstance(mappings, SequenceABC), f'{repr(mappings)} not sequence.' # Number of passed mappings. MAPPINGS_LEN = len(mappings) # If less than two mappings are passed, raise an exception. if MAPPINGS_LEN < 2: # If only one mapping is passed, raise an appropriate exception. if MAPPINGS_LEN == 1: raise _BeartypeUtilMappingException( f'Two or more mappings expected, but only one mapping ' f'{represent_object(mappings[0])} passed.') # Else, no mappings are passed. Raise an appropriate exception. else: raise _BeartypeUtilMappingException( 'Two or more mappings expected, but no mappings passed.') # Else, two or more mappings are passed. assert isinstance(mappings[0], Mapping), ( f'First mapping {repr(mappings[0])} not mapping.') # Merged mapping to be returned, initialized to the merger of the first two # passed mappings. mapping_merged = merge_mappings_two(mappings[0], mappings[1]) # If three or more mappings are passed... if MAPPINGS_LEN > 2: # For each of the remaining mappings... for mapping in mappings[2:]: # Merge this mapping into the merged mapping to be returned. mapping_merged = merge_mappings_two(mapping_merged, mapping) # Else, only two mappings are passed. In these case, these mappings have # already been merged above. # Return this merged mapping. return mapping_merged # ....................{ UPDATERS }.................... def update_mapping(mapping_trg: MutableMapping, mapping_src: Mapping) -> None: ''' Safely update in-place the first passed mapping with all key-value pairs of the second passed mapping if these mappings contain no **key-value collisions** (i.e., if these mappings either only contain different keys *or* share one or more key-value pairs) *or* raise an exception otherwise (i.e., if these mappings contain one or more of the same keys associated with different values). Parameters ---------- mapping_trg: MutableMapping Target mapping to be safely updated in-place with all key-value pairs of ``mapping_src``. This mapping is modified by this function and *must* thus be mutable. mapping_src: Mapping Source mapping to be safely merged into ``mapping_trg``. This mapping is *not* modified by this function and may thus be immutable. Raises ------ _BeartypeUtilMappingException If these mappings contain one or more key-value collisions. See Also -------- :func:`beartype._util.kind.map.utilmaptest.die_if_mappings_two_items_collide` Further details. ''' assert isinstance(mapping_trg, MutableMapping), ( f'{repr(mapping_trg)} not mutable mapping.') assert isinstance(mapping_src, Mapping), ( f'{repr(mapping_src)} not mapping.') # If the second mapping is empty, silently reduce to a noop. if not mapping_src: return # Else, the second mapping is non-empty. # Avoid circular import dependencies. from beartype._util.kind.map.utilmaptest import ( die_if_mappings_two_items_collide) # If these mappings contain a key-value collision, raise an exception. die_if_mappings_two_items_collide(mapping_trg, mapping_src) # Else, these mappings contain *NO* key-value collisions. # Update the former mapping from the latter mapping. Since no unsafe # collisions exist, this update is now guaranteed to be safe. mapping_trg.update(mapping_src) beartype-0.18.5/beartype/_util/kind/map/utilmaptest.py000066400000000000000000000211671461113517100230050ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **dictionary testers** (i.e., low-level callables testing and validating the contents of passed dictionaries in various general-purpose ways). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilMappingException from beartype.typing import AbstractSet from collections.abc import ( Hashable, Mapping, Set, ) # ....................{ VALIDATORS }.................... def die_if_mappings_two_items_collide( mapping_a: Mapping, mapping_b: Mapping) -> None: ''' Raise an exception if the two passed mappings contain a **key-value collision** (i.e., the same key such that the values associated with that key in these mappings differ). A key-value collision occurs when any key ``ka`` and associated value ``va`` of the first mapping and any key ``kb`` and associated value ``vb`` of the second mapping satisfy ``ka == kb && va != vb``. Equivalently, a key-value collision occurs when any common keys shared between both mappings are associated with different values. Parameters ---------- mapping_a: Mapping First mapping to be inspected. mapping_b: Mapping Second mapping to be inspected. Raises ------ _BeartypeUtilMappingException If these mappings contain one or more key-value collisions. ''' assert isinstance(mapping_a, Mapping), f'{repr(mapping_a)} not mapping.' assert isinstance(mapping_b, Mapping), f'{repr(mapping_b)} not mapping.' # For each key of the first mapping... for mapping_a_key in mapping_a: # If... # # Note this simplistic detection logic has been exhaustively optimized # with iterative profiling to be the most performant solution. Notably, # alternative solutions calling dictionary methods (e.g., dict.items(), # dict.get()) are *DRAMATICALLY* slower -- which is really fascinating. # CPython appears to have internally optimized pure dictionary syntax. if ( # This key resides in the second mapping as well *AND*... mapping_a_key in mapping_b and # This key unsafely maps to a different value in the second # mapping... mapping_a[mapping_a_key] is not mapping_b[mapping_a_key] ): # Immediately short-circuit this iteration to raise an exception below. # Merging these mappings would silently and thus unsafely override the # values associated with these keys in the first mapping with the # values associated with these keys in the second mapping. break # Else, all key collisions are safe (i.e., all shared keys are associated # with the same values in both mappings). Since merging these mappings as # is will *NOT* silently and thus unsafely override any values of either # mapping, accept these mappings as is. # # Note that this awkward branching structure has been profiled to be # optimally efficient, for reasons that honestly elude us. Notably, this # structure is faster than: # * The equivalent "any(...)" generator comprehension -- suggesting we # should similarly unroll *ALL* calls to the any() and all() builtins in # our critical performance path. Thanks, CPython. # * The equivalent test against items intersection, which has the # additional caveat of raising an exception when one or more mapping # items are unhashable and is thus substantially more fragile: e.g., # if len(mapping_keys_shared) == len(mapping_a.items() & mapping_b.items()): # return else: return # Set of all key collisions (i.e., keys residing in both mappings). Since # keys are necessarily hashable, this set intersection is guaranteed to be # safe and thus *NEVER* raise a "TypeError" exception. # # Note that omitting the keys() method call on the latter but *NOT* former # mapping behaves as expected and offers a helpful microoptimization. mapping_keys_shared = mapping_a.keys() & mapping_b # type: ignore[operator] # Set of all keys in all item collisions (i.e., items residing in both # mappings). Equivalently, this is the set of all safe key collisions (i.e., # all shared keys associated with the same values in both mappings). # # Ideally, we would efficiently intersect these items as follows: # mapping_items_shared = mapping_a.items() & mapping_b.items() # Sadly, doing so raises a "TypeError" if one or more values of these # mappings are unhashable -- as they typically are in common use cases # throughout this codebase. Ergo, we fallback to a less efficient but # considerably more robust alternative supporting unhashable values. mapping_keys_shared_safe = { # For each possibly unsafe key collision (i.e., shared key associated # with possibly different values in both mappings), this key... mapping_key_shared for mapping_key_shared in mapping_keys_shared # If this key maps to the same value in both mappings and is thus safe. if ( mapping_a[mapping_key_shared] is mapping_b[mapping_key_shared] ) } # Dictionary of all unsafe key-value pairs (i.e., pairs such that merging # these keys would silently override the values associated with these keys # in either the first or second mappings) from these mappings. mapping_a_unsafe = dict( (key_shared_unsafe, mapping_a[key_shared_unsafe]) for key_shared_unsafe in mapping_keys_shared if key_shared_unsafe not in mapping_keys_shared_safe ) mapping_b_unsafe = dict( (key_shared_unsafe, mapping_b[key_shared_unsafe]) for key_shared_unsafe in mapping_keys_shared if key_shared_unsafe not in mapping_keys_shared_safe ) # Raise a human-readable exception. exception_message = ( f'Mappings not safely mergeable due to key-value collisions:\n' f'~~~~[ mapping_a collisions ]~~~~\n{repr(mapping_a_unsafe)}\n' f'~~~~[ mapping_b collisions ]~~~~\n{repr(mapping_b_unsafe)}' ) # print(exception_message) raise _BeartypeUtilMappingException(exception_message) # ....................{ TESTERS }.................... #FIXME: Unit test us up, please. def is_mapping_keys_all( mapping: Mapping, keys: AbstractSet[Hashable]) -> bool: ''' :data:`True` only if the passed mapping contains *all* of the passed keys. Parameters ---------- mapping: Mapping Mapping to be tested. keys: AbstractSet[Hashable] Set of one or more keys to test this mapping against. Returns ------- bool :data:`True` only if this mapping contains *all* of these keys. ''' assert isinstance(mapping, Mapping), f'{repr(mapping)} not mapping.' assert isinstance(keys, Set), f'{repr(keys)} not set.' assert bool(keys), 'Keys empty.' # Return true only if this mapping contains *ALL* of these keys, # equivalent to efficiently testing whether this set of one or more keys is # a strict subset of the set of all keys in this mapping. # # Note that we intentionally do *NOT* call the set.issubclass() method here. # Even standard set types that otherwise satisfy the "collections.abc.Set" # protocol do *NOT* necessarily define that method. return keys <= mapping.keys() #FIXME: Unit test us up, please. def is_mapping_keys_any( mapping: Mapping, keys: AbstractSet[Hashable]) -> bool: ''' :data:`True` only if the passed mapping contains *any* (i.e., one or more, at least one) of the passed keys. Parameters ---------- mapping: Mapping Mapping to be tested. keys: AbstractSet[Hashable] Set of one or more keys to test this mapping against. Returns ------- bool :data:`True` only if this mapping contains *any* of these keys. ''' assert isinstance(mapping, Mapping), f'{repr(mapping)} not mapping.' assert isinstance(keys, Set), f'{repr(keys)} not set.' assert bool(keys), 'Keys empty.' # Return true only if this mapping contains one or more of these keys, # equivalent to efficiently testing whether the set intersection between # this set of one or more keys *AND* the set of all keys in this mapping is # a non-empty set. return bool(keys & mapping.keys()) beartype-0.18.5/beartype/_util/module/000077500000000000000000000000001461113517100176345ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/module/__init__.py000066400000000000000000000000001461113517100217330ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/module/utilmoddeprecate.py000066400000000000000000000213341461113517100235430ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **Python module deprecation** utilities (i.e., callables deprecating arbitrary module attributes in a reusable fashion). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To avoid circular import dependencies, avoid importing from *ANY* # package-specific submodule either here or in the body of any callable defined # by this submodule. This submodule is typically called from the "__init__" # submodules of public subpackages. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype.typing import Any, Dict from beartype._util.utilobject import SENTINEL from warnings import warn # ....................{ IMPORTERS }.................... def deprecate_module_attr( attr_deprecated_name: str, attr_deprecated_name_to_nondeprecated_name: Dict[str, str], attr_nondeprecated_name_to_value: Dict[str, Any], ) -> object: ''' Dynamically retrieve a deprecated attribute with the passed unqualified name mapped by the passed dictionary to a corresponding non-deprecated attribute from the submodule with the passed dictionary of globally scoped attributes and emit a non-fatal deprecation warning on each such retrieval if that submodule defines this attribute *or* raise an exception otherwise. This function is intended to be called by :pep:`562`-compliant globally scoped ``__getattr__()`` dunder functions, which the Python interpreter implicitly calls under Python >= 3.7 *after* failing to directly retrieve an explicit attribute with this name from that submodule. Parameters ---------- attr_deprecated_name : str Unqualified name of the deprecated attribute to be retrieved. attr_deprecated_name_to_nondeprecated_name : Dict[str, str] Dictionary mapping from the unqualified name of each deprecated attribute retrieved by this function to either: * If that submodule defines a corresponding non-deprecated attribute, the unqualified name of that attribute. * If that submodule is deprecating that attribute *without* defining a corresponding non-deprecated attribute, ``None``. attr_nondeprecated_name_to_value : Dict[str, object] Dictionary mapping from the unqualified name to value of each module-scoped attribute defined by the caller's submodule, typically passed as the ``globals()`` builtin. This function intentionally does *not* implicitly inspect this dictionary from the call stack, as call stack inspection is non-portable under Python. Returns ---------- object Value of this deprecated attribute. Warns ---------- DeprecationWarning If this attribute is deprecated. Raises ---------- AttributeError If this attribute is unrecognized and thus erroneous. ImportError If the passed ``attr_nondeprecated_name_to_value`` dictionary fails to define the non-deprecated variant of the passed deprecated attribute mapped to by the passed ``attr_deprecated_name_to_nondeprecated_name`` dictionary. See Also ---------- https://www.python.org/dev/peps/pep-0562/#id8 :pep:`562`-compliant dunder function inspiring this implementation. ''' assert isinstance(attr_deprecated_name, str), ( f'{repr(attr_deprecated_name)} not string.') assert isinstance(attr_deprecated_name_to_nondeprecated_name, dict), ( f'{repr(attr_deprecated_name_to_nondeprecated_name)} not dictionary.') assert isinstance(attr_nondeprecated_name_to_value, dict), ( f'{repr(attr_nondeprecated_name_to_value)} not dictionary.') # Fully-qualified name of the caller's submodule. Since all physical # submodules (i.e., those defined on-disk) define this dunder attribute # *AND* this function is only ever called by such submodules, this # attribute is effectively guaranteed to exist. MODULE_NAME = attr_nondeprecated_name_to_value['__name__'] # Unqualified name of the non-deprecated attribute originating this # deprecated attribute if this attribute is deprecated *OR* the sentinel. attr_nondeprecated_name = attr_deprecated_name_to_nondeprecated_name.get( attr_deprecated_name, SENTINEL) # If this attribute is deprecated... if attr_nondeprecated_name is not SENTINEL: assert isinstance(attr_nondeprecated_name, str), ( f'{repr(attr_nondeprecated_name)} not string.') # Value of the non-deprecated attribute originating this deprecated # attribute if the former exists *OR* the sentintel. attr_nondeprecated_value = attr_nondeprecated_name_to_value.get( attr_nondeprecated_name, SENTINEL) # If that module fails to define this non-deprecated attribute, raise # an exception. # # Note that: # * This should *NEVER* happen but surely will. In fact, this just did. # * This intentionally raises an beartype-agnostic "ImportError" # exception rather than a beartype-specific exception. Why? Because # this function is *ONLY* ever called by the module-scoped # __getattr__() dunder function in the "__init__.py" submodules # defining public namespaces of public packages. In turn, that # __getattr__() dunder function is only ever implicitly called by # Python's non-trivial import machinery. For unknown reasons, that # machinery silently ignores *ALL* exceptions raised by that # __getattr__() dunder function and thus raised by this function # *EXCEPT* "ImportError" exceptions. Of necessity, we have *NO* # recourse but to defer to Python's poorly documented API constraints. if attr_nondeprecated_value is SENTINEL: raise ImportError( f'Deprecated attribute ' f'"{attr_deprecated_name}" in submodule "{MODULE_NAME}" ' f'originates from missing non-deprecated attribute ' f'"{attr_nondeprecated_name}" not defined by that submodule.' ) # Else, that module defines this non-deprecated attribute. # Substring suffixing the warning message emitted below. warning_suffix = '' # If this deprecated attribute originates from a public non-deprecated # attribute, inform users of the existence of the latter. if not attr_nondeprecated_name.startswith('_'): warning_suffix = ( f' Please globally replace all references to this ' f'attribute with its non-deprecated equivalent ' f'"{attr_nondeprecated_name}" from the same submodule.' ) # Else, this deprecated attribute originates from a private # non-deprecated attribute. In this case, avoid informing users of the # existence of the latter. # Emit a non-fatal warning of the standard "DeprecationWarning" # category, which CPython filters (ignores) by default. # # Note that we intentionally: # * Do *NOT* emit a non-fatal warning of our non-standard # "BeartypeDecorHintPepDeprecationWarning" category, which applies # *ONLY* to PEP-compliant type hint deprecations. # * Do *NOT* call the higher-level issue_warning() function, which would # erroneously declare that this deprecation originates from the # external caller rather than this codebase itself. warn( ( f'Deprecated attribute ' f'"{attr_deprecated_name}" in submodule "{MODULE_NAME}" ' f'scheduled for removal under a future release.' f'{warning_suffix}' ), DeprecationWarning, ) # Return the value of this deprecated attribute. return attr_nondeprecated_value # Else, this attribute is *NOT* deprecated. Since Python called this dunder # function, this attribute is undefined and thus erroneous. # Raise the same exception raised by Python on accessing a non-existent # attribute of a module *NOT* defining this dunder function. # # Note that Python's non-trivial import machinery silently coerces this # "AttributeError" exception into an "ImportError" exception. Just do it! raise AttributeError( f"module '{MODULE_NAME}' has no attribute '{attr_deprecated_name}'") beartype-0.18.5/beartype/_util/module/utilmodget.py000066400000000000000000000320571461113517100223720ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **Python module getter** (i.e., callables dynamically retrieving modules and/or attributes in modules) utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._cave._cavefast import ModuleType from beartype.roar._roarexc import _BeartypeUtilModuleException from beartype.typing import Optional from inspect import findsource from pathlib import Path from sys import modules as sys_modules # ....................{ GETTERS }.................... #FIXME: Unit test us up, please. def get_module_imported_or_none(module_name: str) -> Optional[ModuleType]: ''' Previously imported module, package, or C extension with the passed fully-qualified name if previously imported *or* :data:`None` otherwise (i.e., if that module, package, or C extension has yet to be imported). Parameters ---------- module_name : str Fully-qualified name of the previously imported module to be returned. Returns ------- Either: * If a module, package, or C extension with this fully-qualified name has already been imported, that module, package, or C extension. * Else, :data:`None`. ''' # Donkey One-liner Country: Codebase Freeze! return sys_modules.get(module_name) # ....................{ GETTERS ~ object }.................... def get_object_module_or_none(obj: object) -> Optional[ModuleType]: ''' Module declaring the passed object if this object defines the ``__module__`` dunder instance variable *or* :data:`None` otherwise. Parameters ---------- obj : object Object to be inspected. Returns ------- Optional[ModuleType] Either: * Module declaring this object if this object declares a ``__module__`` dunder attribute. * :data:`None` otherwise. ''' # Fully-qualified name of the module defining this object if any or "None". module_name = get_object_module_name_or_none(obj) # Return either: # * If a module defines this object, that module. # * Else, "None". return get_module_imported_or_none(module_name) if module_name else None def get_object_module(obj: object) -> ModuleType: ''' Module declaring the passed object if this object defines the ``__module__`` dunder instance variable *or* raise an exception otherwise (i.e., if this object does *not* define that variable). Parameters ---------- obj : object Object to be inspected. Returns ------- ModuleType Module declaring this object. Raises ------ _BeartypeUtilModuleException If this object does *not* define the ``__module__`` dunder attribute. ''' # Fully-qualified name of the module defining this object if any *OR* raise # an exception otherwise. module_name = get_object_module_name(obj) # Module defining this object if any *OR* "None" otherwise. module = get_module_imported_or_none(module_name) # If this module was *NOT* previously imported despite this object existing # and thus having been imported from something, this object deceptively lies # about its module. In this case, raise an exception. if module is None: raise _BeartypeUtilModuleException( f'{repr(obj)} module "{module_name}" not found.') # If this module was previously imported. # Return this module. return module # ....................{ GETTERS ~ object : line }.................... def get_object_module_line_number_begin(obj: object) -> int: ''' **Line number** (i.e., 1-based index) of the first line of the source code of the module declaring the passed object if this object is either a callable or class *or* raise an exception otherwise (i.e., if this object is neither a callable nor class). Parameters ---------- obj : object Object to be inspected. Returns ------- int 1-based index of the first line of the source code of the module declaring the passed object. Raises ------ _BeartypeUtilModuleException If this object is neither a callable nor class. ''' # If this object is a class, defer to the standard "inspect" module. # # Note that: # * Deciding whether an object is a class is slightly faster than deciding # whether an object is a callable. The former trivially reduces to a # single isinstance() call against a single superclass; the latter is # considerably less trivial. Ergo, this object is tested as a class first. # * Deciding the line number of the first line declaring an arbitrary class # in its underlying source code module file is highly non-trivial (and in # fact requires extremely slow AST-based parsing). For maintainability and # robustness, we defer to the well-tested standard "inspect" module # despite the performance hit in doing so. if isinstance(obj, type): _, cls_source_line_number_start = findsource(obj) return cls_source_line_number_start # Else, this object is *NOT* a class. # Avoid circular import dependencies. from beartype._util.func.utilfunccodeobj import get_func_codeobj_or_none # Code object underlying this object if this object is a pure-Python # callable *OR* "None" otherwise. # # Note this is the canonical means of deciding whether an arbitrary object # is a pure-Python callable, as our is_func_python() function demonstrates. func_codeobj = get_func_codeobj_or_none(obj) # If this object is a pure-Python callable, return the line number of the # first line declaring this object in its underlying source code file. if func_codeobj is not None: return func_codeobj.co_firstlineno # Else, this object is neither a pure-Python callable *NOR* a class. # In this case, raise an exception. raise _BeartypeUtilModuleException( f'{repr(obj)} neither callable nor class.') # ....................{ GETTERS ~ object : name }.................... #FIXME: Unit test us up, please. def get_object_module_name(obj: object) -> str: ''' **Fully-qualified name** (i.e., ``.``-delimited name prefixed by the declaring package) of the module declaring the passed object if this object defines the ``__module__`` dunder instance variable *or* raise an exception otherwise (i.e., if this object does *not* define that variable). Parameters ---------- obj : object Object to be inspected. Returns ------- str Fully-qualified name of the module declaring this object. Raises ------ _BeartypeUtilModuleException If this object does *not* define the ``__module__`` dunder attribute. ''' # Fully-qualified name of the module declaring this object if this object # defines the "__module__" dunder instance variable *OR* "None" otherwise. module_name = get_object_module_name_or_none(obj) # If this object defines *NO* "__module__" dunder instance variable, raise # an exception. if module_name is None: raise _BeartypeUtilModuleException( f'{repr(obj)} "__module__" dunder attribute undefined ' f'(e.g., due to being neither class nor callable).' ) # Else, this fully-qualified module name exists. # Return this name. return module_name #FIXME: Unit test us up, please. def get_object_module_name_or_none(obj: object) -> Optional[str]: ''' **Fully-qualified name** (i.e., ``.``-delimited name prefixed by the declaring package) of the module declaring the passed object if this object defines the ``__module__`` dunder instance variable *or* :data:`None` otherwise. Parameters ---------- obj : object Object to be inspected. Returns ------- Optional[str] Either: * Fully-qualified name of the module declaring this object if this object declares a ``__module__`` dunder attribute. * :data:`None` otherwise. ''' # Let it be, speaking one-liners of wisdom. return getattr(obj, '__module__', None) # ....................{ GETTERS ~ object : type : name }.................... #FIXME: Unit test us up, please. def get_object_type_module_name_or_none(obj: object) -> Optional[str]: ''' **Fully-qualified name** (i.e., ``.``-delimited name prefixed by the declaring package) of the module declaring either the passed object if this object is a class *or* the class of this object otherwise (i.e., if this object is *not* a class) if this class declares the ``__module__`` dunder instance variable *or* :data:`None` otherwise. Parameters ---------- obj : object Object to be inspected. Returns ------- Optional[str] Either: * Fully-qualified name of the module declaring the type of this object if this type declares a ``__module__`` dunder attribute. * :data:`None` otherwise. ''' # Avoid circular import dependencies. from beartype._util.utilobject import get_object_type_unless_type # Make it so, ensign. return get_object_module_name_or_none(get_object_type_unless_type(obj)) # ....................{ GETTERS ~ module : dir }.................... #FIXME: Unit test us up. def get_module_dir(module: ModuleType) -> Path: ''' High-level :class:`Path` object encapsulating the absolute dirname of the parent directory containing the passed module if this module is physically defined on-disk *or* raise an exception otherwise (i.e., if this module is abstractly defined only in-memory). Parameters ---------- module : ModuleType Module to be inspected. Returns ------- Path High-level :class:`Path` object encapsulating the absolute dirname of the parent directory containing this on-disk module. Raises ------ _BeartypeUtilModuleException If this module *only* resides in memory. ''' # Absolute filename of this module if this module is physically defined # on-disk *OR* raise an exception otherwise (i.e., if this module is # abstractly defined only in-memory). module_filename = get_module_filename(module) # High-level "Path" object encapsulating this file and the parent directory # directly containing this file. module_file = Path(module_filename) module_dir = module_file.parent # Return this "Path" object. return module_dir # ....................{ GETTERS ~ module : file }.................... #FIXME: Unit test us up. def get_module_filename(module: ModuleType) -> str: ''' Absolute filename of the passed module if this module is physically defined on-disk *or* raise an exception otherwise (i.e., if this module is abstractly defined only in-memory). Parameters ---------- module : ModuleType Module to be inspected. Returns ------- str Absolute filename of this on-disk module. Raises ------ _BeartypeUtilModuleException If this module *only* resides in memory. See Also -------- :func:`get_module_filename_or_none` Further details. ''' # Absolute filename of this module if on-disk *OR* "None" otherwise. module_filename = get_module_filename_or_none(module) # If this module resides *ONLY* in memory, raise an exception. if module_filename is None: raise _BeartypeUtilModuleException( f'Module {repr(module)} file not found ' f'(e.g., due to either being a namespace (sub)package or ' f'a dynamically defined in-memory module).' ) # Else, this module resides on disk. # Return this filename. return module_filename #FIXME: Unit test us up. def get_module_filename_or_none(module: ModuleType) -> Optional[str]: ''' Absolute filename of the passed module if this module is physically defined on-disk *or* :data:`None` otherwise (i.e., if this module is abstractly defined only in-memory). Specifically, this getter returns either: * If this module is actually a package, the absolute filename of the ``"__init__.py"`` submodule directly contained in this package. * Else, the absolute filename of this module as provided by the `__file__` dunder attribute of this in-memory module object. In either case, the filename returned by this getter (if any) necessarily refers to a file rather than a directory. Parameters ---------- module : ModuleType Module to be inspected. Returns ------- Optional[str] Either: * Absolute filename of this module if this module resides on disk. * :data:`None` if this module *only* resides in memory. ''' # Thus spake Onelinerthustra. return getattr(module, '__file__', None) beartype-0.18.5/beartype/_util/module/utilmodimport.py000066400000000000000000000470241461113517100231250ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **Python module importer** utilities (i.e., callables dynamically importing modules and/or attributes from modules). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeModuleUnimportableWarning from beartype.roar._roarexc import _BeartypeUtilModuleException from beartype.typing import ( Any, Optional, ) from beartype._cave._cavemap import NoneTypeOr from beartype._data.cls.datacls import TYPE_BUILTIN_NAME_TO_TYPE from beartype._data.hint.datahinttyping import TypeException from beartype._util.error.utilerrwarn import issue_warning from beartype._util.text.utiltextidentifier import die_unless_identifier from beartype._util.utilobject import SENTINEL from importlib import import_module as importlib_import_module from types import ModuleType # ....................{ IMPORTERS }.................... #FIXME: Preserved until requisite, which shouldn't be long. #FIXME: Unit test us up, please. # def import_module( # # Mandatory parameters. # module_name: str, # # # Optional parameters. # exception_cls: TypeException = _BeartypeUtilModuleException, # ) -> ModuleType: # ''' # Dynamically import and return the module, package, or C extension with the # passed fully-qualified name if importable *or* raise an exception # otherwise (i.e., if that module, package, or C extension is unimportable). # # Parameters # ---------- # module_name : str # Fully-qualified name of the module to be imported. # exception_cls : type # Type of exception to be raised by this function. Defaults to # :class:`_BeartypeUtilModuleException`. # # Raises # ---------- # exception_cls # If no module with this name exists. # Exception # If a module with this name exists *but* that module is unimportable # due to raising module-scoped exceptions at importation time. Since # modules may perform arbitrary Turing-complete logic at module scope, # callers should be prepared to handle *any* possible exception. # ''' # assert isinstance(exception_cls, type), ( # f'{repr(exception_cls)} not type.') # # # Module with this name if this module is importable *OR* "None" otherwise. # module = import_module_or_none(module_name) # # # If this module is unimportable, raise an exception. # if module is None: # raise exception_cls( # f'Module "{module_name}" not found.') from exception # # Else, this module is importable. # # # Return this module. # return module def import_module_or_none( # Mandatory parameters. module_name: str, # Optional parameters. exception_cls: TypeException = _BeartypeUtilModuleException, exception_prefix: str = 'Module attribute ', ) -> Optional[ModuleType]: ''' Dynamically import and return the module, package, or C extension with the passed fully-qualified name if importable *or* return :data:`None` otherwise (i.e., if that module, package, or C extension is unimportable). For safety, this function also emits a non-fatal warning when that module, package, or C extension exists but is still unimportable (e.g., due to raising an exception at module scope). Parameters ---------- module_name : str Fully-qualified name of the module to be imported. exception_cls : Type[Exception] Type of exception to be raised by this function. Defaults to :class:`._BeartypeUtilModuleException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- Either: * If a module, package, or C extension with this fully-qualified name is importable, that module, package, or C extension. * Else, :data:`None`. Raises ------ exception_cls If this name is *not* a syntactically valid Python identifier. Warns ----- BeartypeModuleUnimportableWarning If a module with this name exists *but* that module is unimportable due to raising module-scoped exceptions at importation time. ''' # Avoid circular import dependencies. from beartype._util.module.utilmodget import get_module_imported_or_none # If this module name is *NOT* a syntactically valid Python identifier, # raise an exception. die_unless_identifier( text=module_name, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # Else, this module name is a syntactically valid Python identifier. # Module cached with "sys.modules" if this module has already been imported # elsewhere under the active Python interpreter *OR* "None" otherwise. module = get_module_imported_or_none(module_name) # If this module has already been imported, return this cached module. if module is not None: return module # Else, this module has yet to be imported. # Attempt to dynamically import and return this module. try: return importlib_import_module(module_name) # If this module does *NOT* exist, return "None". except ModuleNotFoundError: pass # If this module exists but raises unexpected exceptions from module scope, # first emit a non-fatal warning notifying the user and then return "None". except Exception as exception: issue_warning( cls=BeartypeModuleUnimportableWarning, message=( f'Ignoring module "{module_name}" importation exception:\n' f' {exception.__class__.__name__}: {exception}' ), ) # Inform the caller that this module is unimportable. return None # ....................{ IMPORTERS ~ attr }.................... def import_module_attr( # Mandatory parameters. attr_name: str, # Optional parameters. module_name: Optional[str] = None, exception_cls: TypeException = _BeartypeUtilModuleException, exception_prefix: str = 'Module attribute ', ) -> Any: ''' Dynamically import and return the **module attribute** (i.e., object declared at module scope) with the passed possibly unqualified name from the module with the passed fully-qualified name if importable *or* raise an exception otherwise. Parameters ---------- attr_name : str Possibly unqualified name of the module attribute to be imported. module_name: Optional[str] Either: * If this attribute name is unqualified (i.e., contains *no* ``.`` delimiters), the fully-qualified name of the module declaring this attribute. * Else, this parameter is silently ignored. Defaults to :data:`None`, in which case this attribute name must be either fully-qualified *or* the unqualified name of a builtin type. exception_cls : Type[Exception] Type of exception to be raised by this function. Defaults to :class:`._BeartypeUtilModuleException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- object The module attribute with this fully-qualified name. Raises ------ exception_cls If either: * This attribute or module name is syntactically invalid. * *No* module with this name exists. * A module with by this name exists *but* that module declares no attribute by this name. Warns ----- BeartypeModuleUnimportableWarning If a module prefixed by this name exists *but* that module is unimportable due to module-scoped side effects at importation time. See Also -------- :func:`.import_module_attr_or_sentinel` Further commentary. ''' # Attribute with this name dynamically imported from that module if # importable *OR* the sentinel placeholder otherwise (i.e., if this # attribute is unimportable). module_attr = import_module_attr_or_sentinel( attr_name=attr_name, module_name=module_name, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # If this attribute is unimportable... if module_attr is SENTINEL: assert isinstance(exception_cls, type), f'{exception_cls} not type.' assert isinstance(exception_prefix, str), ( f'{exception_prefix} not string.') # Avoid circular import dependencies. from beartype._util.module.utilmodtest import is_module # Exception message to be raised. exception_message = f'{exception_prefix}"{attr_name}" unimportable' # If this attribute name contains *NO* "." characters, this is an # unqualified basename. In this case... if '.' not in attr_name: # If either no module name was passed *OR* only an empty module name # was passed, then an empty module name was passed *AND* this # attribute contains no "." characters. Ergo, this is an unqualified # basename. Moreover, since the above importation failed, this # attribute is *NOT* the name of a builtin type. Explain this edge # case with an appropriate substring. if not module_name: exception_message += ( ', as:\n' '* Not relative to a package or module ' '(i.e., contains no "." delimiters).\n' '* Not the name of a builtin type (e.g., "int", "str").' ) # Else, a non-empty module name was passed. # # If this module is importable, append an appropriate substring. elif is_module(module_name): exception_message += f' from module "{module_name}".' # Else, this module is unimportable. Append an appropriate # substring. else: exception_message += ( f' from unimportable module "{module_name}".') # Else, this attribute name contains one or more "." characters. In # this case... else: # Fully-qualified name of the module declaring this attribute. module_name, _, _ = attr_name.rpartition('.') # If this module is importable, append an appropriate substring. if is_module(module_name): exception_message += '.' # Else, this module is unimportable. Append an appropriate # substring. else: exception_message += ( f' from unimportable module "{module_name}".') # Raise this exception. raise exception_cls(exception_message) # Else, this module declares this attribute. # Else, return this attribute. return module_attr #FIXME: Fix up all tests of this function, please. def import_module_attr_or_sentinel( # Mandatory parameters. attr_name: str, # Optional parameters. module_name: Optional[str] = None, exception_cls: TypeException = _BeartypeUtilModuleException, exception_prefix: str = 'Module attribute ', ) -> Any: ''' Dynamically import and return the **module attribute** (i.e., object declared at module scope) with the passed possibly unqualified name from the module with the passed fully-qualified name if importable *or* the placeholder :data:`.SENTINEL` otherwise. This importer expects this attribute name to be either: * **Fully-qualified** (i.e., contain one or more ``.`` delimiters), in which case this module name is silently ignored and may thus be :data:`None` or the empty string *or*... * **Unqualified** (i.e., contain *no* ``.`` delimiters), in which case either: * This module name *must* be a non-empty string *or*... * This unqualified attribute name *must* be that of a **builtin type** (e.g., ``"int"``, ``"str"``). Parameters ---------- attr_name : str Possibly unqualified name of the module attribute to be imported. module_name: Optional[str] Either: * If this attribute name is unqualified (i.e., contains *no* ``.`` delimiters), the fully-qualified name of the module declaring this attribute. * Else, this parameter is silently ignored. Defaults to :data:`None`, in which case ``attr_name`` must be either fully-qualified *or* the unqualified name of a builtin type. exception_cls : Type[Exception] Type of exception to be raised by this function. Defaults to :class:`._BeartypeUtilModuleException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to the empty string. Returns ------- object Either: * If *no* module with this name exists, :data:`.SENTINEL`. * If a module with by this name exists *but* that module declares no attribute by this name, :data:`.SENTINEL`. * Else, the module attribute with this fully-qualified name. Raises ------ exception_cls If this name is syntactically invalid. Warns ----- BeartypeModuleUnimportableWarning If a module prefixed by this name exists *but* that module is unimportable due to module-scoped side effects at importation time. ''' assert isinstance(module_name, NoneTypeOr[str]), ( f'{repr(module_name)} neither string nor "None".') # If this attribute name is *NOT* a syntactically valid Python identifier, # raise an exception. die_unless_identifier( text=attr_name, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # Else, this attribute name is a syntactically valid Python identifier. # True only if this attribute name contains *NO* "." characters and is thus # an unqualified basename relative to this module name. is_attr_name_unqualified = '.' not in attr_name # Unqualified basename of this attribute, defaulting to this attribute name. attr_basename = attr_name # If this attribute name is an unqualified basename... if is_attr_name_unqualified: # If either no module name was passed *OR* only an empty module name was # passed... if not module_name: # Builtin type with this name if any *OR* the sentinel otherwise # (i.e., if *NO* builtin type with this name exists). module_attr = TYPE_BUILTIN_NAME_TO_TYPE.get(attr_name, SENTINEL) # Return this object. return module_attr # Else, a non-empty module name was passed. # Else, this attribute name contains one or more "." characters. else: # Fully-qualified name of the module declaring this attribute *AND* the # unqualified basename of this attribute relative to this module, # efficiently split from the passed name. # # Note that: # * This silently overrides the passed module name with the module name # prefixing the name of this attribute, which is presumed to be # authoritative. The passed module name is only an optional fallback # in the event that this attribute name contains *NO* "." characters. # * By the prior validation, this split is guaranteed to be safe. module_name, _, attr_basename = attr_name.rpartition('.') # In any case: # * "module_name" is now a non-empty string. # * "attr_basename" is now an unqualified basename. # Module with this fully-qualified name if importable *OR* "None" otherwise. module = import_module_or_none(module_name) # If that module is unimportable, return "None". if module is None: return SENTINEL # Else, that module is importable. # Attribute with this name if that module declares this attribute *OR* the # sentinel otherwise. module_attr = getattr(module, attr_basename, SENTINEL) # If... if ( # That module does not declare this attribute *AND*... module_attr is SENTINEL and # This attribute name is an unqualified basename... is_attr_name_unqualified # Then this attribute was imported relative to this module. In this case, # this attribute could still be the name of a builtin type. ): # Builtin type with this name if any *OR* the sentinel otherwise # (i.e., if *NO* builtin type with this name exists). # # Note that this edge case is distinct from the prior edge case and thus # *MUST* be handled distinctly. Why? Because this module *COULD* have # globally overridden a builtin type by declaring a global attribute of # the same name. Although extremely unlikely (and strongly frowned # upon), Python *DOES* permit insanity like: # # In some user-defined module at global scope... # class int(object): ... # <-- by all the gods never do this module_attr = TYPE_BUILTIN_NAME_TO_TYPE.get(attr_basename, SENTINEL) # print(f'Attempting to import "{attr_basename}" as builtin type {repr(module_attr)}...') # print(f'TYPE_BUILTIN_NAME_TO_TYPE: {TYPE_BUILTIN_NAME_TO_TYPE}') # Else, either that module declared this attribute *OR* this attribute name # is fully-qualified and thus not the name of a builtin type. In either # case, return this attribute as is. # Return this attribute. return module_attr def import_module_attr_or_none(*args, **kwargs) -> Any: ''' Dynamically import and return the **module attribute** (i.e., object declared at module scope) with the passed fully-qualified name if importable *or* :data:`None` otherwise. Caveats ------- **This importer ambiguously returns false negatives in edge cases and is thus mildly unsafe.** Consider calling the unambiguous :func:`.import_module_attr_or_sentinel` importer instead. Why? Because this importer returns :data:`None` both when this attribute is unimportable *and* when this attribute is importable but has a value of :data:`None`. Nonetheless, this importer remains convenient for various use cases in which this distinction is mostly irrelevant. Parameters ---------- All parameters are passed as is to the lower-level :func:`.import_module_attr_or_sentinel` importer. Returns ------- object Either: * If *no* module prefixed this name exists, :data:`None`. * If a module prefixed by this name exists *but* that module declares no attribute by this name, :data:`None`. * Else, the module attribute with this fully-qualified name. Raises ------ exception_cls If this name is syntactically invalid. Warns ----- BeartypeModuleUnimportableWarning If a module prefixed by this name exists *but* that module is unimportable due to module-scoped side effects at importation time. ''' # Module attribute with this name if that module declares this attribute # *OR* the sentinel placeholder otherwise. module_attr = import_module_attr_or_sentinel(*args, **kwargs) # Return either this attribute if importable *OR* "None" otherwise. return module_attr if module_attr is not SENTINEL else None beartype-0.18.5/beartype/_util/module/utilmodtest.py000066400000000000000000000204661461113517100225730ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **Python module tester** (i.e., callables dynamically testing modules and/or attributes in modules) utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilModuleException from beartype._data.hint.datahinttyping import TypeException from beartype._util.text.utiltextidentifier import die_unless_identifier from beartype._util.text.utiltextversion import convert_str_version_to_tuple from importlib.metadata import version as get_module_version # type: ignore[attr-defined] # ....................{ RAISERS }.................... #FIXME: Excise us up. This function is no longer called anywhere. *sigh* def die_unless_module_attr_name( # Mandatory parameters. module_attr_name: str, # Optional parameters. exception_cls: TypeException = _BeartypeUtilModuleException, exception_prefix: str = 'Module attribute name ', ) -> None: ''' Raise an exception unless the passed string is the fully-qualified syntactically valid name of a **module attribute** (i.e., object declared at module scope by a module) that may or may not actually exist. This validator does *not* validate this attribute to actually exist -- only that the name of this attribute is syntactically valid. Parameters ---------- module_attr_name : str Fully-qualified name of the module attribute to be validated. exception_cls : type, optional Type of exception to be raised in the event of a fatal error. Defaults to :class:`._BeartypeUtilModuleException`. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to something reasonably sane. Raises ------ exception_cls If either: * This name is *not* a string. * This name is a string containing either: * *No* ``.`` characters and thus either: * Is relative to the calling subpackage and thus *not* fully-qualified (e.g., ``muh_submodule``). * Refers to a builtin object (e.g., ``str``). While technically fully-qualified, the names of builtin objects are *not* explicitly importable as is. Since these builtin objects are implicitly imported everywhere, there exists *no* demonstrable reason to even attempt to import them anywhere. * One or more ``.`` characters but syntactically invalid as an identifier (e.g., ``0h!muh?G0d.``). ''' assert isinstance(exception_cls, type), f'{repr(exception_cls)} not type.' assert isinstance(exception_prefix, str), ( f'{repr(exception_prefix)} not string.') # If this object is *NOT* a string, raise an exception. if not isinstance(module_attr_name, str): raise exception_cls( f'{exception_prefix}{repr(module_attr_name)} not string.') # Else, this object is a string. # # If this string contains *NO* "." characters and thus either is relative to # the calling subpackage or refers to a builtin object, raise an exception. elif '.' not in module_attr_name: raise exception_cls( f'{exception_prefix}"{module_attr_name}" ' f'relative or refers to builtin object ' f'(i.e., due to containing no "." characters).' ) # Else, this string contains one or more "." characters and is thus the # fully-qualified name of a non-builtin type. # # If this string is syntactically invalid as a fully-qualified module # attribute name, raise an exception. else: die_unless_identifier( text=module_attr_name, exception_cls=exception_cls, exception_prefix=exception_prefix, ) # Else, this string is syntactically valid as a fully-qualified module # attribute name. # ....................{ TESTERS }.................... def is_module(module_name: str) -> bool: ''' :data:`True` only if the module or C extension with the passed fully-qualified name is importable under the active Python interpreter. Caveats ------- **This tester dynamically imports this module as an unavoidable side effect of performing this test.** Parameters ---------- module_name : str Fully-qualified name of the module to be imported. Returns ------- bool :data:`True` only if this module is importable. Warns ----- BeartypeModuleUnimportableWarning If a module with this name exists *but* that module is unimportable due to raising module-scoped exceptions at importation time. ''' # Avoid circular import dependencies. from beartype._util.module.utilmodimport import import_module_or_none # Module with this name if this module is importable *OR* "None" otherwise. module = import_module_or_none(module_name) # Return true only if this module is importable. return module is not None #FIXME: Unit test us up against "setuptools", the only third-party package #*BASICALLY* guaranteed to be importable. def is_module_version_at_least(module_name: str, version_minimum: str) -> bool: ''' :data:`True` only if the module or C extension with the passed fully-qualified name is both importable under the active Python interpreter *and* at least as new as the passed version. Caveats ------- **This tester dynamically imports this module as an unavoidable side effect of performing this test.** Parameters ---------- module_name : str Fully-qualified name of the module to be imported. version_minimum : str Minimum version to test this module against as a dot-delimited :pep:`440`-compliant version specifier (e.g., ``42.42.42rc42.post42``). Returns ------- bool :data:`True` only if: * This module is importable. * This module's version is at least the passed version. Warns ----- BeartypeModuleUnimportableWarning If a module with this name exists *but* that module is unimportable due to raising module-scoped exceptions at importation time. ''' assert isinstance(version_minimum, str), ( f'{repr(version_minimum)} not string.') # If this module is unimportable, return false immediately. if not is_module(module_name): return False # Else, this module is importable. # Current version of this module installed under the active Python # interpreter if any *OR* raise an exception otherwise (which should # *NEVER* happen by prior logic testing this module to be importable). version_actual = get_module_version(module_name) # Tuples of version parts parsed from version strings. version_actual_parts = convert_str_version_to_tuple(version_actual) version_minimum_parts = convert_str_version_to_tuple(version_minimum) # Return true only if this module's version satisfies this minimum. return version_actual_parts >= version_minimum_parts # ....................{ TESTERS ~ package }.................... #FIXME: Unit test us up, please. def is_package(package_name: str) -> bool: ''' :data:`True` only if the package with the passed fully-qualified name is importable under the active Python interpreter. Caveats ------- **This tester dynamically imports this module as an unavoidable side effect of performing this test.** Parameters ---------- package_name : str Fully-qualified name of the package to be imported. Returns ------- bool :data:`True` only if this package is importable. Warns ----- BeartypeModuleUnimportableWarning If a package with this name exists *but* that package is unimportable due to raising module-scoped exceptions from the top-level `__init__` submodule of this package at importation time. ''' # Be the one liner you want to see in the world. return is_module(f'{package_name}.__init__') beartype-0.18.5/beartype/_util/os/000077500000000000000000000000001461113517100167705ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/os/__init__.py000066400000000000000000000000001461113517100210670ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/os/utilosshell.py000066400000000000000000000026021461113517100217110ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **shell** (i.e., external low-level command-line environment encapsulating the active Python interpreter as a parent process of this interpreter) utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from os import environ # ....................{ TESTERS }.................... get_shell_var_value_or_none = environ.get ''' String value of the shell environment variable with the passed name if the parent shell defines this variable *or* :data:`None` otherwise (i.e., if the parent shell does *not* define this variable). Caveats ------- **This getter is a human-readable alias of the comparable** :func:`os.getenv` **function and** :meth:`os.environ.get` **method.** This getter exists only for disambiguity and clarity. This getter is *not* an alias of the :meth:`os.environ.__getitem__` dunder method, which raises a :exc:`KeyError` exception rather than returns :data:`None` if the parent shell fails to define this variable. See Also -------- https://stackoverflow.com/a/41626355/2809027 StackOverflow answer strongly inspiring this alias. ''' beartype-0.18.5/beartype/_util/os/utilostest.py000066400000000000000000000027401461113517100215640ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **platform tester** (i.e., functions detecting the current platform the active Python interpreter is running under) utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._util.cache.utilcachecall import callable_cached from platform import system as platform_system from sys import platform as sys_platform # ....................{ TESTERS }.................... @callable_cached def is_os_linux() -> bool: ''' ``True`` only if the current platform is a **Linux distribution.** This tester is memoized for efficiency. ''' return platform_system() == 'Linux' @callable_cached def is_os_macos() -> bool: ''' ``True`` only if the current platform is **Apple macOS**, the operating system previously known as "OS X." This tester is memoized for efficiency. ''' return platform_system() == 'Darwin' @callable_cached def is_os_windows_vanilla() -> bool: ''' ``True`` only if the current platform is **vanilla Microsoft Windows** (i.e., *not* running the Cygwin POSIX compatibility layer). This tester is memoized for efficiency. ''' return sys_platform == 'win32' beartype-0.18.5/beartype/_util/os/utilostty.py000066400000000000000000000053731461113517100214320ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **TTY** (i.e., interactive terminal expected to be reasonably POSIX-compliant, which even recent post-Windows 10 terminals now guarantee) utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... import sys # ....................{ TESTERS }.................... #FIXME: Unit test us up, please. def is_stdout_terminal() -> bool: ''' :data:`True` only if standard output is currently attached to a **TTY** (i.e., interactive terminal). If this tester returns :data:`True`, the TTY to which standard output is currently attached may be safely assumed to support **ANSI escape sequences** (i.e., POSIX-compliant colour codes). This assumption even holds under platforms that are otherwise *not* POSIX-compliant, including: * All popular terminals (including the stock Windows Terminal) and interactive development environments (IDEs) (including VSCode) bundled with Microsoft Windows, beginning at Windows 10. Caveats ---------- **This tester is intentionally not memoized** (i.e., via the :func:`beartype._util.cache.utilcachecall.callable_cached` decorator), as external callers can and frequently do monkey-patch or otherwise modify the value of the global :attr:`sys.stdout` output stream. See Also ---------- https://stackoverflow.com/questions/3818511/how-to-tell-if-python-script-is-being-run-in-a-terminal-or-via-gui StackOverflow thread strongly inspiring this implementation. ''' # print(f'sys.stdout: {repr(sys.stdout)} [{type(sys.stdout)}]') # print(f'sys.stderr: {repr(sys.stderr)} [{type(sys.stderr)}]') # One-liners for great justice. # # Note that: # * Input and output streams are *NOT* guaranteed to define the isatty() # method. For safety, we defensively test for the existence of that method # before deferring to that method. # * All popular terminals under Windows >= 10 -- including terminals bundled # out-of-the-box with Windows -- now support ANSII escape sequences. Since # older Windows versions are compelling security risks and thus ignorable # for contemporary purposes, Windows no longer needs to be excluded from # ANSII-based colourization. All praise Satya Nadella. \o/ return hasattr(sys.stdout, 'isatty') and sys.stdout.isatty() # return hasattr(sys.stdin, 'isatty') and sys.stdin.isatty() # return hasattr(sys.stderr, 'isatty') and sys.stderr.isatty() beartype-0.18.5/beartype/_util/path/000077500000000000000000000000001461113517100173035ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/path/__init__.py000066400000000000000000000000001461113517100214020ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/path/utilpathremove.py000066400000000000000000000202401461113517100227230ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **path removers** (i.e., low-level callables permanently removing on-disk files and directories in various reasonably safe and portable ways). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... # from beartype.roar._roarexc import _BeartypeUtilPathException from beartype._data.hint.datahinttyping import ( PathnameLike, PathnameLikeTuple, ) from importlib.machinery import BYTECODE_SUFFIXES from pathlib import Path # ....................{ REMOVERS }.................... #FIXME: Unit test us up, please. def remove_package_bytecode_files(package_dirname: PathnameLike) -> None: ''' Permanently, silently, and recursively remove all **bytecode files** (i.e., pure-Python bytecode compiled to platform-dependent temporary files residing in temporary ``__pycache__/`` subdirectories) of both the passed package and all subpackages of that package regardless of nesting depth. Usage ---------- This function is typically intended for usage in our test suite. Unit tests exercising :mod:`beartype` functionality that dynamically modifies the contents of bytecode files guarantee idempotency (i.e., reproducibility) by calling this function *before* exercising that functionality. Examples include :mod:`beartype.claw` import hooks that dynamically transform the abstract syntax trees (ASTs) of sample modules embedded in our test suite *before* permanently serializing (i.e., saving, writing) those changes back to disk within those bytecode files. Preventing desynchronization between the frequently changing implementations of those import hooks and those bytecode files requires calling this function beforehand. Caveats ---------- **This function is subject to subtle race conditions if multiple threads and/or processes concurrently attempt to mutate this package on the local filesystem.** Since *all* filesystem-centric logic suffers similar issues, we leave this issue as an exercise for the caller. Parameters ---------- package_dirname : PathnameLike Absolute dirname of the package to remove all previously compiled bytecode files from. ''' assert isinstance(package_dirname, PathnameLikeTuple), ( f'{repr(package_dirname)} neither string nor "Path" object.') # Avoid circular import dependencies. from beartype._util.path.utilpathtest import die_unless_dir # High-level "Path" object encapsulating this dirname. package_dir = Path(package_dirname) # If this directory does *NOT* exist, raise an exception. die_unless_dir(package_dir) # Else, this directory exists. # For the "."-prefixed filetype of each type of platform-dependent bytecode # file generated by the current platform... # # Note that Python-specific glob syntax does *not* support disjunction # (i.e., alternation). In particular, POSIX-compliant glob disjunction # syntax "{match1,...,matchN}" is unsupported. If supported, that syntax # would enable this inefficient O(n) iteration to be trivially optimized # into a single O(1) call to the remove_paths_globbed() function. for BYTECODE_SUFFIX in BYTECODE_SUFFIXES: # Permanently and silently remove *ALL* bytecode files previously # compiled by Python into this "__pycache__/" subdirectory. remove_paths_globbed( dirname=package_dir, # Note that this filetype is already prefixed by ".". *sigh* glob=f'**/__pycache__/*{BYTECODE_SUFFIX}', ) #FIXME: Unit test us up, please. def remove_paths_globbed(dirname: PathnameLike, glob: str) -> None: ''' Permanently, silently, and possibly recursively remove *all* target files and empty directories from the source directory with the passed dirname matching the passed Python-specific glob expression. Note that Python-specific glob syntax is exactly that supported by the standard :mod:`fnmatch` module *plus* the recursive glob syntax ``"**/"``. Specifically, Python-specific glob syntax supports *only* the following small subset of POSIX-compliant glob syntax: * ``"*"`` matches everything. * ``"?"`` matches any single character. * ``"[seq]"`` matches any character in the substring ``"seq"``. * ``"[!seq]"`` matches any character not in the substring ``"seq"``. * ``"**/"`` matches *all* subdirectories recursively regardless of depth (e.g., ``"**/*.jpg"``, recursively removing all JPEG-formatted images from both this directory and all subdirectories of this directory). Caveats ---------- **This function silently ignores all non-empty directories matched by this glob expression.** Consider an alternate approach leveraging recursive directory tree traversal if requiring non-empty directory removal. **This function is subject to subtle race conditions if multiple threads and/or processes concurrently attempt to mutate this source directory.** Since *all* filesystem-centric logic suffers similar issues, we leave this issue as an exercise for the caller. **This function is currently inefficiently implemented in a single-threaded manner for simplicity.** This approach is appropriate when removing a small number of files but inappropriate when removing a large number of files. In the latter case, consider an alternate approach leveraging either multithreading or multiprocessing. See also this `popular article`_. .. _popular article: https://superfastpython.com/multithreaded-file-deletion Parameters ---------- dirname : PathnameLike Dirname of the directory to remove *all* files and empty directories matching this glob from, specified as a **pathname-like** (i.e., either a low-level string possibly signifying a pathname *or* a high-level :class:`Path` instance definitely encapsulating a pathname). glob : str Python-specific glob expression matching *all* files and empty directories to be removed from this directory (e.g., ``"*.jpg"``). Raises ---------- _BeartypeUtilPathException If either: * This directory does *not* exist. * This directory exists but is *not* actually a directory. See Also ---------- https://stackoverflow.com/a/38189275/2809027 StackOverflow answer strongly inspiring this implementation. ''' assert isinstance(dirname, PathnameLikeTuple), ( f'{repr(dirname)} neither string nor "Path" object.') assert isinstance(glob, str), f'{repr(glob)} not string.' # Avoid circular import dependencies. from beartype._util.path.utilpathtest import die_unless_dir # High-level "Path" object encapsulating this dirname. dirname_path = Path(dirname) # If this directory does *NOT* exist, raise an exception. die_unless_dir(dirname_path) # Else, this directory exists. # For each matching pathname globbed from this dirname as a "Path" object... for pathname_globbed in dirname_path.glob(glob): # print(f'Removing globbed path "{pathname_globbed}"...') # If this pathname refers to a file... if pathname_globbed.is_file(): #FIXME: Pass "missing_ok=True" *AFTER* dropping Python 3.7, as doing #so will improve the robustness of this logic against race #conditions. # Silently remove this file if feasible *OR* raise an exception. pathname_globbed.unlink() # Else, this pathname does *NOT* refer to a file. # # If this pathname refers to a (hopefully empty) subdirectory... elif pathname_globbed.is_dir(): # Silently remove this empty subdirectory if feasible *OR* raise an # exception. pathname_globbed.rmdir() # Else, this pathname refers to neither a file *NOR* subdirectory. In # this case, silently ignore this pathname. beartype-0.18.5/beartype/_util/path/utilpathtest.py000066400000000000000000000136411461113517100224140ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **path testers** (i.e., low-level callables testing various aspects of on-disk files and directories and raising exceptions when those files and directories fail to satisfy various constraints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilPathException from beartype._data.hint.datahinttyping import ( PathnameLike, # PathnameLikeTuple, TypeException, ) from os import ( X_OK, access as is_path_permissions, ) from pathlib import Path # ....................{ RAISERS ~ dir }.................... #FIXME: Unit test us up, please. def die_unless_dir( # Mandatory parameters. dirname: PathnameLike, # Optional parameters. exception_cls: TypeException = _BeartypeUtilPathException, ) -> None: ''' Raise an exception of the passed type if *no* directory with the passed dirname exists. Parameters ---------- dirname : PathnameLike Dirname to be validated. exception_cls : Type[Exception], optional Type of exception to be raised in the event of a fatal error. Defaults to :exc:`._BeartypeUtilPathException`. Raises ------ exception_cls If *no* directory with the passed dirname exists. ''' # High-level "Path" object encapsulating this dirname. dirname_path = Path(dirname) # If either no path with this pathname exists *OR* a path with this pathname # exists but this path is not a directory... if not dirname_path.is_dir(): assert isinstance(exception_cls, type), ( f'{repr(exception_cls)} not type.') # If no path with this pathname exists, raise an appropriate exception. if not dirname_path.exists(): raise exception_cls(f'Directory "{dirname_path}" not found.') # Else, a path with this pathname exists. # By elimination, a path with this pathname exists but this path is not # a directory. In this case, raise an appropriate exception. raise exception_cls(f'Path "{dirname_path}" not directory.') # Else, a directory with this dirname exists. # ....................{ RAISERS ~ file }.................... #FIXME: Unit test us up, please. def die_unless_file( # Mandatory parameters. filename: PathnameLike, # Optional parameters. exception_cls: TypeException = _BeartypeUtilPathException, ) -> None: ''' Raise an exception of the passed type if *no* file with the passed filename exists. Parameters ---------- filename : PathnameLike Dirname to be validated. exception_cls : Type[Exception], optional Type of exception to be raised in the event of a fatal error. Defaults to :exc:`._BeartypeUtilPathException`. Raises ------ exception_cls If *no* file with the passed filename exists. ''' # High-level "Path" object encapsulating this filename. filename_path = Path(filename) # If either no path with this pathname exists *OR* a path with this pathname # exists but this path is not a file... if not filename_path.is_file(): assert isinstance(exception_cls, type), ( f'{repr(exception_cls)} not type.') # If no path with this pathname exists, raise an appropriate exception. if not filename_path.exists(): raise exception_cls(f'File "{filename_path}" not found.') # Else, a path with this pathname exists. # By elimination, a path with this pathname exists but this path is not # a file. In this case, raise an appropriate exception. raise exception_cls(f'Path "{filename_path}" not file.') # Else, a file with this filename exists. #FIXME: Unit test us up, please. def die_unless_file_executable( # Mandatory parameters. filename: PathnameLike, # Optional parameters. exception_cls: TypeException = _BeartypeUtilPathException, ) -> None: ''' Raise an exception of the passed type if either no file with the passed filename exists *or* this file exists but is not **executable** (i.e., the current user lacks sufficient permissions to execute this file). Parameters ---------- filename : PathnameLike Dirname to be validated. exception_cls : Type[Exception], optional Type of exception to be raised in the event of a fatal error. Defaults to :exc:`._BeartypeUtilPathException`. Raises ------ :exception_cls If either: * No file with the passed filename exists. * This file exists but is not executable by the current user. ''' # If *NO* file with this filename exists, raise an exception. die_unless_file(filename=filename, exception_cls=exception_cls) # Else, a file with this filename exists. # Note that this logic necessarily leverages the low-level "os.path" # submodule rather than the object-oriented "pathlib.Path" class, which # currently lacks *ANY* public facilities for introspecting permissions # (including executability) as of Python 3.12. This is why we sigh. # Reduce this possible high-level "Path" object to a low-level filename. filename_str = str(filename) # If the current user has *NO* permission to execute this file... if not is_path_permissions(filename_str, X_OK): assert isinstance(exception_cls, type), ( f'{repr(exception_cls)} not type.') # Raise an appropriate exception. raise exception_cls(f'File "{filename_str}" not executable.') # Else, the current user has permission to execute this file. Ergo, this # file is an executable file with respect to this user. beartype-0.18.5/beartype/_util/py/000077500000000000000000000000001461113517100167775ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/py/__init__.py000066400000000000000000000000001461113517100210760ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/py/utilpyinterpreter.py000066400000000000000000000200231461113517100231600ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **Python interpreter** utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import ( _BeartypeUtilPythonInterpreterException, ) from beartype._data.hint.datahinttyping import CommandWords from beartype._util.cache.utilcachecall import callable_cached from platform import python_implementation from sys import executable as sys_executable # ....................{ TESTERS }.................... @callable_cached def is_python_pypy() -> bool: ''' :data:`True` only if the active Python interpreter is **PyPy**. This tester is memoized for efficiency. ''' return python_implementation() == 'PyPy' def is_python_optimized() -> bool: ''' :data:`True` only if the active Python interpreter is currently **optimized** (i.e., either the current Python process was invoked with at least one ``-O`` command-line option *or* the ``${PYTHONOPTIMIZE}`` environment variable is currently set to a non-zero integer). This tester is intentionally *not* memoized (e.g., by the ``@callable_cached`` decorator), as doing so would prevent this tester from detecting dynamic changes to the ``PYTHONOPTIMIZE`` environment variable manually applied by the external user. Technically, Python itself detects *no* such changes. Pragmatically, there's *no* demonstrable justification for :mod:`beartype` itself to behave similarly; since testing environment variable values is both trivial *and* yields a better outcome for users, this tester does so. Indeed, our userbase `explicitly requested that we do so `__. .. _beartype issue: https://github.com/beartype/beartype/issues/341 ''' # If Python disabled the "__debug__" dunder global, either the current # Python process was invoked with at least one ``-O`` command-line option # *OR* the "${PYTHONOPTIMIZE}" environment variable was set to a non-zero # integer at process invocation time. In either case, return true. if not __debug__: # pragma: no cover return True # Else, Python enabled the "__debug__" dunder global. Although the # "${PYTHONOPTIMIZE}" environment variable was *NOT* set to a non-zero # integer at process invocation time, that variable *COULD* have since been # set by the external user (e.g., in an interactive REPL). Let's decide. # Avoid circular import dependencies. from beartype._util.os.utilosshell import get_shell_var_value_or_none # String value of this environment variable if set *OR* "None" otherwise. PYTHONOPTIMIZE_str = get_shell_var_value_or_none('PYTHONOPTIMIZE') # If this environment variable is set... if PYTHONOPTIMIZE_str is not None: # print(f'Detecting ${{PYTHONOPTIMIZE}} value {PYTHONOPTIMIZE_str}...') # Attempt to coerce this string into an integer. try: PYTHONOPTIMIZE_int = int(PYTHONOPTIMIZE_str) # If doing so raises *ANY* exception whatsoever, return false. except: return False # If this integer is non-zero, this environment variable has since been # set to a non-zero integer by the user. In this case, return true. if PYTHONOPTIMIZE_int > 0: return True # Else, this environment variable remains zeroed and thus disabled. # Else, this environment variable is unset. # Return false as a fallback. return False # ....................{ GETTERS ~ path }.................... @callable_cached def get_interpreter_command_words() -> CommandWords: ''' **Active Python interpreter command words** (i.e., iterable of one or more shell words unambiguously running the executable binary for this interpreter and machine architecture). This getter is memoized for efficiency. Caveats ------- **This high-level getter should always be called in lieu of the low-level** :func:`.get_interpreter_filename` **getter** when attempting to rerun this interpreter as a subprocess of the active Python process. Why? Because the absolute filename of the executable binary for this interpreter is insufficient to unambiguously run this binary under edge cases, including: * **macOS.** Under macOS, the executable binary for this interpreter may be bundled with one or more other executable binaries targeting different machine architectures (e.g., 32-bit, 64-bit) in a single so-called "universal binary." Distinguishing between these bundled binaries requires passing this interpreter to a prefixing macOS-specific command: ``arch``. Returns ------- CommandWords Iterable of one or more shell words unambiguously running this binary. ''' #FIXME: Uncomment if required. Although this was certainly required a decade #ago, it's unclear whether this is still required; indeed, given the #increased prevalence of Apple Silicon, it seems likely that an entirely #different macOS-specific prefix might be required now. Thus, I sigh. *sigh* # # Avoid circular import dependencies. # from beartype._util.os.utilostest import is_os_macos # # # List of such shell words. # command_words = None # type: ignore[assignment] # # # If the current platform is macOS, this interpreter is only unambiguously runnable via the # # macOS-specific "arch" command. In this case... # if is_os_macos(): # # Run the "arch" command. # command_words = ['arch'] # # # Instruct this command to run the architecture-specific binary in # # Python's universal binary corresponding to the current architecture. # if is_wordsize_64(): # command_words.append('-i386') # else: # command_words.append('-x86_64') # # # Instruct this command, lastly, to run this interpreter. # command_words.append(get_interpreter_filename()) # # Else, this interpreter is unambiguously runnable as is. # else: # command_words = [get_interpreter_filename()] # Iterable of all interpreter shell words to be returned. command_words = (get_interpreter_filename(),) # Return this iterable. return command_words @callable_cached def get_interpreter_filename() -> str: ''' Absolute filename of the executable binary underlying the active Python interpreter if Python provides this filename *or* raise an exception otherwise (i.e., if Python refuses to provide this filename, typically due to this filename being embedded in a frozen bundle of some sort). This getter is memoized for efficiency. Raises ------ _BeartypeUtilPathException If Python successfully queried this filename but no such file exists. _BeartypeUtilPythonInterpreterException If Python failed to query this filename. Returns ------- str Absolute filename of this binary. ''' # Avoid circular import dependencies. from beartype._util.path.utilpathtest import die_unless_file_executable # If Python refuses to provide this filename, raise an exception. # # Note that this test intentionally matches both the empty string and # "None", as the official documentation for "sys.executable" states: # If Python is unable to retrieve the real path to its executable, # sys.executable will be an empty string or None. if not sys_executable: raise _BeartypeUtilPythonInterpreterException( 'Absolute filename of active Python interpreter not found.') # Else, Python provides this filename. # If this file is *NOT* executable, raise an exception. die_unless_file_executable(sys_executable) # Else, this file is executable. # Return this filename. return sys_executable beartype-0.18.5/beartype/_util/py/utilpyversion.py000066400000000000000000000105611461113517100223100ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **Python interpreter version utilities**. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from sys import version_info # ....................{ CONSTANTS ~ at least }.................... IS_PYTHON_AT_LEAST_4_0 = version_info >= (4, 0) ''' :data:`True` only if the active Python interpreter targets at least Python 4.0.0. ''' #FIXME: After dropping Python 3.13 support: #* Refactor all code conditionally testing this global to be unconditional. #* Remove this global. #* Remove all decorators resembling: # @skip_if_python_version_less_than('3.13.0') IS_PYTHON_AT_LEAST_3_13 = IS_PYTHON_AT_LEAST_4_0 or version_info >= (3, 13) ''' :data:`True` only if the active Python interpreter targets at least Python 3.12.0. ''' #FIXME: After dropping Python 3.13 support: #* Refactor all code conditionally testing this global to be unconditional. #* Remove this global. #* Remove all decorators resembling: # @skip_if_python_version_less_than('3.12.0') IS_PYTHON_AT_LEAST_3_12 = IS_PYTHON_AT_LEAST_3_13 or version_info >= (3, 12) ''' :data:`True` only if the active Python interpreter targets at least Python 3.13.0. ''' #FIXME: After dropping Python 3.10 support: #* Refactor all code conditionally testing this global to be unconditional. #* Remove this global. IS_PYTHON_AT_MOST_3_11 = not IS_PYTHON_AT_LEAST_3_12 ''' :data:`True` only if the active Python interpreter targets at most Python 3.11.x. ''' #FIXME: After dropping Python 3.10 support: #* Refactor all code conditionally testing this global to be unconditional. #* Remove this global. #* Remove all decorators resembling: # @skip_if_python_version_less_than('3.11.0') IS_PYTHON_AT_LEAST_3_11 = IS_PYTHON_AT_LEAST_3_12 or version_info >= (3, 11) ''' :data:`True` only if the active Python interpreter targets at least Python 3.11.0. ''' #FIXME: After dropping Python 3.9 support: #* Refactor all code conditionally testing this global to be unconditional. #* Remove this global. IS_PYTHON_AT_MOST_3_10 = not IS_PYTHON_AT_LEAST_3_11 ''' :data:`True` only if the active Python interpreter targets at most Python 3.10.x. ''' #FIXME: After dropping Python 3.9 support: #* Refactor all code conditionally testing this global to be unconditional. #* Remove this global. #* Remove all decorators resembling: # @skip_if_python_version_less_than('3.10.0') IS_PYTHON_AT_LEAST_3_10 = IS_PYTHON_AT_LEAST_3_11 or version_info >= (3, 10) ''' :data:`True` only if the active Python interpreter targets at least Python 3.10.0. ''' #FIXME: After dropping Python 3.9 support: #* Refactor all code conditionally testing this global to be unconditional. #* Remove this global. IS_PYTHON_AT_MOST_3_9 = not IS_PYTHON_AT_LEAST_3_10 ''' :data:`True` only if the active Python interpreter targets at most Python 3.9.x. ''' #FIXME: After dropping Python 3.8 support: #* Refactor all code conditionally testing this global to be unconditional. #* Remove this global. #* Remove all decorators resembling: # @skip_if_python_version_less_than('3.9.0') IS_PYTHON_AT_LEAST_3_9 = IS_PYTHON_AT_LEAST_3_10 or version_info >= (3, 9) ''' :data:`True` only if the active Python interpreter targets at least Python 3.9.0. ''' #FIXME: After dropping Python 3.8 support: #* Refactor all code conditionally testing this global to be unconditional. #* Remove this global. IS_PYTHON_AT_MOST_3_8 = not IS_PYTHON_AT_LEAST_3_9 ''' :data:`True` only if the active Python interpreter targets at most Python 3.8.x. ''' #FIXME: After dropping Python 3.8 support: #* Refactor all code conditionally testing this global to be unconditional. #* Remove this global. IS_PYTHON_3_8 = version_info[:2] == (3, 8) ''' :data:`True` only if the active Python interpreter targets exactly Python 3.8.x. ''' # ....................{ GETTERS }.................... def get_python_version_major_minor() -> str: ''' ``"."``-delimited major and minor version of the active Python interpreter (e.g., ``3.11``, ``3.7``), excluding the patch version of this interpreter. ''' # Heroic one-liners are an inspiration to us all. return f'{version_info[0]}.{version_info[1]}' beartype-0.18.5/beartype/_util/py/utilpyweakref.py000066400000000000000000000175531461113517100222570ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **weak reference** (i.e., references to objects explicitly allowing those objects to be garbage-collected at *any* time) utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilPythonWeakrefException from beartype.typing import ( Tuple, ) from weakref import ref as weakref_ref # ....................{ GETTERS }.................... def make_obj_weakref_and_repr(obj: object) -> Tuple[object, str]: ''' 2-tuple ``(weakref, repr)`` weakly referring to the passed object. Parameters ---------- obj : object Arbitrary object to be weakly referred to. Returns ---------- Tuple[object, str] 2-tuple ``(weakref, repr)`` weakly referring to this object such that: * ``weakref`` is either: * If this object supports weak references, a **weak reference** (i.e., :class:`weakref.ref` instance) to this object. * If this object prohibits weak references (e.g., due to being a common C-based variable-sized container like a tuple or string), ``None``. * ``repr`` is the machine-readable representation of this object, truncated to ~10KB to minimize space consumption in the worst case of an obscenely large object. ''' # Avoid circular import dependencies. from beartype._util.text.utiltextrepr import represent_object # Weak reference to this object if this object supports weak references *OR* # "None" otherwise (e.g., if this object is a variable-sized container). obj_weakref = None # Machine-readable representation of this object truncated to minimize space # consumption for the worst case of an obscenely large object. obj_repr = represent_object( obj=obj, # Store at most 1KB of the full representation, which should # certainly suffice for most use cases. Note that the # default of 96B is far too small to be useful here. max_len=1000, ) # If this object is "None", substitute "None" for this non-"None" # placeholder. Since the "weakref.ref" class ambiguously returns "None" when # this object has already been garbage-collected, this placeholder enables # subsequent calls to the get_obj_weakref_or_repr() getter to disambiguate # between these two common edge cases. if obj is None: obj_weakref = _WEAKREF_NONE # Else, this object is *NOT* "None". In this case... else: # Attempt to classify a weak reference to this object for safety. try: obj_weakref = weakref_ref(obj) # If doing so raises a "TypeError", this object *CANNOT* be weakly # referred to. Sadly, builtin variable-sized C-based types (e.g., # "dict", "int", "list", "tuple") *CANNOT* be weakly referred to. This # constraint is officially documented by the "weakref" module: # Several built-in types such as list and dict do not directly # support weak references but can add support through subclassing. # CPython implementation detail: Other built-in types such as tuple # and int do not support weak references even when subclassed. # # Since this edge case is common, permitting this exception to unwind # the call stack is unacceptable; likewise, even coercing this exception # into non-fatal warnings would generic excessive warning spam and is # thus also unacceptable. The only sane solution remaining is to # silently store the machine-readable representation of this object and # return that rather than this object from the "object" property. except TypeError: pass return obj_weakref, obj_repr def get_weakref_obj_or_repr(obj_weakref: object, obj_repr: str) -> object: ''' Object weakly referred to by the passed object if this object is indeed a weak reference to another existing object *or* the passed machine-readable representation otherwise (i.e., if this object is either ``None`` *or* is a weak reference to a dead garbage-collected object). This function is typically passed the pair of objects returned by a prior call to the companion :func:`make_obj_weakref_and_repr` function. Parameters ---------- obj_weakref : object Either: * If the **referent** (i.e., target object being weakly referred to) is the ``None`` singleton, the :data:`_WEAKREF_NONE` placeholder. * Else if the referent supports weak references, a **weak reference** (i.e., :class:`weakref.ref` instance) to that object. * Else, ``None``. obj_repr : str Machine-readable representation of that object, typically truncated to some number of characters to avoid worst-case space consumption. Returns ---------- object Either: * If this weak reference is the :data:`_WEAKREF_NONE` placeholder, the ``None`` singleton. * Else if this referent support weak references, either: * If this referent is still alive (i.e., has yet to be garbage-collected), this referent. * Else, this referent is now dead (i.e., has already been garbage-collected). In this case, the passed representation. * Else, this referent does *not* support weak references (i.e., this weak reference is ``None``). In this case, the passed representation. Raises ---------- _BeartypeUtilPythonWeakrefException If ``obj_weakref`` is invalid: i.e., neither ``None``, :data:`_WEAKREF_NONE`, nor a weak reference. ''' assert isinstance(obj_repr, str), f'{repr(obj_repr)} not string.' # If this weak reference is "None", the prior call to # make_obj_weakref_and_repr() was passed an object that could *NOT* be # weakly referred to (e.g., C-based container). In this case, fallback to # the machine-readable representation of that object. if obj_weakref is None: return obj_repr # Else, this weak reference is *NOT* "None". # # If this weak reference is "_WEAKREF_NONE", the prior call to # make_obj_weakref_and_repr() was passed the "None" singleton. In this case, # substitute this placeholder for "None". See that factory. elif obj_weakref is _WEAKREF_NONE: return None # Else, this weak reference is *NOT* that placeholder. # # If this weak reference is *NOT* a weak reference, raise an exception. elif not isinstance(obj_weakref, weakref_ref): raise _BeartypeUtilPythonWeakrefException( f'Weak reference {repr(obj_weakref)} invalid ' f'(i.e., neither weak reference, "None", nor "_WEAKREF_NONE").' ) # Else, this weak reference is a weak reference. # Object weakly referred to by this weak reference if this object is alive # *OR* "None" otherwise (i.e., if this object was garbage-collected). obj = obj_weakref() # Return either... return ( # If this object is still alive, this object; obj if obj is not None else # Else, this object is now dead. In this case, the machine-readable # representation of this object instead. obj_repr ) # ....................{ PROPERTIES ~ constants }.................... _WEAKREF_NONE = object() ''' Singleton substitute for the ``None`` singleton, enabling :class:`BeartypeCallHintViolation` exceptions to differentiate between weak references to ``None`` and weak references whose referents are already dead (i.e., have already been garbage-collected). ''' beartype-0.18.5/beartype/_util/py/utilpyword.py000066400000000000000000000044161461113517100216000ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **Python word size** (i.e., bit length of Python variables of internal type ``Py_ssize_t`` under the active Python interpreter) utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from sys import maxsize # ....................{ INTEGERS }.................... SHORT_MAX_32_BIT = 1 << 32 ''' Maximum value of **32-bit Python shorts** (i.e., integer variables of internal type ``Py_ssize_t`` under 32-bit Python interpreters roughly corresponding to the ``long`` C type under 32-bit machines, confusingly). This value is suitable for comparison with :attr:`sys.maxsize`, the maximum value of such variables under the active Python interpreter. ''' # ....................{ BOOLEANS }.................... IS_WORD_SIZE_64 = maxsize > SHORT_MAX_32_BIT ''' :data:`True` only if the active Python interpreter is **64-bit** (i.e., was compiled with a 64-bit toolchain into a 64-bit executable). Equivalently, this is :data:`True` only if the maximum value of Python shorts under this interpreter is larger than the maximum value of 32-bit Python shorts. While obtuse, this test is well-recognized by the Python community as the best means of testing this portably. Valid but worse alternatives include: * ``'PROCESSOR_ARCHITEW6432' in os.environ``, which depends upon optional environment variables and hence is clearly unreliable. * ``platform.architecture()[0] == '64bit'``, which fails under: * macOS, returning ``64bit`` even when the active Python interpreter is a 32-bit executable binary embedded in a so-called "universal binary." ''' # ....................{ INTEGERS ~ more }.................... WORD_SIZE = 64 if IS_WORD_SIZE_64 else 32 ''' Bit length of **Python shorts** (i.e., integer variables of internal type ``Py_ssize_t`` roughly corresponding to the ``long`` C type, confusingly). This integer is guaranteed to be either: * If the active Python interpreter is 64-bit, ``64``. * Else, ``32``. ''' beartype-0.18.5/beartype/_util/text/000077500000000000000000000000001461113517100173335ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/text/__init__.py000066400000000000000000000000001461113517100214320ustar00rootroot00000000000000beartype-0.18.5/beartype/_util/text/utiltextansi.py000066400000000000000000000263311461113517100224470ustar00rootroot00000000000000# --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **ANSI utilities** (i.e., low-level callables handling ANSI escape sequences colouring arbitrary strings). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._data.hint.datahinttyping import BoolTristate from re import compile as re_compile # ....................{ CONSTANTS }.................... ANSI_RESET = '\033[0m' ''' ANSI escape sequence resetting the effect of all prior ANSI sequence sequences, effectively "undoing" all colors and styles applied by those sequences. ''' # ....................{ CONSTANTS ~ color }.................... COLOR_BLUE = '\033[34m' ''' ANSI escape sequence colouring all subsequent characters as blue. ''' COLOR_CYAN = '\033[36m' ''' ANSI escape sequence colouring all subsequent characters as **cyan** (i.e., light blue). ''' COLOR_GREEN = '\033[32m' ''' ANSI escape sequence colouring all subsequent characters as **green**. ''' COLOR_MAGENTA = '\033[35m' ''' ANSI escape sequence colouring all subsequent characters as **magenta** (i.e., purple, dark blue). ''' COLOR_RED = '\033[31m' ''' ANSI escape sequence colouring all subsequent characters as **red**. ''' COLOR_YELLOW = '\033[33m' ''' ANSI escape sequence colouring all subsequent characters as **yellow**. ''' # ....................{ CONSTANTS ~ style }.................... STYLE_BOLD = '\033[1m' ''' ANSI escape sequence stylizing all subsequent characters as bold. ''' # ....................{ TESTERS }.................... def is_str_ansi(text: str) -> bool: ''' :data:`True` only if the passed text contains one or more ANSI escape sequences. Parameters ---------- text : str Text to be tested. Returns ------- bool :data:`True` only if this text contains one or more ANSI escape sequences. ''' assert isinstance(text, str), f'{repr(text)} not string.' # Return true only this compiled regex matching ANSI escape sequences # returns a non-"None" match object when passed this text. return _ANSI_REGEX.search(text) is not None # ....................{ COLOURIZERS }.................... def color_hint( # Mandatory parameters. text: str, # Optional parameters. is_color: BoolTristate = True, ) -> str: ''' Colour the passed substring as a type hint if the passed tri-state colouring boolean instructs this function to do so. Parameters ---------- text : str Text to be coloured as a type hint. is_color : BoolTristate Tri-state colouring boolean governing ANSI usage. See the :attr:`beartype.BeartypeConf.is_color` attribute for further details. Defaults to :data:`True`. Returns ------- str This text conditionally coloured as a type hint. ''' assert isinstance(text, str), f'{repr(text)} not string.' # Return either... return ( # If this tri-state boolean instructs this function to colour this # string, this string coloured with ANSI; f'{STYLE_BOLD}{COLOR_GREEN}{text}{ANSI_RESET}' if _is_color(is_color) else # Else, this string uncoloured. text ) def color_pith( # Mandatory parameters. text: str, # Optional parameters. is_color: BoolTristate = True, ) -> str: ''' Colour the passed substring as a **pith representation** (i.e., machine-readable string describing the value of the object currently being type-checked, typically created by the :func:`beartype._util.text.utiltextrepr.represent_object` function) if the passed tri-state colouring boolean instructs this function to do so. Parameters ---------- text : str Text to be coloured as a representation. is_color : BoolTristate Tri-state colouring boolean governing ANSI usage. See the :attr:`beartype.BeartypeConf.is_color` attribute for further details. Defaults to :data:`True`. Returns ------- str This text conditionally coloured as a representation. ''' assert isinstance(text, str), f'{repr(text)} not string.' # Return either... return ( # If this tri-state boolean instructs this function to colour this # string, this string coloured with ANSI; f'{STYLE_BOLD}{COLOR_RED}{text}{ANSI_RESET}' if _is_color(is_color) else # Else, this string uncoloured. text ) def color_type( # Mandatory parameters. text: str, # Optional parameters. is_color: BoolTristate = True, ) -> str: ''' Colour the passed substring as a simple class if the passed tri-state colouring boolean instructs this function to do so. Parameters ---------- text : str Text to be coloured as a simple class. is_color : BoolTristate Tri-state colouring boolean governing ANSI usage. See the :attr:`beartype.BeartypeConf.is_color` attribute for further details. Defaults to :data:`True`. Returns ------- str This text conditionally coloured as a simple class. ''' assert isinstance(text, str), f'{repr(text)} not string.' # Return either... return ( # If this tri-state boolean instructs this function to colour this # string, this string coloured with ANSI; f'{STYLE_BOLD}{COLOR_YELLOW}{text}{ANSI_RESET}' if _is_color(is_color) else # Else, this string uncoloured. text ) # ....................{ COLOURIZERS ~ name }.................... def color_attr_name( # Mandatory parameters. text: str, # Optional parameters. is_color: BoolTristate = True, ) -> str: ''' Colour the passed substring as a **Python identifier** (e.g., possibly fully-qualified name of a module, class, callable, or variable name) if the passed tri-state colouring boolean instructs this function to do so. Parameters ---------- text : str Text to be coloured as a Python identifier. is_color : BoolTristate Tri-state colouring boolean governing ANSI usage. See the :attr:`beartype.BeartypeConf.is_color` attribute for further details. Defaults to :data:`True`. Returns ------- str This text conditionally coloured as a Python identifier. ''' assert isinstance(text, str), f'{repr(text)} not string.' # Return either... return ( # If this tri-state boolean instructs this function to colour this # string, this string coloured with ANSI; f'{STYLE_BOLD}{COLOR_MAGENTA}{text}{ANSI_RESET}' if _is_color(is_color) else # Else, this string uncoloured. text ) def color_arg_name( # Mandatory parameters. text: str, # Optional parameters. is_color: BoolTristate = True, ) -> str: ''' Colour the passed substring as an **argument name** (i.e., of the parameter of a :func:`beartype.beartype`-decorated callable currently being type-checked) if the passed tri-state colouring boolean instructs this function to do so. Parameters ---------- text : str Text to be coloured as an argument name. is_color : BoolTristate Tri-state colouring boolean governing ANSI usage. See the :attr:`beartype.BeartypeConf.is_color` attribute for further details. Defaults to :data:`True`. Returns ------- str This text coloured as an argument name. ''' assert isinstance(text, str), f'{repr(text)} not string.' # Return either... return ( # If this tri-state boolean instructs this function to colour this # string, this string coloured with ANSI; f'{STYLE_BOLD}{COLOR_BLUE}{text}{ANSI_RESET}' if _is_color(is_color) else # Else, this string uncoloured. text ) # ....................{ STRIPPERS }.................... #FIXME: Unit test up the "is_color" parameter. def strip_str_ansi( # Mandatory parameters. text: str, # Optional parameters. is_color: BoolTristate = False, ) -> str: ''' Strip *all* ANSI escape sequences from the passed string if the passed tri-state colouring boolean instructs this function to do so. Specifically: * If ``is_color is True``, this function silently reduces to a noop. * If ``is_color is False``, this function unconditionally strips all ANSI escape sequences from this string. * If ``is_color is None``, this function conditionally strips all ANSI escape sequences from this string only if standard output is currently attached to an interactive terminal. Parameters ---------- text : str Text to be stripped. is_color : BoolTristate Tri-state colouring boolean governing ANSI usage. See the :attr:`beartype.BeartypeConf.is_color` attribute for further details. Defaults to :data:`False`. Returns ------- str This text conditionally stripped of ANSI. ''' assert isinstance(text, str), f'{repr(text)} not string.' # Return either... return ( # If this tri-state boolean instructs this function to preserve all ANSI # in this string, this string unmodified; text if _is_color(is_color) else # Else, this string stripped of all ANSI. _ANSI_REGEX.sub('', text) ) # ....................{ PRIVATE ~ constants }.................... _ANSI_REGEX = re_compile(r'\033\[[0-9;?]*[A-Za-z]') ''' Compiled regular expression matching a single ANSI escape sequence. ''' # ....................{ PRIVATE ~ testers }.................... def _is_color(is_color: BoolTristate) -> bool: ''' Reduce the passed tri-state colouring boolean governing ANSI usage to a simple boolean. Specifically, this tester returns either: * :data:`True` only if the passed ``is_color`` parameter is either: * :data:`True`. * :data:`None` and standard output is currently attached to an interactive POSIX-compliant terminal. Note that this is the common case, as the :attr:`beartype.BeartypeConf.is_color` attribute underlying this parameter typically defaults to :data:`None`. * :data:`False` otherwise. Parameters ---------- is_color : BoolTristate Tri-state colouring boolean governing ANSI usage. See the :attr:`beartype.BeartypeConf.is_color` attribute for further details. ''' assert isinstance(is_color, bool) or is_color is None, ( # <-- "NoneTypeOr" is unavailable here f'{repr(is_color)} not tri-state boolean.') # Avoid circular import dependencies. from beartype._util.os.utilostty import is_stdout_terminal # Return true only if the passed tri-state boolean is either... return ( # True *OR*... is_color is True or # "None" and standard output is currently attached to an interactive # POSIX-compliant terminal. (is_color is None and is_stdout_terminal()) ) beartype-0.18.5/beartype/_util/text/utiltextget.py000066400000000000000000000072701461113517100222750ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **string getters** (i.e., low-level callables slicing and returning substrings out of arbitrary strings, typically to acquire prefixes and suffixes satisfying various conditions). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... # from beartype.roar._roarexc import _BeartypeUtilTextException # ....................{ GETTERS }.................... #FIXME: Uncomment if ever needed. # def get_str_prefix_greedy(text: str, anchor: str) -> str: # ''' # **Greedily anchored prefix** (i.e., substring ranging from the first # character to the last instance of the passed substring) of the passed # string if any *or* raise an exception otherwise (i.e., if this string # contains no such substring). # # Parameters # ---------- # text : str # String to be searched. # anchor: str # Substring to search this string for. # # Returns # ---------- # str # Prefix of this string preceding the last instance of this substring. # # Raises # ---------- # _BeartypeUtilTextException # If this string contains *no* instance of this substring. # # See Also # ---------- # :func:`get_str_prefix_greedy_or_none` # Further details. # ''' # # # Greedily anchored prefix of this string if any *OR* "None" otherwise. # text_prefix_greedy = get_str_prefix_greedy_or_none(text, anchor) # # # If this string contains *NO* such prefix, raise an exception. # if text_prefix_greedy is None: # raise _BeartypeUtilTextException( # f'String "{text}" substring "{anchor}" not found.') # # # Else, return this prefix. # return text_prefix_greedy #FIXME: Uncomment if ever needed. # def get_str_prefix_greedy_or_none(text: str, anchor: str) -> 'Optional[str]': # ''' # **Greedily anchored prefix** (i.e., substring ranging from the first # character to the last instance of the passed substring) of the passed # string if any *or* ``None`` otherwise (i.e., if this string contains no # such substring). # # Parameters # ---------- # text : str # String to be searched. # anchor: str # Substring to search this string for. # # Returns # ---------- # Optional[str] # Either: # # * If this string contains this substring, the prefix of this string # preceding the last instance of this substring. # * Else, ``None``. # # Examples # ---------- # >>> from beartype._util.text.utiltextget import ( # ... get_str_prefix_greedy_or_none) # >>> get_str_prefix_greedy_or_none( # ... text='Opposition...contradiction...premonition...compromise.', # ... anchor='.') # Opposition...contradiction...premonition...compromise # >>> get_str_prefix_greedy_or_none( # ... text='This is an anomaly. Disabled. What is true?', # ... anchor='!') # None # ''' # assert isinstance(text, str), f'{repr(text)} not string.' # assert isinstance(anchor, str), f'{repr(anchor)} not string.' # # # Return either... # return ( # # If this string contains this substring, the substring of this string # # preceding the last instance of this substring in this string. # text[:text.rindex(anchor)] # if anchor in text else # # Else, "None". # None # ) beartype-0.18.5/beartype/_util/text/utiltextidentifier.py000066400000000000000000000152701461113517100236370ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **Python identifier utilities** (i.e., low-level callables handling unqualified and qualified attribute, callable, class, module, and variable names). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilTextIdentifierException from beartype._data.hint.datahinttyping import TypeException # ....................{ RAISERS }.................... def die_unless_identifier( # Mandatory parameters. text: str, # Optional parameters. exception_cls: TypeException = _BeartypeUtilTextIdentifierException, exception_prefix: str = '', ) -> None: ''' Raise an exception unless the passed string is a valid **Python attribute name** (i.e., ``.``-delimited concatenation of one or more :pep:`3131`-compliant syntactically valid Python identifiers, including the names of attributes, callables, classes, modules, and variables). Parameters ---------- text : str String to be validated. exception_cls : Type[Exception] Type of exception to be raised in the event of a fatal error. Defaults to :exc:`._BeartypeUtilTextIdentifierException`. exception_prefix : str, optional Human-readable label prefixing the representation of this string in the exception message. Defaults to the empty string. Raises ------ exception_cls If this string is *not* a valid Python attribute name. See Also -------- :func:`.is_identifier` Further details. ''' # If this string is *NOT* a valid Python attribute name, raise an exception. if not (isinstance(text, str) and is_identifier(text)): assert isinstance(exception_cls, type), ( 'f{repr(exception_cls)} not exception class.') assert isinstance(exception_prefix, str), ( 'f{repr(exception_prefix)} not string.') raise exception_cls( f'{exception_prefix}{repr(text)} not valid Python attribute name.') # Else, this string is a valid Python attribute name. # ....................{ TESTERS }.................... def is_dunder(text: str) -> bool: ''' :data:`True` only if the passed string vaguely conforms to the name of a **dunder attribute** (i.e., attribute whose name is both prefixed and suffixed by ``"__"`` double underscore substrings). Parameters ---------- text : str String to be inspected. Returns ------- bool :data:`True` only if this string conforms to a dunder attribute name. ''' assert isinstance(text, str), f'{repr(text)} not string.' # Return us up the powerful one-liner of power. return text.startswith('__') and text.endswith('__') def is_identifier(text: str) -> bool: ''' :data:`True` only if the passed string is a valid **Python attribute name** (i.e., ``.``-delimited concatenation of one or more :pep:`3131`-compliant syntactically valid Python identifiers, including the names of attributes, callables, classes, modules, and variables). This tester is suitable for detecting whether this string is the fully-qualified name of an arbitrary Python object. Caveats ---------- **This tester is mildly slow,** due to unavoidably splitting this string on ``.`` delimiters and iteratively passing each of the split substrings to the :meth:`str.isidentifier` builtin. Due to the following caveat, this inefficiency is unavoidable. **This tester is not optimizable with regular expressions** -- at least, not trivially. Technically, this tester *can* be optimized by leveraging the "General Category" of Unicode filters provided by the third-party :mod:`regex` package. Practically, doing so would require the third-party :mod:`regex` package and would still almost certainly fail in edge cases. Why? Because Python 3 permits Python identifiers to contain Unicode letters and digits in the "General Category" of Unicode code points, which is extremely non-trivial to match with the standard :mod:`re` module. Parameters ---------- text : str String to be inspected. Returns ------- bool :data:`True` only if this string is the ``.``-delimited concatenation of one or more syntactically valid Python identifiers. ''' assert isinstance(text, str), f'{repr(text)} not string.' # If this text contains *NO* "." delimiters and is thus expected to be an # unqualified Python identifier, return true only if this is the case. if '.' not in text: return text.isidentifier() # Else, this text contains one or more "." delimiters and is thus expected # to be a qualified Python identifier. # Return true only if *ALL* "."-delimited substrings split from this string # are valid unqualified Python identifiers. Note that: # * Regular expressions report false negatives. See the docstring. # * Manual iteration is significantly faster than "all(...)"- and # "any(...)"-style comprehensions. # * This approach correctly handles *ALL* edge cases, including when: # * This string is simply the "." character. In this case: # >>> '.'.split('.') # ['', ''] # Since the empty string is *NOT* a valid Python identifier, this # iteration immediately returns false as expected. # * There exists an alternative and significantly more computationally # expensive means of testing this condition, employed by the # typing.ForwardRef.__init__() method to valid the validity of the passed # relative classname: # # Needless to say, we'll never be doing this. # try: # all( # compile(identifier, '', 'eval') # for identifier in text.split('.') # ) # return True # except SyntaxError: # return False for text_basename in text.split('.'): # If this "."-delimited substring is *NOT* a valid unqualified Python # identifier, return false. if not text_basename.isidentifier(): return False # Else, this "."-delimited substring is a valid unqualified Python # identifier. In this case, silently continue to the next. # Return true, since *ALL* "."-delimited substrings split from this string # are valid unqualified Python identifiers. return True beartype-0.18.5/beartype/_util/text/utiltextjoin.py000066400000000000000000000224451461113517100224560ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **string joining utilities** (i.e., callables joining passed strings into new strings delimited by passed substring delimiters). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import Iterable as typing_Iterable from beartype._data.hint.datahinttyping import IterableStrs from collections.abc import ( Iterable, Sequence, ) # ....................{ JOINERS }.................... #FIXME: Unit test the "is_double_quoted" parameter, please. def join_delimited( # Mandatory parameters. strs: IterableStrs, # Mandatory keyword-only parameters. *, delimiter_if_two: str, delimiter_if_three_or_more_nonlast: str, delimiter_if_three_or_more_last: str, # Optional keyword-only parameters. is_double_quoted: bool = False, ) -> str: ''' Concatenate the passed iterable of zero or more strings delimited by the passed delimiter (conditionally depending on both the length of this sequence and index of each string in this sequence), yielding a human-readable string listing arbitrarily many substrings. Specifically, this function returns either: * If this iterable contains no strings, the empty string. * If this iterable contains one string, this string as is is unmodified. * If this iterable contains two strings, these strings delimited by the passed ``delimiter_if_two`` delimiter. * If this iterable contains three or more strings, a string listing these contained strings such that: * All contained strings except the last two are suffixed by the passed ``delimiter_if_three_or_more_nonlast`` delimiter. * The last two contained strings are delimited by the passed ``delimiter_if_three_or_more_last`` separator. Parameters ---------- strs : Iterable[str] Iterable of all strings to be joined. delimiter_if_two : str Substring separating each string contained in this iterable if this iterable contains exactly two strings. delimiter_if_three_or_more_nonlast : str Substring separating each string *except* the last two contained in this iterable if this iterable contains three or more strings. delimiter_if_three_or_more_last : str Substring separating each string the last two contained in this iterable if this iterable contains three or more strings. is_double_quoted : bool, optional :data:`True` only if **double-quoting** (i.e., both prefixing and suffixing by the ``"`` character) each item of this iterable. Defaults to :data:`False`. Returns ------- str Concatenation of these strings. Examples -------- >>> join_delimited( ... strs=('Fulgrim', 'Perturabo', 'Angron', 'Mortarion'), ... delimiter_if_two=' and ', ... delimiter_if_three_or_more_nonlast=', ', ... delimiter_if_three_or_more_last=', and ', ... ) 'Fulgrim, Perturabo, Angron, and Mortarion' ''' assert isinstance(strs, Iterable) and not isinstance(strs, str), ( f'{repr(strs)} not non-string iterable.') assert isinstance(delimiter_if_two, str), ( f'{repr(delimiter_if_two)} not string.') assert isinstance(delimiter_if_three_or_more_nonlast, str), ( f'{repr(delimiter_if_three_or_more_nonlast)} not string.') assert isinstance(delimiter_if_three_or_more_last, str), ( f'{repr(delimiter_if_three_or_more_last)} not string.') # If this iterable is *NOT* a sequence, internally coerce this iterable # into a sequence for subsequent indexing purposes. if not isinstance(strs, Sequence): strs = tuple(strs) # Else, this iterable is already a sequence. # # In either case, this iterable is now a sequence. # If double-quoting these strings, do so. if is_double_quoted: strs = tuple(f'"{text}"' for text in strs) # Else, preserve these strings as is. # Number of strings in this sequence. num_strs = len(strs) # If no strings are passed, return the empty string. if num_strs == 0: return '' # If one string is passed, return this string as is. elif num_strs == 1: # This is clearly a string, yet mypy thinks it's Any return strs[0] # type: ignore[no-any-return] # If two strings are passed, return these strings delimited appropriately. elif num_strs == 2: return f'{strs[0]}{delimiter_if_two}{strs[1]}' # Else, three or more strings are passed. # All such strings except the last two, delimited appropriately. strs_nonlast = delimiter_if_three_or_more_nonlast.join(strs[0:-2]) # The last two such strings, delimited appropriately. strs_last = f'{strs[-2]}{delimiter_if_three_or_more_last}{strs[-1]}' # Return these two substrings, delimited appropriately. return f'{strs_nonlast}{delimiter_if_three_or_more_nonlast}{strs_last}' # ....................{ JOINERS ~ conjunction }.................... #FIXME: Unit test us up, please. def join_delimited_conjunction(strs: IterableStrs, **kwargs) -> str: ''' Concatenate the passed iterable of zero or more strings delimited by commas and/or the conjunction "and" (conditionally depending on both the length of this iterable and index of each string in this iterable), yielding a human-readable string listing arbitrarily many substrings conjunctively. Specifically, this function returns either: * If this iterable contains no strings, the empty string. * If this iterable contains one string, this string as is is unmodified. * If this iterable contains two strings, these strings delimited by the conjunction "and". * If this iterable contains three or more strings, a string listing these contained strings such that: * All contained strings except the last two are suffixed by commas. * The last two contained strings are delimited by the conjunction "and". Parameters ---------- strs : Iterable[str] Iterable of all strings to be concatenated conjunctively. All remaining keyword parameters are passed as is to the lower-level :func:`.join_delimeted` function underlying this higher-level function. Returns ------- str Conjunctive concatenation of these strings. ''' # One of us. We accept one-liner. One of us. return join_delimited( strs=strs, delimiter_if_two=' and ', delimiter_if_three_or_more_nonlast=', ', delimiter_if_three_or_more_last=', and ', **kwargs ) # ....................{ JOINERS ~ disjunction }.................... def join_delimited_disjunction(strs: IterableStrs, **kwargs) -> str: ''' Concatenate the passed iterable of zero or more strings delimited by commas and/or the disjunction "or" (conditionally depending on both the length of this iterable and index of each string in this iterable), yielding a human-readable string listing arbitrarily many substrings disjunctively. Specifically, this function returns either: * If this iterable contains no strings, the empty string. * If this iterable contains one string, this string as is is unmodified. * If this iterable contains two strings, these strings delimited by the disjunction "or". * If this iterable contains three or more strings, a string listing these contained strings such that: * All contained strings except the last two are suffixed by commas. * The last two contained strings are delimited by the disjunction "or". Parameters ---------- strs : Iterable[str] Iterable of all strings to be concatenated disjunctively. All remaining keyword parameters are passed as is to the lower-level :func:`.join_delimeted` function underlying this higher-level function. Returns ------- str Disjunctive concatenation of these strings. ''' # He will join us... OR DIE! *cackling heard* return join_delimited( strs=strs, delimiter_if_two=' or ', delimiter_if_three_or_more_nonlast=', ', delimiter_if_three_or_more_last=', or ', **kwargs ) def join_delimited_disjunction_types(types: typing_Iterable[type]) -> str: ''' Concatenate the human-readable classname of each class in the passed iterable delimited by commas and/or the disjunction "or" (conditionally depending on both the length of this iterable and index of each string in this iterable), yielding a human-readable string listing arbitrarily many classnames disjunctively. Parameters ---------- types : Iterable[type] Iterable of all classes whose human-readable classnames are to be concatenated disjunctively. Returns ------- str Disjunctive concatenation of these classnames. ''' # Avoid circular import dependencies. from beartype._util.text.utiltextlabel import label_type # Make it so, ensign. return join_delimited_disjunction(label_type(cls) for cls in types) beartype-0.18.5/beartype/_util/text/utiltextlabel.py000066400000000000000000000421651461113517100225770ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **text label utilities** (i.e., low-level callables creating and returning human-readable strings describing prominent objects or types, intended to be embedded in human-readable error messages). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._data.hint.datahinttyping import ( BeartypeableT, BoolTristate, ) from beartype._util.utilobject import ( get_object_name, get_object_type_name, ) from collections.abc import Callable # ....................{ LABELLERS ~ beartypeable }.................... def label_beartypeable_kind( obj: BeartypeableT, # pyright: ignore[reportInvalidTypeVarUse] ) -> str: ''' Human-readable label describing the **kind** (i.e., single concise noun synopsizing the category of) of the passed **beartypeable** (i.e., object that is currently being or has already been decorated by the :func:`beartype.beartype` decorator). Parameters ---------- obj : BeartypeableT Beartypeable to describe the kind of. Returns ------- str Human-readable label describing the kind of this beartypeable. ''' # Avoid circular import dependencies. from beartype._util.func.utilfunctest import ( is_func_async, is_func_async_generator, is_func_coro, is_func_python, is_func_sync_generator, ) from beartype._util.func.arg.utilfuncargget import ( get_func_arg_first_name_or_none) #FIXME: Globalize magic strings for efficiency, please. # If this object is a pure-Python class, return an appropriate string. if isinstance(obj, type): return 'class' # Else, this object is *NOT* a pure-Python class. # # If this object is a pure-Python callable... elif is_func_python(obj): # Human-readable prefix describing the exotic nature of this callable if # this is callable is exotic (e.g., coroutine or generator factory) # suffixed by trailing whitespace *OR* the empty string otherwise. func_prefix = '' # Human-readable suffix describing the general nature of this callable # (e.g., function, method) suffixed by trailing whitespace. func_suffix = '' # If this object is an asynchronous callable factory... if is_func_async(obj): # If this object is a coroutine factory, use an appropriate prefix. if is_func_coro(obj): func_prefix = 'coroutine factory ' # If this object is an asynchronous generator factory, use an # appropriate prefix. elif is_func_async_generator(obj): func_prefix = 'asynchronous generator factory ' # Else, this object is an unrecognized kind of asynchronous callable # factory. In this case, fallback to a generic prefix. # # Note that this should *NEVER* occur. Since *ALL* asynchronous # callable factories are either coroutine or asynchronous generator # factories, one of the above conditional branches should have been # entered instead. Nonetheless, preparation prevents disasters. else: # pragma: no cover func_prefix = 'asynchronous ' # Else, this object is a synchronous callable. # # If this object is an synchronous generator factory, use an appropriate # prefix. elif is_func_sync_generator(obj): func_prefix = 'generator factory ' # Else, this object is a standard synchronous callable. In this case, # avoid prefixing this callable by a leading substring. # Name of the first parameter accepted by that callable if any *OR* # "None" otherwise (i.e., if that callable is argumentless). arg_first_name = get_func_arg_first_name_or_none(obj) # If this is the canonical first "self" parameter typically accepted by # instance methods, assume this to be an instance method. # # Note that this heuristic fails in uncommon edge cases -- but that # that's largely irrelevant here. This function is *ONLY* intended to # generate human-readable exception and warning messages. Since this is # hardly mission-critical, false positives are reluctantly acceptable. if arg_first_name == 'self': func_suffix = 'method' # Else, this is *NOT* the canonical first "self" parameter. # # If this is the canonical first "cls" parameter typically accepted by # class methods, assume this to be a class method. elif arg_first_name == 'cls': func_suffix = 'class method' # Else, this is neither the canonical first "self" nor "cls" parameter. # In this case, this is assumed to be a non-method callable. else: func_suffix = 'function' # Return the concatenation of these substrings. # print(f'func_prefix: {func_prefix}; func_suffix: {func_suffix}') return f'{func_prefix}{func_suffix}' # Else, this object is neither a pure-Python class *NOR* callable. # Return a sane placeholder. return 'object' # ....................{ LABELLERS ~ callable }.................... #FIXME: Unit test up the "is_context" parameter, which is currently untested. def label_callable( # Mandatory parameters. func: Callable, # Optional parameters. is_color: BoolTristate = False, is_context: BoolTristate = None, ) -> str: ''' Human-readable label describing the passed **callable** (e.g., function, method, property). Parameters ---------- func : Callable Callable to be labelled. is_color : BoolTristate Tri-state colouring boolean governing ANSI usage. See the :attr:`beartype.BeartypeConf.is_color` attribute for further details. Defaults to :data:`False`. is_context : BoolTristate Either: * :data:`True`, in which case this label is suffixed by additional metadata contextually disambiguating that callable, including: * The line number of the first line declaring that callable in its underlying source code module file. * The absolute filename of that file. * :data:`False`, in which case this label is *not* suffixed by such metadata. * :data:`None`, in which case this label is conditionally suffixed by such metadata only if that callable is a lambda function and thus ambiguously lacks any semblance of an innate context. Defaults to :data:`None`. Returns ------- str Human-readable label describing this callable. ''' assert callable(func), f'{repr(func)} uncallable.' assert isinstance(is_context, bool) or is_context is None, ( # <-- "NoneTypeOr" is unavailable here f'{repr(is_context)} not tri-state boolean.') # Avoid circular import dependencies. from beartype._util.func.arg.utilfuncargget import ( get_func_args_flexible_len) from beartype._util.func.utilfunccodeobj import get_func_codeobj from beartype._util.func.utilfunctest import is_func_lambda from beartype._util.text.utiltextansi import color_attr_name # Substring prefixing the string to be returned, typically identifying the # specialized type of that callable if that callable has a specialized type. func_label_prefix = '' # Fully-qualified name of that callable, coloured if requested. func_label = color_attr_name( text=f' {get_object_name(func)}()', is_color=is_color) # Substring suffixing the string to be returned, typically contextualizing # that callable with respect to its on-disk code module file. func_label_suffix = '' #FIXME: *HMM.* This branch should almost certainly be folded into the #existing label_beartypeable_kind() function, which would then dramatically #simplify this logic here. Let's do this, yo! # If the passed callable is a pure-Python lambda function, that callable # has *NO* unique fully-qualified name. In this case, return a string # uniquely identifying this lambda from various code object metadata. if is_func_lambda(func): # Code object underlying this lambda. func_codeobj = get_func_codeobj(func) # Substring preceding the string to be returned. func_label_prefix = ( f'lambda function of ' f'{get_func_args_flexible_len(func_codeobj)} argument(s)' ) # If the caller failed to request an explicit contextualization, default # to contextualizing this lambda function. if is_context is None: is_context = True # Else, the caller requested an explicit contextualization. In this # case, preserve that contextualization as is. # Else, the passed callable is *NOT* a pure-Python lambda function and thus # has a unique fully-qualified name. In this case, prefix this label with a # substring describing the kind of that callable. else: func_label_prefix = label_beartypeable_kind(func) # If contextualizing that callable, just do it already. Go, @beartype! Go! if is_context: func_label_suffix = f' {label_object_context(func)}' # Else, we are *NOT* contextualizing that callable. # Return that prefix followed by the fully-qualified name of that callable. return f'{func_label_prefix}{func_label}{func_label_suffix}' # ....................{ LABELLERS ~ exception }.................... def label_exception(exception: Exception) -> str: ''' Human-readable label describing the passed exception. Caveats ------- **The label returned by this function does not describe the traceback originating this exception.** To do so, consider calling the standard :func:`traceback.format_exc` function instead. Parameters ---------- exception : Exception Exception to be labelled. Returns ------- str Human-readable label describing this exception. ''' assert isinstance(exception, Exception), ( f'{repr(exception)} not exception.') # Return the fully-qualified name of the class of this exception followed by # this exception's message. return f'{get_object_type_name(exception)}: {str(exception)}' # ....................{ LABELLERS ~ context }.................... #FIXME: Unit test us up, please. def label_object_context(obj: object) -> str: ''' Human-readable label describing the **context** (i.e., absolute filename of the module or script physically declaring the passed object *and* the 1-based line number of the first line declaring this object in this file) of this object if this object is either a callable or class declared on-disk *or* the empty string otherwise (i.e., if this object is neither a callable nor class *or* is either a callable or class declared in-memory). Parameters ---------- func : object Object to label the context of. Returns ------- str Human-readable label describing the context of this object. ''' # Defer test-specific imports. from beartype._util.utilobject import get_object_filename_or_none from beartype._util.module.utilmodget import ( get_object_module_line_number_begin) # Absolute filename of the module or script physically declaring this object # if this object was defined on-disk *OR* "None" otherwise (i.e., if this # object was defined in-memory). obj_filename = get_object_filename_or_none(obj) # If this object is defined on-disk... if obj_filename: # Line number of the first line declaring this object in that file. obj_lineno = get_object_module_line_number_begin(obj) # Return a string describing the context of this object. return f'in file "{obj_filename}" line {obj_lineno}' # Else, this object was defined in-memory. In this case, avoid attempting to # needlessly contextualize this object. # Let's hear it for giving up here and going home. Yeah! Go, @beartype! return '' # ....................{ LABELLERS ~ pith }.................... def label_pith_value( # Mandatory parameters. pith: object, # Optional parameters. is_color: BoolTristate = False, ) -> str: ''' Human-readable label describing the passed value of the **current pith** (i.e., arbitrary object violating the current type check) *not* suffixed by delimiting whitespace. Parameters ---------- pith : object Arbitrary object violating the current type check. is_color : BoolTristate Tri-state colouring boolean governing ANSI usage. See the :attr:`beartype.BeartypeConf.is_color` attribute for further details. Defaults to :data:`False`. Returns ------- str Human-readable label describing this pith value. ''' # Avoid circular import dependencies. from beartype._util.text.utiltextansi import color_pith from beartype._util.text.utiltextrepr import represent_object # Glory be to the one liner that you are about to read. return color_pith(text=represent_object(pith), is_color=is_color) # ....................{ LABELLERS ~ type }.................... def label_type( # Mandatory parameters. cls: type, # Optional parameters. is_color: BoolTristate = False, ) -> str: ''' Human-readable label describing the passed class. Parameters ---------- cls : type Class to be labelled. is_color : BoolTristate Tri-state colouring boolean governing ANSI usage. See the :attr:`beartype.BeartypeConf.is_color` attribute for further details. Defaults to :data:`False`. Returns ------- str Human-readable label describing this class. ''' assert isinstance(cls, type), f'{repr(cls)} not class.' # Avoid circular import dependencies. from beartype._util.cls.utilclstest import is_type_builtin from beartype._util.hint.pep.proposal.utilpep544 import ( is_hint_pep544_protocol) from beartype._util.text.utiltextansi import color_attr_name # Label to be returned, initialized to this class' fully-qualified name. classname = get_object_type_name(cls) # print(f'cls {cls} classname: {classname}') # If this name contains *NO* periods, this class is actually a builtin type # (e.g., "list"). Since builtin types are well-known and thus # self-explanatory, this name requires no additional labelling. In this # case, return this name as is. if '.' not in classname: pass # Else, this name contains one or more periods but could still be a # builtin indirectly accessed via the standard "builtins" module. # # If this name is that of a builtin type uselessly prefixed by the name of # the module declaring all builtin types (e.g., "builtins.list"), reduce # this name to the unqualified basename of this type (e.g., "list"). elif is_type_builtin(cls): classname = cls.__name__ # Else, this is a non-builtin class. Non-builtin classes are *NOT* # well-known and thus benefit from additional labelling. # # If this class is a PEP 544-compliant protocol supporting structural # subtyping, label this protocol. elif is_hint_pep544_protocol(cls): # print(f'cls {cls} is protocol!') classname = f'' # Else if this class is a standard abstract base class (ABC) defined by a # standard submodule also known to support structural subtyping (e.g., # "collections.abc.Hashable", "contextlib.AbstractContextManager"), label # this ABC as a protocol. # # Note that user-defined ABCs do *NOT* generally support structural # subtyping. Doing so requires esoteric knowledge of undocumented and # mostly private "abc.ABCMeta" metaclass internals unlikely to be # implemented by third-party developers. Thanks to the lack of both # publicity and standardization, there exists *NO* general-purpose means of # detecting whether an arbitrary class supports structural subtyping. elif ( classname.startswith('collections.abc.') or classname.startswith('contextlib.') ): classname = f'' # Else, this is a standard class. In this case, label this class as such. else: classname = f'' # Return this labelled classname, possibly coloured. return color_attr_name(text=classname, is_color=is_color) def label_object_type(obj: object) -> str: ''' Human-readable label describing the class of the passed object. Parameters ---------- obj : object Object whose class is to be labelled. Returns ------- str Human-readable label describing the class of this object. ''' # Tell me why, why, why I curse the sky! ...no, srsly. return label_type(type(obj)) beartype-0.18.5/beartype/_util/text/utiltextmunge.py000066400000000000000000000272611461113517100226330ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. """ Project-wide **string munging utilities** (i.e., callables transforming passed strings into new strings with generic string operations). This private submodule is *not* intended for importation by downstream callers. """ # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilTextException from beartype._data.kind.datakindtext import CHARS_PUNCTUATION # ....................{ CASERS }.................... #FIXME: Unit test us up, please. def lowercase_str_char_first(text: str) -> str: ''' Lowercase *only* the first character of the passed string. Parameters ---------- text : str String whose first character is to be lowercased. Returns ------- str This string with the first character lowercased. ''' assert isinstance(text, str), f'{repr(text)} not string.' # If... if ( # This string contains at least two characters *AND*... len(text) >= 2 and # The first character of this string is uppercase... text[0].isupper() ): # Then lowercase only this character for readability. text = f'{text[0].lower()}{text[1:]}' # Return this possibly changed string. return text def uppercase_str_char_first(text: str) -> str: ''' Uppercase *only* the first character of the passed string. Whereas the standard :meth:`str.capitalize` method both uppercases the first character of this string *and* lowercases all remaining characters, this function *only* uppercases the first character. All remaining characters remain unmodified. Parameters ---------- text : str String whose first character is to be uppercased. Returns ------- str This string with the first character uppercased. ''' assert isinstance(text, str), f'{repr(text)} not string.' # If... if ( # This string contains at least two characters *AND*... len(text) >= 2 and # The first character of this string is lowercase... text[0].islower() ): # Then uppercase only this character for readability. text = f'{text[0].upper()}{text[1:]}' # Return this possibly changed string. return text # ....................{ NUMBERERS }.................... def number_str_lines(text: str) -> str: ''' Passed string munged to prefix each line of this string with the 1-based number of that line padded by zeroes out to four digits for alignment. Parameters ---------- text : str String whose lines are to be numbered. Returns ------- str This string with all lines numbered. ''' assert isinstance(text, str), f'{repr(text)} not string.' # For radical benevolence! return '\n'.join( # Note that a format() call rather than f-string is intentionally # applied here, as the former affords more functionality for string # munging than the latter. '(line {:0>4d}) {}'.format(text_line_number, text_line) for text_line_number, text_line in enumerate(text.splitlines(), start=1) ) # ....................{ REPLACERS }.................... def replace_str_substrs(text: str, old: str, new: str) -> str: ''' Passed string with all instances of the passed source substring globally replaced by the passed target substring if this string contains at least one such instance *or* raise an exception otherwise (i.e., if this string contains *no* such instance). Caveats ------- **This higher-level function should always be called in lieu of the lower-level** :meth:`str.replace` method, which unconditionally succeeds regardless of whether this subject string contains at least one instance of this source substring or not. Parameters ---------- text : str Subject string to perform this global replacement on. old : str Source substring of this subject string to be globally replaced. new : str Target substring to globally replace this source substring with in this subject string. Returns ------- str Subject string with all instances of this source substring globally replaced by this target substring. Raises ------ _BeartypeUtilTextException If this subject string contains *no* instances of this source substring. Examples -------- >>> from beartype._util.text.utiltextmunge import replace_str_substrs >>> replace_str_substrs( ... text='And now the STORM-BLAST came, and he', ... old='he', new='hat') And now that STORM-BLAST came, and hat >>> replace_str_substrs( ... text='I shot the ALBATROSS.', old='dross', new='drat') beartype.roar._BeartypeUtilTextException: String "I shot the ALBATROSS." substring "dross" not found. ''' assert isinstance(text, str), f'{repr(text)} not string.' assert isinstance(old, str), f'{repr(old)} not string.' assert isinstance(new, str), f'{repr(new)} not string.' # If this subject contains *NO* instances of this substring, raise an # exception. if old not in text: raise _BeartypeUtilTextException( f'String "{text}" substring "{old}" not found.') # Else, this subject contains one or more instances of this substring. # Return this subject with all instances of this source substring globally # replaced by this target substring. return text.replace(old, new) # ....................{ SUFFIXERS }.................... def suffix_str_unless_suffixed(text: str, suffix: str) -> str: ''' Passed string either suffixed by the passed suffix if this string is not yet suffixed by this suffix *or* this string as is otherwise (i.e., if this string is already suffixed by this suffix). Parameters ---------- text : str String to be conditionally suffixed. suffix : str Suffix to be conditionally appended to this string. Returns ------- str Either: * If this string is *not* yet suffixed by this suffix, this string suffixed by this suffix. * Else, this string as is. ''' assert isinstance(text, str), f'{repr(text)} not string.' assert isinstance(suffix, str), f'{repr(suffix)} not string.' # Suffix us up the redemption arc. return text if text.endswith(suffix) else text + suffix # ....................{ TRUNCATERS }.................... def truncate_str( # Mandatory parameters. text: str, # Optional parameters. max_len: int = 96, ) -> str: ''' Truncate the passed string to the passed maximum string length. Specifically, this function returns either: * If the length of this string is less than this maximum, this string unmodified as is. * Else, this string with the suffix of this string exceeding this maximum replaced by an ASCII ellipsis (i.e., ``"..."`` substring). Caveats ------- **This function is unavoidably slow and should thus not be called from optimized performance-critical code.** This function internally performs mildly expensive operations, including iterating-based string munging. Ideally, this function should *only* be called to create user-oriented exception messages where performance is a negligible concern. Parameters ---------- obj : object String to be truncated. max_len: int, optional Maximum length of the string to be returned. Defaults to a standard line length of 100 characters minus output indentation of 4 characters. Returns ------- str This string possibly truncated. ''' assert isinstance(text, str), f'{repr(text)} not string.' assert isinstance(max_len, int), f'{repr(max_len)} not integer.' assert max_len >= 0, f'{max_len} < 0.' # If this maximum length is *NOT* long enough to at least allow truncation # to ellipsis (i.e., a substring of length 3). In this case, truncate this # string to this length *WITHOUT* ellipsis. if max_len <= 3: return text[:max_len] # Else, this maximum length is long enough to at least allow truncation to # ellipsis (i.e., a substring of length 3). # Length of this string. text_len = len(text) # If this string does *NOT* exceed this maximum length, this string requires # *NO* truncation. In this case, return this string as is. if text_len <= max_len: return text # Else, this string exceeds this maximum length and thus requires # truncation. # Length of this string minus one. text_len_minus_1 = text_len - 1 # Length of the truncated prefix of this string to be returned below, # initialized to this maximum length minus the length of the ellipsis (i.e., # 3 characters) to be injected into this string. text_prefix_len = max_len - 3 # 0-based index of the last character of this string that is *NOT* a # punctuation character, initialized to the length of this string. text_suffix_start_index = text_len # print(f'\nstring: {text}') # print(f'text_len: {text_len}') # print(f'max_len: {max_len}') # print(f'[before backing up] text_prefix_len: {text_prefix_len}') # print(f'[before backing up] text_suffix_start_index: {text_suffix_start_index}') # While... while ( # There exists at least one remaining character to truncate from this # string *AND*... text_prefix_len >= 1 and # The character preceding the current trailing punctuation character of # this string is also a punctuation character... text[text_suffix_start_index - 1] in CHARS_PUNCTUATION ): # Truncate one additional character from this string. text_prefix_len -= 1 # Prepend one additional trailing punctuation character onto this # suffixing substring. text_suffix_start_index -= 1 # print(f'[after backing up] text_prefix_len: {text_prefix_len}') # print(f'[after backing up] text_suffix_start_index: {text_suffix_start_index}') # While... while ( # There exists at least one remaining trailing punctuation character to # inspect in this string *AND*... text_suffix_start_index <= text_len_minus_1 and # The character currently prefixing this suffixing substring of # trailing punctuation characters is a period... text[text_suffix_start_index] == '.' ): # Append one additional character back onto this string. text_prefix_len += 1 # Remove this period from this suffixing substring. Why? Because this # period will already be included in the ellipsis injected into this # string below. Look. It's complicated. Just wave your hands in the air! text_suffix_start_index += 1 # print(f'[after eating dots] text_prefix_len: {text_prefix_len}') # print(f'[after eating dots up] text_suffix_start_index: {text_suffix_start_index}') # Truncated string to be returned, comprising... text = ( # The prefixing substring of this string *NOT* exceeding this maximum # length. f'{text[:text_prefix_len]}' # An ellipsis replacing the remaining truncated middle of this string. f'...' # The suffixing substring of trailing punctuation characters. f'{text[text_suffix_start_index:]}' ) # Return this truncated string. return text beartype-0.18.5/beartype/_util/text/utiltextprefix.py000066400000000000000000000256061461113517100230160ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **text prefix utilities** (i.e., low-level callables creating and returning human-readable strings describing prominent objects or types and *always* suffixed by exactly one space character, intended to prefix human-readable error messages). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._data.func.datafuncarg import ARG_NAME_RETURN from beartype._data.hint.datahinttyping import ( BeartypeableT, BoolTristate, ) from beartype._util.text.utiltextlabel import ( label_callable, label_type, ) from collections.abc import Callable # ....................{ PREFIXERS ~ beartypeable }.................... #FIXME: Unit test this function with respect to classes, please. def prefix_beartypeable( # Mandatory parameters. obj: BeartypeableT, # pyright: ignore[reportInvalidTypeVarUse] # Optional parameters. is_color: BoolTristate = False, ) -> str: ''' Human-readable label describing the passed **beartypeable** (i.e., object that is currently being or has already been decorated by the :func:`beartype.beartype` decorator) suffixed by delimiting whitespace. Parameters ---------- obj : BeartypeableT Beartypeable to be labelled. is_color : BoolTristate Tri-state colouring boolean governing ANSI usage. See the :attr:`beartype.BeartypeConf.is_color` attribute for further details. Defaults to :data:`False`. Returns ------- str Human-readable label describing this beartypeable. ''' # Return either... return ( # If this beartypeable is a class, a label describing this class; f'{label_type(cls=obj, is_color=is_color)} ' if isinstance(obj, type) else # Else, this beartypeable is a callable. In this case, a label # describing this callable. f'{label_callable(func=obj, is_color=is_color)} ' # type: ignore[arg-type] ) def prefix_beartypeable_pith( # Mandatory parameters. func: Callable, pith_name: str, # Optional parameters. is_color: BoolTristate = False, ) -> str: ''' Human-readable label describing either the parameter with the passed name *or* return value if this name is ``"return"`` of the passed **beartypeable callable** (i.e., callable wrapped by the :func:`beartype.beartype` decorator with a wrapper function type-checking that callable) suffixed by delimiting whitespace. Parameters ---------- func : Callable Decorated callable to be labelled. pith_name : str Name of the parameter or return value of this callable to be labelled. is_color : BoolTristate Tri-state colouring boolean governing ANSI usage. See the :attr:`beartype.BeartypeConf.is_color` attribute for further details. Defaults to :data:`False`. Returns ------- str Human-readable label describing either the name of this parameter *or* this return value. ''' assert isinstance(pith_name, str), f'{repr(pith_name)} not string.' # Return a human-readable label describing either... return ( # If this name is "return", the return value of this callable. prefix_callable_return(func=func, is_color=is_color) if pith_name == ARG_NAME_RETURN else # Else, the parameter with this name of this callable. prefix_callable_arg_name( func=func, arg_name=pith_name, is_color=is_color) ) # ....................{ PREFIXERS : callable : name }.................... def prefix_callable_arg_name( # Mandatory parameters. func: Callable, arg_name: str, # Optional parameters. is_color: BoolTristate = False, ) -> str: ''' Human-readable label describing the parameter with the passed name of the passed **decorated callable** (i.e., callable wrapped by the :func:`beartype.beartype` decorator with a wrapper function type-checking that callable) suffixed by delimiting whitespace. Parameters ---------- func : Callable Decorated callable to be labelled. arg_name : str Name of the parameter of this callable to be labelled. is_color : BoolTristate Tri-state colouring boolean governing ANSI usage. See the :attr:`beartype.BeartypeConf.is_color` attribute for further details. Defaults to :data:`False`. Returns ------- str Human-readable label describing this parameter's name. ''' assert isinstance(arg_name, str), f'{repr(arg_name)} not string.' # Avoid circular import dependencies. from beartype._util.text.utiltextansi import color_arg_name # Double-quote this argument name. arg_name = f'"{arg_name}"' # Create and return this label. return ( f'{prefix_beartypeable(obj=func, is_color=is_color)}' f'parameter {color_arg_name(text=arg_name, is_color=is_color)} ' ) def prefix_callable_return( # Mandatory parameters. func: Callable, # Optional parameters. is_color: BoolTristate = False, ) -> str: ''' Human-readable label describing the return of the passed **decorated callable** (i.e., callable wrapped by the :func:`beartype.beartype` decorator with a wrapper function type-checking that callable) suffixed by delimiting whitespace. Parameters ---------- func : Callable Decorated callable to be labelled. is_color : BoolTristate Tri-state colouring boolean governing ANSI usage. See the :attr:`beartype.BeartypeConf.is_color` attribute for further details. Defaults to :data:`False`. Returns ------- str Human-readable label describing this return. ''' # Create and return this label. return f'{prefix_beartypeable(obj=func, is_color=is_color)}return ' # ....................{ PREFIXERS : callable : value }.................... def prefix_callable_arg_value( # Mandatory parameters. func: Callable, arg_name: str, arg_value: object, # Optional parameters. is_color: BoolTristate = False, ) -> str: ''' Human-readable label describing the parameter with the passed name and trimmed value of the passed **decorated callable** (i.e., callable wrapped by the :func:`beartype.beartype` decorator with a wrapper function type-checking that callable) suffixed by delimiting whitespace. Parameters ---------- func : Callable Decorated callable to be labelled. arg_name : str Name of the parameter of this callable to be labelled. arg_value : object Value of the parameter of this callable to be labelled. is_color : BoolTristate Tri-state colouring boolean governing ANSI usage. See the :attr:`beartype.BeartypeConf.is_color` attribute for further details. Defaults to :data:`False`. Returns ------- str Human-readable label describing this parameter's name and value. ''' assert isinstance(arg_name, str), f'{repr(arg_name)} not string.' # Avoid circular import dependencies. # # Note that this function differs enough from the comparable # prefix_callable_arg_name() function to warrant a distinct implementation. from beartype._util.text.utiltextansi import color_arg_name # Create and return this label. return ( f'{prefix_beartypeable(obj=func, is_color=is_color)}' f'parameter {color_arg_name(text=arg_name, is_color=is_color)}=' f'{prefix_pith_value(pith=arg_value, is_color=is_color)}' ) def prefix_callable_return_value( # Mandatory parameters. func: Callable, return_value: object, # Optional parameters. is_color: BoolTristate = False, ) -> str: ''' Human-readable label describing the passed trimmed return value of the passed **decorated callable** (i.e., callable wrapped by the :func:`beartype.beartype` decorator with a wrapper function type-checking that callable) suffixed by delimiting whitespace. Parameters ---------- func : Callable Decorated callable to be labelled. return_value : object Value returned by this callable to be labelled. is_color : BoolTristate Tri-state colouring boolean governing ANSI usage. See the :attr:`beartype.BeartypeConf.is_color` attribute for further details. Defaults to :data:`False`. Returns ------- str Human-readable label describing this return value. ''' # Create and return this label. return ( f'{prefix_callable_return(func=func, is_color=is_color)}' f'{prefix_pith_value(pith=return_value, is_color=is_color)}' ) # ....................{ PREFIXERS ~ pith }.................... def prefix_pith_type( # Mandatory parameters. pith: object, # Optional parameters. is_color: BoolTristate = False, ) -> str: ''' Human-readable label describing the passed type of the **current pith** (i.e., arbitrary object violating the current type check) suffixed by delimiting whitespace. Parameters ---------- pith : object Arbitrary object violating the current type check. is_color : BoolTristate Tri-state colouring boolean governing ANSI usage. See the :attr:`beartype.BeartypeConf.is_color` attribute for further details. Defaults to :data:`False`. Returns ------- str Human-readable label describing this pith type. ''' # Avoid circular import dependencies. from beartype._util.text.utiltextansi import color_type from beartype._util.text.utiltextlabel import label_object_type # To boldly go where no one-liner has gone before. return color_type(text=f'{label_object_type(pith)} ', is_color=is_color) def prefix_pith_value( # Mandatory parameters. pith: object, # Optional parameters. is_color: BoolTristate = False, ) -> str: ''' Human-readable label describing the passed value of the **current pith** (i.e., arbitrary object violating the current type check) suffixed by delimiting whitespace. Parameters ---------- pith : object Arbitrary object violating the current type check. is_color : BoolTristate Tri-state colouring boolean governing ANSI usage. See the :attr:`beartype.BeartypeConf.is_color` attribute for further details. Defaults to :data:`False`. Returns ------- str Human-readable label describing this pith value. ''' # Avoid circular import dependencies. from beartype._util.text.utiltextlabel import label_pith_value # Create and return this label. return f'{label_pith_value(pith=pith, is_color=is_color)} ' beartype-0.18.5/beartype/_util/text/utiltextrepr.py000066400000000000000000000311431461113517100224620ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. """ Project-wide **string munging** (i.e., generic low-level operations transforming strings into new derivative strings) utilities. This private submodule is *not* intended for importation by downstream callers. """ # ....................{ IMPORTS }.................... from beartype.roar._roarwarn import _BeartypeUtilCallableWarning # from beartype.typing import Dict from beartype._cave._cavefast import NumberType from beartype._data.hint.datahinttyping import TypeWarning from beartype._data.kind.datakindtext import CHARS_PUNCTUATION from beartype._util.utilobject import get_object_basename_scoped_or_none from collections.abc import Callable # ....................{ REPRESENTERS }.................... def represent_object( # Mandatory parameters. obj: object, # Optional parameters. max_len: int = 96, ) -> str: ''' Pretty-printed quasi-human-readable variant of the string returned by the non-pretty-printed machine-readable :meth:`obj.__repr__` dunder method of the passed object, truncated to the passed maximum string length. Specifically, this function (in order): #. Obtains this object's representation by calling ``repr(object)``. #. If this representation is neither suffixed by a punctuation character (i.e., character in the standard :attr:`string.punctuation` set) *nor* representing a byte-string whose representations are prefixed by ``b'`` and suffixed by ``'`` (e.g., ``b'Check, mate.'``), double-quotes this representation for disambiguity with preceding characters -- notably, sequence indices. Since strings returned by this function commonly follow sequence indices in error messages, failing to disambiguate the two produces non-human-readable output: >>> def wat(mate: typing.List[str]) -> int: return len(mate) >>> get_func_pith_violation( ... func=muh_func, pith_name='mate', pith_value=[7,]) beartype.roar.BeartypeCallHintParamViolation: @beartyped wat() parameter mate=[7] violates PEP type hint typing.List[str], as list item 0 value 7 not a str. Note the substring "item 0 value 7", which misreads like a blatant bug. Double-quoting the "7" suffices to demarcate values from indices. #. If this representation exceeds the passed maximum length, replaces the suffix of this representation exceeding this length with an ellipses (i.e., ``"..."`` substring). Caveats ------- **This function is unavoidably slow and should thus not be called from optimized performance-critical code.** This function internally performs mildly expensive operations, including iterating-based string munging. Ideally, this function should *only* be called to create user-oriented exception messages where performance is a negligible concern. **This function preserves all quote-protected newline characters** (i.e., ``"\\n"``) **in this representation.** Since the :meth:`str.__repr__` dunder method implicitly quote-protects all newlines in the original string, this function effectively preserves all newlines in strings. Parameters ---------- obj : object Object to be represented. max_len: int, optional Maximum length of the string to be returned. Defaults to a standard line length of 100 characters minus output indentation of 4 characters. Returns ------- str Pretty-printed quasi-human-readable variant of this object's non-pretty-printed machine-readable representation. ''' assert isinstance(max_len, int), f'{repr(max_len)} not integer.' #FIXME: Render this safe against infinitely recursive data structures. #Unfortunately, we *CANNOT* call the standard pprint.saferepr() function to #do so, as that function is *OUTRAGEOUSLY* slow on worst-case edge cases. #Instead, we'll need to implement our own performant saferepr() #alternative. Fortunately, note that someone's already done so: the popular #BSD-licensed Celerity project, whose celerity.utils.saferepr.saferepr() #function claims to actually be faster than the repr() builtin under #certain circumstances. While impressive, repurposing Celerity's saferepr() #implementation for @beartype will be non-trivial; that function internally #leverages a number of non-trivial internal functions, including a #streaming iterator that appears to be performing some sort of ad-hoc #tokenization (!) on the input object's string representation. Although #that submodule is less than 300 lines, that's 300 *INTENSE* lines. #Nonetheless, we'll need to do this sooner or later. Currently, later. By #the time you read this next, probably sooner. Until someone pounds their #fists on our issue tracker, let's pretend this isn't a compelling concern. #See also: # https://github.com/celery/celery/blob/master/celery/utils/saferepr.py #FIXME: Actually, a trivial way to do this without going full-Celery would #be to just leverage the EAFP principle: e.g., # try: # obj_repr = repr(obj) # except RecursionError: # from pprint import saferepr # obj_repr = saferepr(obj) # #Clearly, that will still be slightly less efficient than the #Celery-oriented approach -- but also considerably easier. *sigh* # String describing this object. Note that: # * This representation quote-protects all newlines in this representation. # Ergo, "\n" *MUST* be matched as r"\n" instead below. # * For debuggability, the verbose (albeit less readable) output of repr() # is preferred to the terse (albeit more readable) output of str(). # * For safety, the pprint.saferepr() function explicitly protected against # recursive data structures *WOULD* typically be preferred to the unsafe # repr() builtin *NOT* protected against such recursion. Sadly, # pprint.saferepr() is extremely unoptimized and thus susceptible to # extreme performance regressions when passed a worst-case object (e.g., # deeply nested container). obj_repr = repr(obj) #FIXME: Uncomment to exhibit a performance regression. # from pprint import saferepr # obj_repr = saferepr(obj) # If this representation is empty, return empty double-quotes. Although # most objects (including outlier singletons like "None" and the empty # string) have non-empty representations, caller-defined classes may # maliciously override the __repr__() dunder method to return an empty # string rather than the representation of an empty string (i.e., '""'). if not obj_repr: return '""' # Else, this representation is non-empty. # # If this representation is neither... elif not ( # Prefixed by punctuation *NOR*... obj_repr[0] in CHARS_PUNCTUATION or # An instance of a class whose representations do *NOT* benefit from # explicit quoting... isinstance(obj, _TYPES_UNQUOTABLE) ): # Then this representation is *NOT* demarcated from preceding characters in # the parent string embedding this representation. In this case, # double-quote this representation for disambiguity with preceding # characters (e.g., sequence indices). obj_repr = f'"{obj_repr}"' # If this representation exceeds this maximum length... if len(obj_repr) > max_len: # Avoid circular import dependencies. from beartype._util.text.utiltextmunge import truncate_str # Truncate this representation to this maximum length. obj_repr = truncate_str(text=obj_repr, max_len=max_len) # print(f'obj repr truncated: {obj_repr}') # Return this representation. return obj_repr # ....................{ REPRESENTER ~ callable }.................... def represent_func( # Mandatory parameters. func: Callable, # Optional parameters. warning_cls: TypeWarning = _BeartypeUtilCallableWarning, ) -> str: ''' Machine-readable representation of the passed callable. Caveats ------- **This function is unavoidably slow and should thus not be called from optimized performance-critical code.** This function internally performs extremely expensive operations, including abstract syntax tree (AST)-based parsing of Python scripts and modules deserialized from disk. Ideally, this function should *only* be called to create user-oriented exception messages where performance is a negligible concern. Parameters ---------- func : Callable Callable to be represented. warning_cls : TypeWarning, optional Type of warning to be emitted in the event of a non-fatal error. Defaults to :class:`_BeartypeUtilCallableWarning`. Warns ----- :class:`warning_cls` If this callable is a pure-Python lambda function whose definition is *not* parsable from the script or module defining that lambda. Returns ------- str Machine-readable representation of that callable. ''' assert callable(func), f'{repr(func)} not callable.' # Avoid circular import dependencies. from beartype._util.func.utilfunccode import get_func_code_or_none from beartype._util.func.utilfunctest import is_func_lambda # If that callable is a pure-Python lambda function, return either: # * If this lambda is defined by an on-disk script or module source file, # the exact substring of that file defining this lambda. # * Else (e.g., if this lambda is dynamically defined in-memory), a # placeholder string. if is_func_lambda(func): return ( get_func_code_or_none(func=func, warning_cls=warning_cls) or '' ) # Else, that callable is *NOT* a pure-Python lambda function. #FIXME: Actually, we should be calling a new get_object_name_or_none() #function instead -- but that function currently doesn't exist and we're #lazy. The issue with get_object_basename_scoped_or_none() is that this #getter fails to return the module name of this function. *shrug* func_basename_scoped = get_object_basename_scoped_or_none(func) # If that callable is named, return this name. if func_basename_scoped: return func_basename_scoped # Else, that callable is unnamed due to failing to define both the # "__qualname__" and "__name__" dunder attributes and thus have *NO* names. # Although most callables are named, some are not. This includes: # * Callable "functools.partial" objects. # Return the machine-readable representation of that callable as a fallback. return repr(func) # ....................{ REPRESENTERS ~ pith }.................... def represent_pith( # Mandatory parameters. pith: object, # Optional parameters. is_color: bool = True, ) -> str: ''' Human-readable description of the passed **pith** (i.e., arbitrary object violating the current type check) intended to be embedded in an exception message explaining this violation. Parameters ---------- pith : object Arbitrary object violating the current type check. is_color : bool, optional :data:`True` only if embellishing this label with colour. Defaults to :data:`True` for convenience. Returns ------- str Human-readable description of this object. ''' # Avoid circular import dependencies. from beartype._util.text.utiltextlabel import label_pith_value from beartype._util.text.utiltextprefix import prefix_pith_type # Create and return this representation. return ( f'{prefix_pith_type(pith=pith, is_color=is_color)}' f'{label_pith_value(pith=pith, is_color=is_color)}' ) # ....................{ PRIVATE ~ globals }.................... _TYPES_UNQUOTABLE = ( # Byte strings, whose representations are already quoted as "b'...'". bytes, # Numbers, whose representations are guaranteed to both contain *NO* # whitespace and be sufficiently terse as to benefit from *NO* quoting. NumberType, ) ''' **Unquotable tuple union** (i.e., isinstancable tuple of all classes such that the :func:`represent_object` function intentionally avoids double-quoting the machine-readable representations all instances of these classes, typically due to these representations either being effectively quoted already *or* sufficiently terse as to not benefit from being quoted). ''' beartype-0.18.5/beartype/_util/text/utiltexttest.py000066400000000000000000000032111461113517100224640ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype string testing utilities** (i.e., callables testing whether passed strings satisfy various conditions). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... # ....................{ TESTERS }.................... def is_str_float_or_int(text: str) -> bool: ''' ``True`` only if the passed string is a valid machine-readable representation of either an integer or finite floating-point number. Caveats ---------- This tester intentionally returns ``False`` for non-standard floating-point pseudo-numbers that have no finite value, including: * Not-a-numbers (i.e., ``float('NaN')`` values). * Negative infinity (i.e., ``float('-inf')`` values). * Positive infinity (i.e., ``float('inf')`` values). Parameters ---------- text : str String to be inspected. Returns ---------- bool ``True`` only if this string is a valid machine-readable representation of either an integer or finite floating-point number. ''' assert isinstance(text, str), f'{repr(text)} not string.' # Return true only if this text represents a finite number. See also: # s.lstrip('-').replace('.','',1).replace('e-','',1).replace('e','',1).isdigit() return text.lstrip( '-').replace('.','',1).replace('e-','',1).replace('e','',1).isdigit() beartype-0.18.5/beartype/_util/text/utiltextversion.py000066400000000000000000000136321461113517100232020ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **version string utilities** (i.e., low-level callables handling human-readable ``.``-delimited version strings). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilTextVersionException from beartype.typing import Tuple from re import compile as re_compile # ....................{ CONVERTERS }.................... def convert_str_version_to_tuple(version: str) -> Tuple[int, ...]: ''' Convert the passed human-readable ``.``-delimited version string into a machine-readable version tuple of corresponding integers, suitable for efficient comparison against other such version tuples via standard rich comparison operators (e.g., ``<``, ``==``). Caveats ---------- **This converter strictly requires each ``.``-delimited substring of this string to be a non-negative integer.** The exception is the last ``.``-prefixed substring of this string, which this converter permits to *not* be a non-negative integer. Specifically, that last substring: * *Must* be prefixed by a non-negative integer. * *May* be followed by any other arbitrary characters, which this converter silently ignores as supplementary software-specific version metadata (e.g., release candidates, alpha releases, beta releases). Since that metadata does *not* cleanly generalize to all possible use cases, that metadata *cannot* be safely converted into a non-negative integer. For example, this converter: * Converts the valid version string ``"1.26.0"`` to ``(1, 26, 0)``. * Converts the valid version string ``"1.26.0rc1"`` to ``(1, 26, 0)`` by simply ignoring the non-numeric suffix ``"rc1"``. * Raises an exception for the invalid version string ``"1.26.rc1"``. Parameters ---------- text : str Version string to be converted. Returns ---------- Tuple[int, ...] Machine-readable version tuple of corresponding integers. Raises ---------- _BeartypeUtilTextVersionException If this string is syntactically invalid as a version. ''' assert isinstance(version, str), f'{repr(version)} not version string.' # List of either: # * If this version contains one or more "." delimiters, all "."-delimited # version components split from this version. # * If this version contains *NO* "." delimiters, the 1-list "[version,]". version_substrs = version.split('.') # 0-based index of the last version component in this list. version_substr_index_last = len(version_substrs) - 1 # List of all version components to be returned as a tuple. version_list = [] # For the 0-based index of each "."-delimited version component of this # version string and that component... for version_substr_index, version_substr in enumerate(version_substrs): # Attempt to... try: # Coerce this version component into an integer. version_part = int(version_substr) # If this component is negative, raise an exception. if version_part < 0: raise _BeartypeUtilTextVersionException( f'Version {repr(version)} syntactically invalid ' f'(i.e., version component {repr(version_substr)} negative).' ) # Else, this component is non-negative. # If doing so raises a "ValueError", this version component is *NOT* # syntactically valid as an integer. In this case... except ValueError as exception: # If the 0-based index of this version component is that of the last # version component in this list, this is *NOT* the last version # component. In this case, this component is syntactically invalid. # Raise an exception. if version_substr_index != version_substr_index_last: raise _BeartypeUtilTextVersionException( f'Version {repr(version)} syntactically invalid ' f'(i.e., version component {repr(version_substr)} ' f'not an integer).' ) from exception # Else, this is the last version component. In this case, reduce # this component to its non-negative integer prefix. # Match result if this component is prefixed by a non-negative # integer *OR* "None" otherwise (i.e., if this component is # syntactically invalid). version_substr_match = _VERSION_SUBSTR_LAST_REGEX.match( version_substr) # If this component is syntactically invalid, raise an exception. if version_substr_match is None: raise _BeartypeUtilTextVersionException( f'Version {repr(version)} syntactically invalid ' f'(i.e., version component {repr(version_substr)} ' f'not an integer).' ) from exception # Else, this component is syntactically valid. # Non-negative integer prefixing this component. version_part = int(version_substr_match.group(1)) # Append this version component to this list. version_list.append(version_part) # Return this list coerced into a tuple. return tuple(version_list) # ....................{ PRIVATE ~ constants }.................... _VERSION_SUBSTR_LAST_REGEX = re_compile(r'([0-9]+).+') ''' Compiled regular expression matching the non-negative integer prefixing the last ``.``-delimited version component in a version string (e.g., ``"5"`` in the version string ``"5rc27"``). ''' beartype-0.18.5/beartype/_util/utilobject.py000066400000000000000000000405321461113517100210710ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **object utilities** (i.e., low-level callables handling arbitrary objects in a general-purpose manner). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeUtilObjectNameException from beartype.typing import ( Any, Optional, ) from beartype._data.cls.datacls import TYPES_CONTEXTMANAGER_FAKE from contextlib import AbstractContextManager # ....................{ CLASSES }.................... class Iota(object): ''' **Iota** (i.e., object minimizing space consumption by guaranteeably containing *no* attributes). ''' __slots__ = () # ....................{ CONSTANTS }.................... SENTINEL = Iota() ''' Sentinel object of arbitrary value. This object is internally leveraged by various utility functions to identify erroneous and edge-case input (e.g., iterables of insufficient length). ''' # ....................{ TESTERS }.................... def is_object_context_manager(obj: object) -> bool: ''' :data:`True` only if the passed object is a **context manager** (i.e., object defining both the ``__exit__`` and ``__enter__`` dunder methods required to satisfy the context manager protocol). Parameters ---------- obj : object Object to be inspected. Returns ------- bool :data:`True` only if this object is a context manager. ''' # Return true only if... return ( # This object satisfies the context manager protocol (i.e., defines both # the __enter__() and __exit__() dunder methods) *AND*... isinstance(obj, AbstractContextManager) and # This object is *NOT* a "fake" context manager (i.e., defines erroneous # __enter__() and __exit__() dunder methods trivially reducing to noops # and also emitting non-fatal deprecation warnings). not isinstance(obj, TYPES_CONTEXTMANAGER_FAKE) ) # Note that this tester function *CANNOT* be memoized by the @callable_cached # decorator, which requires all passed parameters to already be hashable. def is_object_hashable(obj: object) -> bool: ''' :data:`True` only if the passed object is **hashable** (i.e., passable to the builtin :func:`hash` function *without* raising an exception and thus usable in hash-based containers like dictionaries and sets). Parameters ---------- obj : object Object to be inspected. Returns ------- bool :data:`True` only if this object is hashable. ''' # Attempt to hash this object. If doing so raises *any* exception # whatsoever, this object is by definition unhashable. # # Note that there also exists a "collections.abc.Hashable" superclass. # Sadly, this superclass is mostly useless for all practical purposes. Why? # Because user-defined classes are free to subclass that superclass # despite overriding the __hash__() dunder method implicitly called by the # builtin hash() function to raise exceptions: e.g., # # from collections.abc import Hashable # class HashUmUp(Hashable): # def __hash__(self): # raise ValueError('uhoh') # # Note also that we catch all possible exceptions rather than merely the # standard "TypeError" exception raised by unhashable builtin types (e.g., # dictionaries, lists, sets). Why? For the same exact reason as above. try: hash(obj) # If this object is unhashable, return false. except: return False # Else, this object is hashable. Return true. return True # ....................{ GETTERS ~ name }.................... def get_object_name(obj: Any) -> str: ''' **Fully-qualified name** (i.e., ``.``-delimited string unambiguously identifying) of the passed object if this object defines either the ``__qualname__`` or ``__name__`` dunder attributes *or* raise an exception otherwise (i.e., if this object defines *no* such attributes). Specifically, this name comprises (in order): #. If this object is transitively declared by a module, the absolute name of that module. #. If this object is transitively declared by another object (e.g., class, callable) and thus nested in that object, the unqualified basenames of all parent objects transitively declaring this object in that module. #. Unqualified basename of this object. Parameters ---------- obj : object Object to be inspected. Returns ------- str Fully-qualified name of this object. Raises ------ _BeartypeUtilObjectNameException If this object defines neither ``__qualname__`` *nor* ``__name__`` dunder attributes. ''' # Avoid circular import dependencies. from beartype._cave._cavefast import CallableOrClassTypes from beartype._util.module.utilmodget import ( get_object_module_name_or_none, get_object_type_module_name_or_none, ) # Lexically scoped name of this object excluding this module name if this # object is named *OR* raise an exception otherwise. object_scopes_name = get_object_basename_scoped(obj) # Fully-qualified name of the module declaring this object if this object # is declared by a module *OR* "None" otherwise, specifically defined as: # * If this object is either a callable or class, the fully-qualified name # of the module declaring this object. # * Else, the fully-qualified name of the module declaring the class of # this object. object_module_name = ( get_object_module_name_or_none(obj) if isinstance(object, CallableOrClassTypes) else get_object_type_module_name_or_none(obj) ) # Return either... return ( # If this module name exists, "."-delimited concatenation of this # module and object name; f'{object_module_name}.{object_scopes_name}' if object_module_name is not None else # Else, this object name as is. object_scopes_name ) # ....................{ GETTERS ~ basename }.................... def get_object_basename_scoped(obj: Any) -> str: ''' **Lexically scoped name** (i.e., ``.``-delimited string unambiguously identifying all lexical scopes encapsulating) the passed object if this object defines either the ``__qualname__`` or ``__name__`` dunder attributes *or* raise an exception otherwise (i.e., if this object defines *no* such attributes). Parameters ---------- obj : object Object to be inspected. Returns ------- str Lexically scoped name of this object. Raises ------ _BeartypeUtilObjectNameException If this object defines neither ``__qualname__`` *nor* ``__name__`` dunder attributes. See Also -------- :func:`.get_object_basename_scoped_or_none` Further details. ''' # Fully-qualified name of this object excluding its module name. object_scoped_name = get_object_basename_scoped_or_none(obj) # If this object is unnamed, raise a human-readable exception. The default # "AttributeError" exception raised by attempting to directly access either # the "obj.__name__" or "obj.__qualname__" attributes is sufficiently # non-explanatory to warrant replacement by our explanatory exception. if object_scoped_name is None: raise _BeartypeUtilObjectNameException( f'{repr(obj)} unnamed ' f'(i.e., declares neither "__name__" nor "__qualname__" ' f'dunder attributes).' ) # Else, this object is named. # Remove all "" placeholder substrings as discussed above. return object_scoped_name.replace('.', '') #FIXME: Unit test us up, please. def get_object_basename_scoped_or_none(obj: Any) -> Optional[str]: ''' **Lexically scoped name** (i.e., ``.``-delimited string unambiguously identifying all lexical scopes encapsulating) the passed object if this object defines either the ``__qualname__`` or ``__name__`` dunder attributes *or* :data:`None` otherwise (i.e., if this object defines *no* such attributes). Specifically, this name comprises (in order): #. If this object is transitively declared by another object (e.g., class, callable) and thus nested in that object, the unqualified basenames of all parent objects transitively declaring this object in that module. For usability, these basenames intentionally omit the meaningless placeholder ``""`` substrings artificially injected by Python itself into the original ``__qualname__`` instance variable underlying this getter: e.g., .. code-block:: python >>> from beartype._util.utilobject import get_object_basename_scoped >>> def muh_func(): ... def muh_closure(): pass ... return muh_closure() >>> muh_func().__qualname__ 'muh_func..muh_closure' # <-- bad Python >>> get_object_basename_scoped(muh_func) 'muh_func.muh_closure' # <-- good @beartype #. Unqualified basename of this object. Caveats ------- **The higher-level** :func:`get_object_name` **getter should typically be called instead of this lower-level getter.** This getter unsafely: * Requires the passed object to declare dunder attributes *not* generally declared by arbitrary instances of user-defined classes. * Omits the fully-qualified name of the module transitively declaring this object and thus fails to return fully-qualified names. **This high-level getter should always be called in lieu of directly accessing the low-level** ``__qualname__`` **dunder attribute on objects.** That attribute contains one meaningless ``""`` placeholder substring conveying *no* meaningful semantics for each parent callable lexically nesting this object. Parameters ---------- obj : object Object to be inspected. Returns ------- Optional[str] Either: * If this object defines at least one of the ``__qualname__`` or ``__name__`` dunder attributes, the lexically scoped name of this object. * Else, :data:`None`. Raises ------ _BeartypeUtilObjectNameException If this object defines neither ``__qualname__`` *nor* ``__name__`` dunder attributes. ''' # Fully-qualified name of this object excluding its module name as follows: # * If this object defines the "__qualname__" dunder attribute whose value # is the "."-delimited concatenation of the unqualified basenames of all # parent objects transitively declaring this object, that value with all # meaningless "" placeholder substrings removed. If this object # is a nested non-method callable (i.e., pure-Python function nested in # one or more parent pure-Python callables), that value contains one such # placeholder for each parent callable containing this callable. Since # placeholders convey no meaningful semantics, placeholders are removed. # * Else if this object defines the "__name__" dunder attribute whose value # is the unqualified basename of this object, that value. # * Else, "None". object_scoped_name = getattr( obj, '__qualname__', getattr( obj, '__name__', None)) # Return either... return ( # If this name exists, all "" placeholder substrings globally # removed from this name as discussed above; object_scoped_name.replace('.', '') if object_scoped_name else # Else, either "None" or the empty string. object_scoped_name ) # ....................{ GETTERS ~ filename }.................... def get_object_filename_or_none(obj: object) -> Optional[str]: ''' Filename of the module or script physically declaring the passed object if this object is either a callable or class physically declared on-disk *or* :data:`None` otherwise (i.e., if this object is neither a callable nor class *or* is either a callable or class dynamically declared in-memory). Parameters ---------- obj : object Object to be inspected. Returns ------- Optional[str] Either: * If this object is either a callable or class physically declared on-disk, the filename of the module or script physically declaring this object. * Else, :data:`None`. ''' # Avoid circular import dependencies. from beartype._util.cls.utilclsget import get_type_filename_or_none from beartype._util.func.utilfuncfile import get_func_filename_or_none from beartype._util.func.utilfunctest import is_func_python # Return either... return ( # If this object is a pure-Python class, the absolute filename of the # source module file defining that class if that class was defined # on-disk *OR* "None" otherwise (i.e., if that class was defined # in-memory); get_type_filename_or_none(obj) if isinstance(obj, type) else # If this object is a pure-Python callable, the absolute filename of the # absolute filename of the source module file defining that callable if # that callable was defined on-disk *OR* "None" otherwise (i.e., if that # callable was defined in-memory); get_func_filename_or_none(obj) if is_func_python(obj) else # Else, "None". None ) # ....................{ GETTERS ~ type }.................... def get_object_type_unless_type(obj: object) -> type: ''' Either the passed object if this object is a class *or* the class of this object otherwise (i.e., if this object is *not* a class). Note that this function *never* raises exceptions on arbitrary objects, as the :obj:`type` builtin wisely returns itself when passed itself: e.g., .. code-block:: python >>> type(type(type)) is type True Parameters ---------- obj : object Object to be inspected. Returns ------- type Type of this object. ''' return obj if isinstance(obj, type) else type(obj) # ....................{ GETTERS ~ type : name }.................... def get_object_type_basename(obj: object) -> str: ''' **Unqualified name** (i.e., non-``.``-delimited basename) of either the passed object if this object is a class *or* the class of this object otherwise (i.e., if this object is *not* a class). Parameters ---------- obj : object Object to be inspected. Returns ------- str Unqualified name of this class. ''' # Elegant simplicity diminishes aggressive tendencies. return get_object_type_unless_type(obj).__name__ def get_object_type_name(obj: object) -> str: ''' **Fully-qualified name** (i.e., ``.``-delimited name prefixed by the declaring module) of either passed object if this object is a class *or* the class of this object otherwise (i.e., if this object is *not* a class). Parameters ---------- obj : object Object to be inspected. Returns ------- str Fully-qualified name of the type of this object. ''' # Avoid circular import dependencies. from beartype._util.module.utilmodget import ( get_object_type_module_name_or_none) # Type of this object. cls = get_object_type_unless_type(obj) # Unqualified name of this type. cls_basename = get_object_type_basename(cls) # Fully-qualified name of the module defining this class if this class is # defined by a module *OR* "None" otherwise. cls_module_name = get_object_type_module_name_or_none(cls) # Return either... return ( # The "."-delimited concatenation of this class basename and module # name if this module name exists. f'{cls_module_name}.{cls_basename}' if cls_module_name is not None else # This class basename as is otherwise. cls_basename ) beartype-0.18.5/beartype/cave/000077500000000000000000000000001461113517100161515ustar00rootroot00000000000000beartype-0.18.5/beartype/cave/__init__.py000066400000000000000000000231421461113517100202640ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype cave.** This submodule collects common types (e.g., :class:`NoneType`, the type of the ``None`` singleton) and tuples of common types (e.g., :data:`CallableTypes`, a tuple of the types of all callable objects). PEP 484 ---------- This module is intentionally *not* compliant with the :pep:`484` standard implemented by the stdlib :mod:`typing` module, which formalizes type hinting annotations with a catalogue of generic classes and metaclasses applicable to common use cases. :mod:`typing` enables end users to enforce contractual guarantees over the contents of arbitrarily complex data structures with the assistance of third-party static type checkers (e.g., :mod:`mypy`, :mod:`pyre`), runtime type checkers (e.g., :mod:`beartype`, :mod:`typeguard`), and integrated development environments (e.g., PyCharm). Genericity comes at a cost, though. Deeply type checking a container containing ``n`` items, for example, requires type checking both that container itself non-recursively *and* each item in that container recursively. Doing so has time complexity ``O(N)`` for ``N >= n`` the total number of items transitively contained in this container (i.e., items directly contained in this container *and* items directly contained in containers contained in this container). While the cost of this operation can be paid either statically *or* amortized at runtime over all calls to annotated callables accepting that container, the underlying cost itself remains the same. By compare, this module only contains standard Python classes and tuples of such classes intended to be passed as is to the C-based :func:`isinstance` builtin and APIs expressed in terms of that builtin (e.g., :mod:`beartype`). This module only enables end users to enforce contractual guarantees over the types but *not* contents of arbitrarily complex data structures. This intentional tradeoff maximizes runtime performance at a cost of ignoring the types of items contained in containers. In summary: ===================== ==================== ==================================== feature set :mod:`beartype.cave` :mod:`typing` ===================== ==================== ==================================== type checking **shallow** **deep** type check items? **no** **yes** :pep:`484`-compliant? **no** **yes** time complexity ``O(1)`` ``O(N)`` performance stupid fast *much* less stupid fast implementation C-based builtin call pure-Python (meta)class method calls low-level primitive :func:`isinstance` :mod:`typing.TypingMeta` ===================== ==================== ==================================== ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: *NEVER IMPORT FROM THIS SUBPACKAGE FROM WITHIN BEARTYPE ITSELF.* # This subpackage currently imports from expensive third-party packages on # importation (e.g., NumPy) despite beartype itself *NEVER* requiring those # imports. Until resolved, this subpackage is considered tainted. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To prevent "mypy --no-implicit-reexport" from raising literally # hundreds of errors at static analysis time, *ALL* public attributes *MUST* be # explicitly reimported under the same names with "{exception_name} as # {exception_name}" syntax rather than merely "{exception_name}". Yes, this is # ludicrous. Yes, this is mypy. For posterity, these failures resemble: # beartype/_cave/_cavefast.py:47: error: Module "beartype.roar" does not # explicitly export attribute "BeartypeCallUnavailableTypeException"; # implicit reexport disabled [attr-defined] #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To avoid polluting the public module namespace, external attributes # should be locally imported at module scope *ONLY* under alternate private # names (e.g., "from argparse import ArgumentParser as _ArgumentParser" rather # than merely "from argparse import ArgumentParser"). #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype.cave._cavelib import ( # Types. ArgParserType as ArgParserType, ArgSubparsersType as ArgSubparsersType, WeakRefCType as WeakRefCType, # Type tuples. WeakRefProxyCTypes as WeakRefProxyCTypes, ) from beartype._cave._caveabc import ( BoolType as BoolType, ) from beartype._cave._cavefast import ( # Types. AnyType as AnyType, AsyncCoroutineCType as AsyncCoroutineCType, AsyncGeneratorCType as AsyncGeneratorCType, CallableCodeObjectType as CallableCodeObjectType, ClassDictType as ClassDictType, CallableFrameType as CallableFrameType, CallableFunctoolsPartialType as CallableFunctoolsPartialType, ClassType as ClassType, ClosureVarCellType as ClosureVarCellType, CollectionType as CollectionType, ContainerType as ContainerType, EllipsisType as EllipsisType, EnumType as EnumType, EnumMemberType as EnumMemberType, ExceptionTracebackType as ExceptionTracebackType, FileType as FileType, FunctionType as FunctionType, FunctionOrMethodCType as FunctionOrMethodCType, GeneratorCType as GeneratorCType, GeneratorType as GeneratorType, HashableType as HashableType, HintGenericSubscriptedType as HintGenericSubscriptedType, IntOrFloatType as IntOrFloatType, IntType as IntType, IterableType as IterableType, IteratorType as IteratorType, MappingMutableType as MappingMutableType, MappingType as MappingType, MethodBoundInstanceDunderCType as MethodBoundInstanceDunderCType, MethodBoundInstanceOrClassType as MethodBoundInstanceOrClassType, MethodDecoratorClassType as MethodDecoratorClassType, MethodDecoratorPropertyType as MethodDecoratorPropertyType, MethodDecoratorStaticType as MethodDecoratorStaticType, MethodUnboundClassCType as MethodUnboundClassCType, MethodUnboundInstanceDunderCType as MethodUnboundInstanceDunderCType, MethodUnboundInstanceNondunderCType as MethodUnboundInstanceNondunderCType, MethodUnboundPropertyNontrivialCExtensionType as MethodUnboundPropertyNontrivialCExtensionType, MethodUnboundPropertyTrivialCExtensionType as MethodUnboundPropertyTrivialCExtensionType, ModuleType as ModuleType, NoneType as NoneType, NotImplementedType as NotImplementedType, NumberRealType as NumberRealType, NumberType as NumberType, SizedType as SizedType, QueueType as QueueType, RegexCompiledType as RegexCompiledType, RegexMatchType as RegexMatchType, SetType as SetType, SequenceMutableType as SequenceMutableType, SequenceType as SequenceType, StrType as StrType, UnavailableType as UnavailableType, # Type tuples. AsyncCTypes as AsyncCTypes, BoolOrNumberTypes as BoolOrNumberTypes, CallableCTypes as CallableCTypes, CallableOrClassTypes as CallableOrClassTypes, CallableOrStrTypes as CallableOrStrTypes, CallableTypes as CallableTypes, DecoratorTypes as DecoratorTypes, FunctionTypes as FunctionTypes, ModuleOrStrTypes as ModuleOrStrTypes, MethodBoundTypes as MethodBoundTypes, MethodDecoratorBuiltinTypes as MethodDecoratorBuiltinTypes, MethodUnboundTypes as MethodUnboundTypes, MethodTypes as MethodTypes, MappingOrSequenceTypes as MappingOrSequenceTypes, ModuleOrSequenceTypes as ModuleOrSequenceTypes, NumberOrIterableTypes as NumberOrIterableTypes, NumberOrSequenceTypes as NumberOrSequenceTypes, RegexTypes as RegexTypes, ScalarTypes as ScalarTypes, TestableTypes as TestableTypes, UnavailableTypes as UnavailableTypes, ) from beartype._cave._cavemap import ( NoneTypeOr as NoneTypeOr, ) # ....................{ DEPRECATIONS }.................... def __getattr__(attr_deprecated_name: str) -> object: ''' Dynamically retrieve a deprecated attribute with the passed unqualified name from this submodule and emit a non-fatal deprecation warning on each such retrieval if this submodule defines this attribute *or* raise an exception otherwise. The Python interpreter implicitly calls this :pep:`562`-compliant module dunder function under Python >= 3.7 *after* failing to directly retrieve an explicit attribute with this name from this submodule. Since this dunder function is only called in the event of an error, neither space nor time efficiency are a concern here. Parameters ---------- attr_deprecated_name : str Unqualified name of the deprecated attribute to be retrieved. Returns ---------- object Value of this deprecated attribute. Warns ---------- :class:`DeprecationWarning` If this attribute is deprecated. Raises ---------- :exc:`AttributeError` If this attribute is unrecognized and thus erroneous. ''' # Isolate imports to avoid polluting the module namespace. from beartype._util.module.utilmoddeprecate import deprecate_module_attr # Return the value of this deprecated attribute and emit a warning. return deprecate_module_attr( attr_deprecated_name=attr_deprecated_name, attr_deprecated_name_to_nondeprecated_name={ 'HintPep585Type': 'HintGenericSubscriptedType', }, attr_nondeprecated_name_to_value=globals(), ) beartype-0.18.5/beartype/cave/_cavelib.py000066400000000000000000000061211461113517100202670ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype slow cave** (i.e., private subset of the public :mod:`beartype.cave` subpackage profiled to *not* be efficiently importable at :mod:`beartype` startup and thus *not* safely importable throughout the internal :mod:`beartype` codebase). This submodule currently imports from expensive third-party packages on importation (e.g., :mod:`numpy`) despite :mod:`beartype` itself *never* requiring those imports. Until resolved, that subpackage is considered tainted. ''' # ....................{ TODO }.................... #FIXME: Excise this submodule away, please. This submodule was a horrendous idea #and has plagued the entire "beartype.cave" subpackage with unnecessary slowdown #at import time. It's simply time for this to go, please. # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To avoid polluting the public module namespace, external attributes # should be locally imported at module scope *ONLY* under alternate private # names (e.g., "from argparse import ArgumentParser as _ArgumentParser" rather # than merely "from argparse import ArgumentParser"). #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from argparse import ( ArgumentParser, _SubParsersAction, ) from weakref import ( ProxyTypes, ref, ) # ....................{ TYPES ~ lib }.................... # Types conditionally dependent upon the importability of third-party # dependencies. These types are subsequently redefined by try-except blocks # below and initially default to "UnavailableType" for simple types. # ....................{ TYPES ~ stdlib : argparse }.................... ArgParserType = ArgumentParser ''' Type of argument parsers parsing all command-line arguments for either top-level commands *or* subcommands of those commands. ''' ArgSubparsersType = _SubParsersAction ''' Type of argument subparser containers parsing subcommands for parent argument parsers parsing either top-level commands *or* subcommands of those commands. ''' # ....................{ TYPES ~ stdlib : weakref }.................... WeakRefCType = ref ''' Type of all **unproxied weak references** (i.e., callable objects yielding strong references to their referred objects when called). This type matches both the C-based :class:`weakref.ref` class *and* the pure-Python :class:`weakref.WeakMethod` class, which subclasses the former. ''' # ....................{ TUPLES ~ stdlib : weakref }.................... WeakRefProxyCTypes = ProxyTypes ''' Tuple of all **C-based weak reference proxy classes** (i.e., classes implemented in low-level C whose instances are weak references to other instances masquerading as those instances). This tuple contains classes matching both callable and uncallable weak reference proxies. ''' beartype-0.18.5/beartype/claw/000077500000000000000000000000001461113517100161615ustar00rootroot00000000000000beartype-0.18.5/beartype/claw/__init__.py000066400000000000000000000034721461113517100203000ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype import hook API.** This subpackage publishes :pep:`302`- and :pep:`451`-compliant import hooks enabling external callers to automatically decorate well-typed third-party packages and modules with runtime type-checking dynamically generated by the :func:`beartype.beartype` decorator in a single line of code. ''' # ....................{ TODO }.................... #FIXME: Technically, we're not quite done here. The "beartype.claw" API #currently silently ignores attempts to subject the "beartype" package itself to #@beartyping. Ideally, that API should instead raise human-readable exceptions #when users explicitly attempt to do so when calling either the #beartype_package() or beartype_packages() functions. After implementing that #functionality, assert that in our test suite, please. # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To avoid polluting the public module namespace, external attributes # should be locally imported at module scope *ONLY* under alternate private # names (e.g., "from argparse import ArgumentParser as _ArgumentParser" rather # than merely "from argparse import ArgumentParser"). #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype.claw._clawmain import ( beartype_all as beartype_all, beartype_package as beartype_package, beartype_packages as beartype_packages, beartype_this_package as beartype_this_package, ) from beartype.claw._pkg.clawpkgcontext import ( beartyping as beartyping, ) beartype-0.18.5/beartype/claw/_ast/000077500000000000000000000000001461113517100171075ustar00rootroot00000000000000beartype-0.18.5/beartype/claw/_ast/__init__.py000066400000000000000000000064041461113517100212240ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **abstract syntax tree (AST) functionality** (i.e., low-level callables and classes instrumenting well-typed third-party modules with runtime type-checking dynamically generated by the :func:`beartype.beartype` decorator). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... #FIXME: [PEP 675] *OMG.* See also the third-party "executing" Python package: # https://github.com/alexmojaki/executing # #IPython itself internally leverages "executing" via "stack_data" (i.e., a #slightly higher-level third-party Python package that internally leverages #"executing") to syntax-highlight the currently executing AST node. Indeed, #"executing" sports an intense test suite (much like ours) effectively #guaranteeing a one-to-one mapping between stack frames and AST nodes. # #So, what's the Big Idea here? The Big Idea here is that @beartype can #internally (...possibly only optionally, but possibly mandatorily) leverage #"executing" to begin performing full-blown static type-checking at runtime -- #especially of mission critical type hints like "typing.LiteralString" which can #*ONLY* be type-checked via static analysis. :o # #So, what's the Little Idea here? The Little Idea here is that @beartype can #generate type-checking wrappers that type-check parameters or returns annotated #by "typing.LiteralString" by calling an internal private utility function -- #say, "_die_unless_literalstring(func: Callable, arg_name: str) -> None" -- with #"func" as the current type-checking wrapper and "arg_name" as either the name #of that parameter or "return". The _die_unless_literalstring() raiser then: #* Dynamically searches up the call stack for the stack frame encapsulating an # external call to the passed "func" callable. #* Passes that stack frame to the "executing" package. #* "executing" then returns the AST node corresponding to that stack frame. #* Introspects that node for the passed parameter whose name is "arg_name". #* Raises an exception unless the value of that parameter is an AST node # corresponding to a string literal. # #Of course, that won't necessarily be fast -- but it will be accurate. Since #security trumps speed, speed is significantly less of a concern insofar as #"typing.LiteralString" is concerned. Of course, we should also employ #significant caching... if we even can. #FIXME: Actually, while demonstrably awesome, even the above fails to suffice to #to statically type-check "typing.LiteralString". We failed to fully read PEP #675, which contains a section on inference. In the worst case, nothing less #than a complete graph of the entire app and all transitive dependencies thereof #suffices to decide whether a parameter satisfies "typing.LiteralString". # #Thankfully, the above idea generalizes from "typing.LiteralString" to other #fascinating topics as well. Indeed, given sufficient caching, one could begin #to internally generate and cache a mypy-like graph network whose nodes are #typed attributes and whose edges are relations between those typed attributes. beartype-0.18.5/beartype/claw/_ast/_clawaststar.py000066400000000000000000000111021461113517100221430ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **abstract syntax tree (AST) star imports** (i.e., set of all ``import`` statements importing attributes required by beartype-specific code dynamically injected into otherwise beartype-agnostic user code by the :class:`beartype.claw._ast.clawastmain.BeartypeNodeTransformer` subclass). This private submodule is a "clearing house" of beartype AST imports, intended to be dynamically injected as a single **star-import statement** (e.g., of the syntactic form ``from beartype.claw._ast._clawaststar import *``) injected into the module scope of otherwise beartype-agnostic user code by the :class:`beartype.claw._ast.clawastmain.BeartypeNodeTransformer` subclass. Doing so enables all *other* such beartype-specific code to conveniently reference the attributes explicitly imported and implicitly exported by this submodule. This private submodule is *not* intended for importation by downstream callers. Caveats ------- This private submodule intentionally imports the original attributes into the currently visited submodule under obfuscated beartype-specific names suffixed by the arbitrary substring ``"_beartype__"``, significantly reducing the likelihood of a namespace collision with existing attributes of the same name in that submodule. Since all visited submodules are user-defined and thus outside beartype control, some level of obfuscation is effectively mandatory. ''' #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: *ALL* imports performed below *MUST* be explicitly listed in the # "__all__" dunder global declared before to actually have an effect. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ IMPORTS }.................... # Imports required by PEP-agnostic nodes injected into the current AST by the # "beartype.claw._ast..clawastmain.BeartypeNodeTransformer" superclass. # Import our beartype decorator to be applied by our AST transformer to all # applicable callables and classes in third-party modules. from beartype._decor.decorcache import beartype as __beartype__ # Import our beartype import hook state (i.e., non-thread-safe singleton # centralizing *all* global state maintained by beartype import hooks). from beartype.claw._clawstate import claw_state as __claw_state_beartype__ # ....................{ IMPORTS ~ pep : 526 }.................... # Imports required by PEP 526-compliant nodes injected into the current AST by # "beartype.claw._ast.pep.clawastpep526.BeartypeNodeTransformerPep526Mixin". # Import our beartype exception-raiser (i.e., beartype-specific function raising # exceptions on runtime type-checking violations, applied by our AST transformer # to all applicable PEP 526-compliant annotated variable assignments). # # Note that we intentionally import this exception-raiser from our private # "beartype.door._doorcheck" submodule rather than our public "beartype.door" # subpackage. Why? Because the former consumes marginally less space and time to # import than the latter. Whereas the latter imports the full "TypeHint" type # hierarchy, the former only imports low-level utility functions. from beartype.door._doorcheck import ( die_if_unbearable as __die_if_unbearable_beartype__) # ....................{ IMPORTS ~ pep : 695 }.................... # Imports required by PEP 695-compliant nodes injected into the current AST by # "beartype.claw._ast.pep.clawastpep695.BeartypeNodeTransformerPep695Mixin". # Import our PEP 695-compliant type alias forward reference proxy factory # iterator (i.e., generator iteratively creating and yielding one forward # reference proxy for each unqualified relative forward reference in the passed # PEP 695-compliant type alias). from beartype._util.hint.pep.proposal.utilpep695 import ( iter_hint_pep695_forwardrefs as __iter_hint_pep695_forwardref_beartype__) # ....................{ GLOBALS }.................... __all__ = [ '__beartype__', '__claw_state_beartype__', '__die_if_unbearable_beartype__', '__iter_hint_pep695_forwardref_beartype__', ] ''' Special list global of the unqualified names of all public submodule attributes explicitly exported by and thus safely importable from this submodule. Note that this global *must* be defined. If this global is *not* defined, then importing star imports from this submodule silently reduces to a noop. ''' beartype-0.18.5/beartype/claw/_ast/_clawastutil.py000066400000000000000000000267131461113517100221650ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **abstract syntax tree (AST) mungers** (i.e., low-level callables modifying various properties of various nodes in the currently visited AST). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from ast import ( AST, Attribute, Call, ClassDef, Index, Name, Subscript, expr, keyword, ) from beartype.claw._clawmagic import ( NODE_CONTEXT_LOAD, BEARTYPE_CLAW_STATE_CONF_CACHE_VAR_NAME, BEARTYPE_CLAW_STATE_OBJ_NAME, BEARTYPE_DECORATOR_FUNC_NAME, ) from beartype._data.hint.datahinttyping import ( NodeDecoratable, ) from beartype._conf.confcls import ( BEARTYPE_CONF_DEFAULT, BeartypeConf, ) from beartype._util.ast.utilastmake import ( make_node_kwarg, make_node_name_load, make_node_object_attr_load, make_node_str, ) from beartype._util.ast.utilastmunge import copy_node_metadata from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_9 # ....................{ SUBCLASSES }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: To improve forward compatibility with the superclass API over which # we have *NO* control, avoid accidental conflicts by suffixing *ALL* private # and public attributes of this subclass by "_beartype". #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! class BeartypeNodeTransformerUtilityMixin(object): ''' **Beartype abstract syntax tree (AST) node utility transformer** (i.e., low-level mixin of the high-level :class:`beartype.claw._ast.clawastmain.BeartypeNodeTransformer` subclass supplementing that subclass with various low-level methods creating, modifying, and introspecting common node types and subclass properties). ''' # ....................{ PRIVATE ~ decorators }.................... #FIXME: Unit test us up, please. def _decorate_node_beartype( self, node: NodeDecoratable, conf: BeartypeConf) -> None: ''' Add a new **child beartype decoration node** (i.e., abstract syntax tree (AST) node applying the :func:`beartype.beartype` decorator configured by the passed beartype configuration) to the passed **parent decoratable node** (i.e., AST node encapsulating the definition of a pure-Python object supporting decoration by one or more ``"@"``-prefixed decorations, including both pure-Python classes *and* callables). Note that this function **prepends** rather than appends this child decoration node to the beginning of the list of all child decoration nodes for this parent decoratable node. Since that list is "stored outermost first (i.e. the first in the list will be applied last)", prepending guarantees that the beartype decorator will be applied last (i.e., *after* all other decorators). This ensures that explicitly configured beartype decorations applied to this decoratable by the end user (e.g., ``@beartype(conf=BeartypeConf(...))``) assume precedence over implicitly configured beartype decorations applied by this function. Parameters ---------- node : AST Decoratable node to add a new child beartype decoration node to. conf : BeartypeConf **Beartype configuration** (i.e., dataclass configuring the :mod:`beartype.beartype` decorator for some decoratable object(s) decorated by a parent node passing this dataclass to that decorator). ''' assert isinstance(node, AST), f'{repr(node)} not AST node.' assert isinstance(conf, BeartypeConf), ( f'{repr(conf)} not configuration.') # Child decoration node decorating that callable by our beartype # decorator. beartype_decorator: expr = Name( id=BEARTYPE_DECORATOR_FUNC_NAME, ctx=NODE_CONTEXT_LOAD) # Copy all source code metadata from this parent callable node onto this # child decoration node. copy_node_metadata(node_src=node, node_trg=beartype_decorator) # If the current beartype configuration is *NOT* the default beartype # configuration, this configuration is a user-defined beartype # configuration which *MUST* be passed to a call to the beartype # decorator. Merely referencing this decorator does *NOT* suffice. In # this case... if conf != BEARTYPE_CONF_DEFAULT: # Replace the reference to this decorator defined above with a # call to this decorator passed this configuration. beartype_decorator = Call( func=beartype_decorator, args=[], # Node encapsulating the passing of this configuration as # the "conf" keyword argument to this call. keywords=[ self._make_node_keyword_conf_beartype(node_sibling=node)], ) # Copy all source code metadata from this parent callable node onto # this child call node. copy_node_metadata(node_src=node, node_trg=beartype_decorator) # Else, this configuration is simply the default beartype configuration. # In this case, avoid passing that configuration to the beartype # decorator for both efficiency and simplicity. # Prepend this child decoration node to the beginning of the list of all # child decoration nodes for this parent callable node. Since this list # is "stored outermost first (i.e. the first in the list will be applied # last)", prepending guarantees that our decorator will be applied last # (i.e., *AFTER* all subsequent decorators). This ensures that # explicitly configured @beartype decorations (e.g., # "beartype(conf=BeartypeConf(...))") assume precedence over implicitly # configured @beartype decorations inserted by this hook. node.decorator_list.insert(0, beartype_decorator) # ....................{ PRIVATE ~ factories }.................... #FIXME: Unit test us up, please. def _make_node_keyword_conf_beartype(self, node_sibling: AST) -> keyword: ''' Create and return a new **beartype configuration keyword argument node** (i.e., abstract syntax tree (AST) node passing the beartype configuration associated with the currently visited module as a ``conf`` keyword to a :func:`beartype.beartype` decorator orchestrated by the caller). Parameters ---------- node_sibling : AST Sibling node to copy source code metadata from. Returns ------- keyword Keyword node passing this configuration to an arbitrary function. ''' # Node encapsulating the fully-qualified name of the current module. node_module_name = make_node_str( text=self._module_name_beartype, node_sibling=node_sibling) # type: ignore[attr-defined] # Node encapsulating a reference to the beartype configuration object # cache (i.e., dictionary mapping from fully-qualified module names to # the beartype configurations associated with those modules). node_module_name_to_conf = make_node_object_attr_load( obj_name=BEARTYPE_CLAW_STATE_OBJ_NAME, attr_name='module_name_to_beartype_conf', node_sibling=node_sibling, ) # Node encapsulating the indexation of a dictionary by the # fully-qualified name of the current module. node_module_name_index: AST = None # type: ignore[assignment] # If the active Python interpreter targets Python >= 3.9... if IS_PYTHON_AT_LEAST_3_9: # Reuse this node in a manner specific to Python >= 3.9, which # fundamentally broke backward compatibility with Python 3.8 with # respect to dictionary subscription. node_module_name_index = node_module_name # Else, the active Python interpreter targets Python 3.8. In this case.. else: # Create this node in a manner specific to Python 3.8, which # requires an additional intermediary node *NOT* required under # Python >= 3.9. node_module_name_index = Index(value=node_module_name) # Copy all source code metadata (e.g., line numbers) from this # sibling node onto this new node. copy_node_metadata( node_src=node_sibling, node_trg=node_module_name_index) # Node encapsulating a reference to this beartype configuration, # indirectly (and efficiently) accessed via a dictionary lookup into # this object cache. While cumbersome, this indirection is effectively # "glue" integrating this AST node generation algorithm with the # corresponding Python code subsequently interpreted by Python at # runtime during module importation. node_conf = Subscript( value=node_module_name_to_conf, slice=node_module_name_index, ctx=NODE_CONTEXT_LOAD, ) # Node encapsulating the passing of this beartype configuration as the # "conf" keyword argument to an arbitrary function call of some suitable # "beartype" function orchestrated by the caller. node_keyword_conf = make_node_kwarg( kwarg_name='conf', kwarg_value=node_conf, node_sibling=node_sibling) # Copy all source code metadata (e.g., line numbers) from this sibling # node onto these new nodes. copy_node_metadata(node_src=node_sibling, node_trg=node_conf) # Return this "conf" keyword node. return node_keyword_conf # ..................{ PRIVATE ~ properties }.................. @property def _is_scope_module_beartype(self) -> bool: ''' :data:`True` only if the lexical scope of the currently visited node is the **module scope** (i.e., this node is declared directly in the body of the current user-defined module, implying this node to be a global). Returns ------- bool :data:`True` only if the current lexical scope is a module scope. ''' # Return true only if the stack of all lexical nodes is currently empty, # implying the current node resides directly in the body of a module. return not self._scope_stack_beartype # type: ignore[attr-defined] @property def _is_scope_class_beartype(self) -> bool: ''' :data:`True` only if the lexical scope of the currently visited node is a **class scope** (i.e., this node resides directly in the body of a user-defined class). Returns ------- bool :data:`True` only if the current lexical scope is a class scope. ''' # Return true only if... return ( # The stack of all lexical scope is currently non-empty *AND*... bool(self._scope_stack_beartype) and # type: ignore[attr-defined] # The current node resides directly in the body of a class. self._scope_stack_beartype[-1] is ClassDef # type: ignore[attr-defined] ) beartype-0.18.5/beartype/claw/_ast/clawastmain.py000066400000000000000000000634001461113517100217670ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **abstract syntax tree (AST) transformers** (i.e., low-level classes instrumenting well-typed third-party modules with runtime type-checking dynamically generated by the :func:`beartype.beartype` decorator). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... #FIXME: [PEP 484] Additionally define: #* Generator transformers. The idea here is that @beartype *CAN* actually # automatically type-check generator yields, sends, and returns at runtime. # How? By automatically injecting appropriate die_if_unbearable() calls # type-checking the values to be yielded, sent, and returned against the # appropriate type hints of the current generator factory *BEFORE* yielding, # sending, and returning those values. Shockingly, typeguard already does this # -- which is all manner of impressive. See the # TypeguardTransformer._use_memo() context manager for working code. Wow! # #See also: # https://github.com/agronholm/typeguard/blob/master/src/typeguard/_transformer.py #FIXME: [SPEED] Consider generalizing the BeartypeNodeTransformer.__new__() #class method to internally cache and return "BeartypeNodeTransformer" instances #depending on the passed "conf_beartype" parameter. In general, most codebases #will only leverage a single @beartype configuration (if any @beartype #configuration at all); ergo, caching improves everything by enabling us to #reuse the same "BeartypeNodeTransformer" instance for every hooked module. #Score @beartype! # #See the BeartypeConf.__new__() method for relevant logic. \o/ #FIXME: Oh, wait. We probably do *NOT* want to cache -- at least, not without #defining a comparable reinit() method as we do for "BeartypeCall". After #retrieving a cached "BeartypeNodeTransformer" instance, we'll need to #immediately call BeartypeNodeTransformer.reinit() to reinitialize that #instance. # #This is all feasible, of course -- but let's just roll with the naive #implementation for now, please. # ....................{ IMPORTS }.................... from ast import ( AST, ClassDef, Constant, Expr, ImportFrom, Module, NodeTransformer, ) from beartype.claw._ast.pep.clawastpep526 import ( BeartypeNodeTransformerPep526Mixin) from beartype.claw._ast.pep.clawastpep695 import ( BeartypeNodeTransformerPep695Mixin) from beartype.claw._ast._clawastutil import BeartypeNodeTransformerUtilityMixin from beartype._data.hint.datahinttyping import ( NodeCallable, NodeT, ) from beartype.typing import ( List, Optional, Type, ) from beartype._data.ast.dataast import TYPES_NODE_LEXICAL_SCOPE from beartype._conf.confcls import BeartypeConf # from beartype._util.ast.utilastget import get_node_repr_indented from beartype._util.ast.utilastmake import make_node_importfrom from beartype._util.ast.utilasttest import is_node_callable_typed # ....................{ SUBCLASSES }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: To improve forward compatibility with the superclass API over which # we have *NO* control, avoid accidental conflicts by suffixing *ALL* private # and public attributes of this subclass by "_beartype". #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! #FIXME: Unit test us up, please. class BeartypeNodeTransformer( # PEP-agnostic superclass defining "core" AST node transformation logic. NodeTransformer, # PEP-agnostic mixins defining supplementary AST node functionality in a # PEP-agnostic manner. BeartypeNodeTransformerUtilityMixin, # PEP-specific mixins defining additional AST node transformations in a # PEP-specific manner. BeartypeNodeTransformerPep526Mixin, BeartypeNodeTransformerPep695Mixin, ): ''' **Beartype abstract syntax tree (AST) node transformer** (i.e., visitor pattern recursively transforming the AST tree passed to the :meth:`visit` method by decorating all typed callables and classes by the :func:`beartype.beartype` decorator). Design ------ This class was largely designed by reverse-engineering the standard :mod:`ast` module using the following code snippet. When run as the body of a script from the command line (e.g., ``python3 {muh_script}.py``), this snippet pretty-prints the desired target AST subtree implementing the desired source code (specified in this snippet via the ``CODE`` global). In short, this snippet trivializes the definition of arbitrarily complex AST-based code from arbitrarily complex Python code: .. code-block:: python import ast # Arbitrary desired code to pretty-print the AST representation of. CODE = """ from beartype import beartype from beartype._conf.confcache import beartype_conf_id_to_conf @beartype(conf=beartype_conf_id_to_conf[139870142111616]) def muh_func(): pass """ # Dismantled, this is: # * "indent=...", producing pretty-printed (i.e., indented) output. # * "include_attributes=True", enabling pseudo-nodes (i.e., nodes lacking # associated code metadata) to be distinguished from standard nodes # (i.e., nodes having such metadata). print(ast.dump(ast.parse(CODE), indent=4, include_attributes=True)) Attributes ---------- _conf_beartype : BeartypeConf **Beartype configuration** (i.e., dataclass configuring the :mod:`beartype.beartype` decorator for *all* decoratable objects recursively decorated by this node transformer). _module_name_beartype : str Fully-qualified name of the current module being transformed. _scope_name_beartype : str Fully-qualified name of the current lexical scope (i.e., ``.``-delimited absolute name of the module containing this scope followed by the relative basenames of zero or more classes and/or callables). This name is guaranteed to be prefixed by :attr:`._module_name_beartype`. _scope_stack_beartype : list[type[AST]] **Current lexical scope stack** (i.e., list of the zero or more types of parent nodes of the node being recursively visited by this node transformer such that each of those parent nodes declares a new lexical scope). Specifically: * If this stack is empty, the current node directly resides in the body of a module (i.e., is a global attribute). * If this stack is non-empty, the current node does *not* directly reside in the body of a module. Instead, if the last item of this stack is: * The :class:`ClassDef` node type, the current node directly resides in the body of a class (i.e., is a class attribute or method). * The :class:`FunctionDef` node type, the current node directly resides in the body of a callable (i.e., is a local attribute). See Also -------- https://github.com/agronholm/typeguard/blob/fe5b578595e00d5e45c3795d451dcd7935743809/src/typeguard/importhook.py Last commit of the third-party Python package whose ``@typeguard.importhook.TypeguardTransformer`` class implements import hooks performing runtime type-checking in a similar manner, strongly inspiring this implementation. Note that all subsequent commits to that package generalize those import hooks into something else entirely, which increasingly resembles a static type-checker run at runtime; while fascinating and almost certainly ingenious, those commits are sufficiently inscrutable, undocumented, and unintelligible to warrant caution. Nonetheless, thanks so much to @agronholm (Alex Grönholm) for his pulse-pounding innovations in this burgeoning field! Our AST transformer is for you, @agronholm. ''' # ..................{ CLASS VARIABLES }.................. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Subclasses declaring uniquely subclass-specific instance # variables *MUST* additionally slot those variables. Subclasses violating # this constraint will be usable but unslotted, which defeats the purpose. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Slot all instance variables defined on this object to reduce the costs of # both reading and writing these variables by approximately ~10%. __slots__ = ( '_conf_beartype', '_module_name_beartype', '_scope_name_beartype', '_scope_stack_beartype', ) # ..................{ INITIALIZERS }.................. def __init__( self, # Mandatory keyword-only parameters. *, module_name_beartype : str, conf_beartype: BeartypeConf, ) -> None: ''' Initialize this node transformer. Parameters ---------- module_name_beartype : str Fully-qualified name of the external third-party module being transformed by this node transformer. conf_beartype : BeartypeConf **Beartype configuration** (i.e., dataclass configuring the :mod:`beartype.beartype` decorator for *all* decoratable objects recursively decorated by this node transformer). ''' assert isinstance(module_name_beartype, str), ( f'{repr(module_name_beartype)} not string.') assert isinstance(conf_beartype, BeartypeConf), ( f'{repr(conf_beartype)} not beartype configuration.') # Initialize our superclass. super().__init__() # Classify all passed parameters. self._conf_beartype = conf_beartype self._module_name_beartype = self._scope_name_beartype = ( module_name_beartype) # Nullify all remaining instance variables for safety. self._scope_stack_beartype: List[Type[AST]] = [] # ..................{ SUPERCLASS }.................. # Overridden methods first defined by the "NodeTransformer" superclass. def generic_visit(self, node: NodeT) -> NodeT: ''' Recursively visit and possibly transform *all* child nodes of the passed parent node in-place (i.e., preserving this parent node as is). Parameters ---------- node : NodeT Parent node to transform *all* child nodes of. Returns ------- NodeT Parent node returned and thus preserved as is. ''' # Type of this parent node. node_type = type(node) # If this parent node declares a new lexical scope (i.e., by defining a # new class or callable)... if node_type in TYPES_NODE_LEXICAL_SCOPE: # Fully-qualified name of the current lexical scope *BEFORE* # visiting this new lexical scope. scope_name_old = self._scope_name_beartype # Append to this fully-qualified name the unqualified basename of # this new class or callable declaring this new lexical scope. # # Note that both the "ast.ClassDef" *AND* "ast.FunctionDef" node # types define the "name" instance variable accessed here. self._scope_name_beartype += f'.{node.name}' # type: ignore[attr-defined] # Add the type of this parent node to the top of the stack of all # current lexical scopes *BEFORE* visiting any child nodes of this # parent node. self._scope_stack_beartype.append(node_type) # Recursively visit *ALL* child nodes of this parent node. super().generic_visit(node) # Restore the fully-qualified name of the prior lexical scope. self._scope_name_beartype = scope_name_old # Remove the type of this parent node from the top of the stack of # all current lexical scopes *AFTER* visiting all child nodes of # this parent node. self._scope_stack_beartype.pop() # Else, this parent node does *NOT* declare a new lexical scope. In this # case... else: # Recursively visit all child nodes of this parent node *WITHOUT*. # modifying the stack of all current lexical scopes. super().generic_visit(node) # Return this parent node as is. return node # ..................{ VISITORS ~ module }.................. def visit_Module(self, node: Module) -> Module: ''' Add a new abstract syntax tree (AST) child node to the passed **module node** (i.e., node encapsulating the module currently being loaded by the :class:`beartype.claw._importlib._clawimpload.BeartypeSourceFileLoader`) importing various attributes required by lower-level child nodes added by subsequent visitor methods defined by this transformer. Parameters ---------- node : Module Module node to be transformed. Returns ------- Module That same module node. ''' # 0-based index of an early child node of this parent module node # immediately *BEFORE* which to insert one or more statements importing # beartype-specific attributes, defaulting to the first child node of # this parent module. Specifically, if this module begins with: # * Neither a module docstring *NOR* any "from __future__" imports, this # index is guaranteed to be 0. # * Only a module docstring but *NO* "from __future__" imports, this # index is guaranteed to be 1. # * A module docstring and one or more "from __future__" imports, this # index is guaranteed to be one more than the number of such imports. node_index_import_beartype_attrs = 0 # 0-based index of the last child node of this parent module node. node_index_last = len(node.body) # Child node of this parent module node immediately preceding the output # import child node to be added below, defaulting to this parent module # node to ensure that the copy_node_metadata() function below *ALWAYS* # copies from a valid node (for simplicity). node_prev: AST = node # For the 0-based index and value of each direct child node of this # parent module node... # # This iteration efficiently finds "node_index_import_beartype_attrs" # (i.e., the 0-based index of the first safe position in the list of all # child nodes of this parent module node to insert an import statement # importing our beartype decorator). Despite superficially appearing to # perform a linear search of all n child nodes of this module parent # node and thus exhibit worst-case O(n) time complexity, this iteration # is guaranteed to exhibit worst-case O(1) time complexity. \o/ # # Note that the "node.body" instance variable for module nodes is a list # of *ALL* child nodes of this parent module node. for node_prev in node.body: # print(f'node_index_import_beartype_attrs [IN]: {node_index_import_beartype_attrs}') # If it is *NOT* the case that this child node signifies either... if not ( # A module docstring... # # If that module defines a docstring, that docstring *MUST* be # the first expression of that module. That docstring *MUST* be # explicitly found and iterated past to ensure that the import # statement added below appears *AFTER* rather than *BEFORE* any # docstring. (The latter would destroy the semantics of that # docstring by reducing that docstring to an ignorable string.) ( isinstance(node_prev, Expr) and isinstance(node_prev.value, Constant) ) or # A future import (i.e., import of the form "from __future__ # ...") *OR*... # # If that module performs one or more future imports, these # imports *MUST* necessarily be the first non-docstring # statement of that module and thus appear *BEFORE* all import # statements that are actually imports -- including the import # statement added below. ( isinstance(node_prev, ImportFrom) and node_prev.module == '__future__' ) # Then immediately halt iteration, guaranteeing O(1) runtime. ): break # Else, this child node signifies either a module docstring of # future import. In this case, implicitly skip past this child node # to the next child node. # Insert beartype-specific attributes immediately *AFTER* this node. node_index_import_beartype_attrs += 1 # "node_index_import_beartype_attrs" is now the index of the first safe # position in this list to insert output child import nodes below. # print(f'node_index_import_beartype_attrs [AFTER]: {node_index_import_beartype_attrs}') # print(f'len(node.body): {len(node.body)}') # If the 0-based index of an early child node of this parent module node # immediately *BEFORE* which to insert one or more statements importing # beartype-specific attributes is *NOT* that of the last child node of # this parent module node, this module contains one or more semantically # meaningful child nodes and is thus non-empty. In this case... if node_index_import_beartype_attrs != node_index_last: # print('Injecting beartype imports...') # Module-scoped import nodes (i.e., child nodes to be inserted under # the parent node encapsulating the currently visited submodule in # the AST for that module). # # Note that: # * The original attributes are imported into the currently visited # submodule under obfuscated beartype-specific names, # significantly reducing the likelihood of a namespace collision # with existing attributes of the same name in that submodule. # * These nodes are intentionally *NOT* generalized into global # constants. In theory, doing so would reduce space and time # complexity by enabling efficient reuse here. In practice, doing # so would also be fundamentally wrong; these nodes are # subsequently modified to respect the source code metadata (e.g., # line numbers) of this AST module parent node, which prevents # such trivial reuse. Although we could further attempt to # circumvent that by shallowly or deeply copying from global # constants, both the copy() and deepcopy() functions defined by # the standard "copy" module are pure-Python and thus shockingly # slow -- which defeats the purpose. # Node importing all beartype-specific attributes explicitly # imported and implicitly exported by our private # "beartype.claw._ast.clawaststar" submodule, comprising the set of # all attributes required by code dynamically injected into this AST # by this AST transformer. node_import_all = make_node_importfrom( module_name='beartype.claw._ast._clawaststar', source_attr_name='*', node_sibling=node_prev, ) # Insert these output child import nodes at this safe position of # the list of all child nodes of this parent module node. # # Note that this syntax efficiently (albeit unreadably) inserts # these output child import nodes at the desired index (in this # arbitrary order) of this parent module node. node.body[node_index_import_beartype_attrs:0] = (node_import_all,) # Else, the 0-based index of an early child node of this parent module # node immediately *BEFORE* which to insert one or more statements # importing beartype-specific attributes is that of the last child node # of this parent module node. In this case, this module contains *NO* # semantically meaningful child nodes and is thus effectively empty. # In this case, silently reduce to a noop. This edge case is *EXTREMELY* # uncommon and thus *NOT* optimized for (either here or elsewhere). # # Note that this edge cases cleanly matches: # * Syntactically empty modules containing only zero or more whitespace # characters and zero or more inline comments. # * Syntactically non-empty modules containing only a prefacing module # docstring and/or one or more "from __future__" import statements. # Semantically, these sorts of modules are effectively empty as well. # Recursively transform *ALL* child nodes of this parent module node. node = self.generic_visit(node) # #FIXME: Conditionally perform this logic if "conf.is_debug", please. # print( # f'Module abstract syntax tree (AST) transformed by @beartype to:\n\n' # f'{get_node_repr_indented(node)}' # ) # Return this transformed module node. return node # ..................{ VISITORS ~ class }.................. #FIXME: Implement us up, please. def visit_ClassDef(self, node: ClassDef) -> Optional[ClassDef]: ''' Add a new child node to the passed **class node** (i.e., node encapsulating the definition of a pure-Python class) unconditionally decorating that class by our private :func:`beartype._decor.decorcore.beartype_object_nonfatal` decorator. Parameters ---------- node : ClassDef Class node to be transformed. Returns ------- Optional[ClassDef] This same class node. ''' # Add a new child decoration node to this parent class node decorating # this class by @beartype under this configuration. self._decorate_node_beartype(node=node, conf=self._conf_beartype) # Recursively transform *ALL* child nodes of this parent class node. # Note that doing so implicitly calls the visit_FunctionDef() method # (defined below), each of which then effectively reduces to a noop. return self.generic_visit(node) # ..................{ VISITORS ~ callable }.................. def visit_FunctionDef(self, node: NodeCallable) -> Optional[NodeCallable]: ''' Add a new child node to the passed **callable node** (i.e., node encapsulating the definition of a pure-Python function or method) decorating that callable by our private :func:`beartype._decor.decorcore.beartype_object_nonfatal` decorator if and only if that callable is **typed** (i.e., annotated by a return type hint and/or one or more parameter type hints). Parameters ---------- node : NodeCallable Callable node to be transformed. Returns ------- Optional[NodeCallable] This same callable node. ''' # If... if ( # * This callable node has one or more parent nodes previously # visited by this node transformer *AND* the immediate parent node # of this callable node is a class node, then this callable node # encapsulates a method rather than a function. In this case, the # visit_ClassDef() method defined above has already explicitly # decorated the class defining this method by the @beartype # decorator, which then implicitly decorates both this and all # other methods of that class by that decorator. For safety and # efficiency, avoid needlessly re-decorating this method by the # same decorator by preserving and returning this node as is. # * That is *NOT* the case, then this callable node is either the # root node of the current AST *OR* has a parent node that is not # a class node. In either case, this callable node necessarily # encapsulates a function (rather than a method), which yet to be # decorated. Do so now! So say we all. # # This logic corresponds to the above "That is *NOT* the case" case # (i.e., this callable node necessarily encapsulates a function). # Look. Just accept that we have a tenuous grasp on reality at best. not self._is_scope_class_beartype and # ...and the currently visited callable is annotated by one or more # type hints and thus *NOT* ignorable with respect to beartype # decoration... is_node_callable_typed(node) ): # Add a new child decoration node to this parent callable node # decorating this callable by @beartype under this configuration. self._decorate_node_beartype(node=node, conf=self._conf_beartype) # Else, that callable is ignorable. In this case, avoid needlessly # decorating that callable by @beartype for efficiency. # Recursively transform *ALL* child nodes of this parent callable node. return self.generic_visit(node) beartype-0.18.5/beartype/claw/_ast/pep/000077500000000000000000000000001461113517100176735ustar00rootroot00000000000000beartype-0.18.5/beartype/claw/_ast/pep/__init__.py000066400000000000000000000000001461113517100217720ustar00rootroot00000000000000beartype-0.18.5/beartype/claw/_ast/pep/clawastpep526.py000066400000000000000000000454021461113517100226520ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype :pep:`526`-compliant **abstract syntax tree (AST) transformers** (i.e., low-level classes instrumenting :pep:`526`-compliant annotated variable assignments in well-typed third-party modules with runtime type-checking dynamically generated by the :func:`beartype.beartype` decorator). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from ast import ( AST, AnnAssign, Attribute, Name, ) from beartype.claw._clawmagic import BEARTYPE_RAISER_FUNC_NAME from beartype._data.hint.datahinttyping import NodeVisitResult from beartype._conf.confcls import BEARTYPE_CONF_DEFAULT from beartype._util.ast.utilastmake import ( make_node_call_expr, make_node_kwarg, make_node_name_load, make_node_object_attr_load, make_node_str, ) from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_9 from beartype._util.text.utiltextansi import color_attr_name # ....................{ SUBCLASSES }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: To improve forward compatibility with the superclass API over which # we have *NO* control, avoid accidental conflicts by suffixing *ALL* private # and public attributes of this subclass by "_beartype". #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! class BeartypeNodeTransformerPep526Mixin(object): ''' Beartype :pep:`526`-compliant **abstract syntax tree (AST) node transformer** (i.e., visitor pattern recursively transforming *all* :pep:`526`-compliant annotated variable assignments in the AST tree passed to the :meth:`visit` method of the :class:`beartype.claw._ast.clawastmain.BeartypeNodeTransformer` subclass also subclassing this mixin). ''' # ..................{ VISITORS ~ pep : 526 }.................. def visit_AnnAssign(self, node: AnnAssign) -> NodeVisitResult: ''' Add a new child node to the passed **annotated assignment node** (i.e., node signifying the assignment of an attribute annotated by a :pep:`526`-compliant type hint) inserting a subsequent statement following that annotated assignment type-checking that attribute against that type hint by passing both to our :func:`beartype.door.is_bearable` tester. Note that the :class:`.AnnAssign` subclass defines these instance variables: * ``node.annotation``, a child node describing the PEP-compliant type hint annotating this assignment, typically an instance of either: * :class:`ast.Name`. * :class:`ast.Constant`. Note that this node is *not* itself a valid PEP-compliant type hint and should *not* be treated as such here or elsewhere. * ``node.target``, a child node describing the target attribute assigned to by this assignment, guaranteed to be an instance of either: * :class:`ast.Name`, in which case this is a **simple assignment** (i.e., to a local or global variable). This is the common case in which the attribute being assigned to is *NOT* embedded in parentheses and thus denotes a simple attribute name rather than a full-blown Python expression. * :class:`ast.Attribute`, in which case this is an **object assignment** (i.e., to an instance or class variable of an object). * :class:`ast.Subscript`, in which case this assignment is to the item subscripted by an index of a container rather than to that container itself. * ``node.simple``, an integer :superscript:`sigh` that is either: * If ``node.target`` is an :class:`ast.Name` node, 1. * Else, 0. * ``node.value``, an optional child node defined as either: * If this attribute is actually assigned to, a node encapsulating the new value assigned to this target attribute. * Else, :data:`None`. You may now be thinking to yourself as you wear a bear hat while rummaging through this filthy code: "What do you mean, 'if this attribute is actually assigned to'? Isn't this attribute necessarily assigned to? Isn't that what the 'AnnAssign' subclass means? I mean, it's right there in the bloody subclass name: 'AnnAssign', right? Clearly, *SOMETHING* is bloody well being assigned to. Right?" Wrong. The name of the :class:`.AnnAssign` subclass was poorly chosen. That subclass ambiguously encapsulates both: * Annotated variable assignments (e.g., ``muh_attr: int = 42``). * Annotated variables *without* assignments (e.g., ``muh_attr: int``). Parameters ---------- node : AnnAssign Annotated assignment node to be transformed. Returns ------- NodeVisitResult Either: * If this annotated assignment node is *not* **simple** (i.e., the attribute being assigned to is embedded in parentheses and thus denotes a full-blown Python expression rather than a simple attribute name), that same parent node unmodified. * If this annotated assignment node is *not* **assigned** (i.e., the attribute in question is simply annotated with a type hint rather than both annotated with a type hint *and* assigned to), that same parent node unmodified. * Else, a 2-list comprising both that node and a new adjacent :class:`Call` node performing this type-check. See Also -------- https://github.com/awf/awfutils Third-party Python package whose ``@awfutils.typecheck`` decorator implements statement-level :func:`isinstance`-based type-checking in a similar manner, strongly inspiring this implementation. Thanks so much to Cambridge researcher @awf (Andrew Fitzgibbon) for the phenomenal inspiration! ''' # Recursively transform *ALL* child nodes of this parent callable node. self.generic_visit(node) # type: ignore[attr-defined] # If either... if ( # It is *NOT* the case that... not ( # This beartype configuration enables type-checking of PEP # 526-compliant annotated variable assignments *AND*... self._conf_beartype.claw_is_pep526 and # type: ignore[attr-defined] # This statement is an assignment (e.g., "muh_var: int = 2") # rather than just an unassigned annotation of an attribute # (e.g., "muh_var: int"). node.value # Then either this beartype configuration disables type-checking of # PEP 526-compliant annotated variable assignments *OR* this # statement is just an unassigned annotation of an attribute *OR*... ) or # This assignment node has one or more parent nodes previously # visited by this node transformer *AND* the immediate parent node # of this assignment node is a class node, then this assignment node # encapsulates a PEP 681-compliant annotated field declaration # rather than an PEP 526-compliant annotated variable assignment. In # this case, the visit_ClassDef() method defined above has already # explicitly decorated the class declaring this annotated field by # the @beartype decorator, which then implicitly decorates both this # and all other fields of that class by that decorator. For safety # and efficiency, avoid needlessly re-decorating this field by the # same decorator by simply preserving and returning this node as is. # # Note, however, that this is *NOT* simply an efficiency concern. # This is a significant semantic concern. While a subset of PEP # 681-compliant annotated field declarations *ARE* amenable to # type-checking by our die_if_unbearable(), still others are # absolutely *NOT* amenable to such type-checking. Indeed, in both # the average and the worst case, PEP 681-compliant annotated field # declarations both supersede and violate PEP 484 typing semantics. # Since PEP 681 assumes supremacy over PEP 484 here, @beartype has # little to say and much to ignore: e.g., # # from dataclasses import dataclass, field # # @dataclass # class MuhDataclass(object): # # This annotated field declaration is safely # # type-checkable by die_if_unbearable(), clearly. # muh_safe_field: int = 0xBABECAFE # # # This annotated field declaration is *NOT* safely # # type-checkable by die_if_unbearable(). Clearly, a # # dataclass "field" instance is *NOT* a valid integer and # # thus violates the type hint annotating this field. Since # # PEP 681 standardizes declarations like this as # # semantically valid, @beartype has *NO* alternative but # # to quietly turn a blind eye to what otherwise might be # # considered a type violation. # muh_unsafe_field: int = field(default=0xCAFEBABE) self._is_scope_class_beartype # type: ignore[attr-defined] ): # Then simply preserve and return this node as is. return node # Else: # * This beartype configuration enables type-checking of PEP # 526-compliant annotated variable assignments. # * This assignment is simple and assigning to an attribute name. # Human-readable label prefixing the exception message raised by our # die_if_unbearable() type-checker called below when the value assigned # to this variable violates the type hint annotating this variable. For # efficiency, we precompute this label at import hook time. exception_prefix: str = None # type: ignore[assignment] # Unqualified basename of this variable in the current lexical scope. var_basename: str = None # type: ignore[assignment] # Child node passing the value newly assigned to this attribute by this # assignment as the first parameter to die_if_unbearable(). node_func_arg_pith: AST = None # type: ignore[assignment] # Child node referencing the target variable being assigned to, # localized purely as a negligible optimization. node_target = node.target # If this target variable is a simple local or global variable... if isinstance(node_target, Name): # Unqualified basename of this variable in this lexical scope. var_basename = node_target.id # Child node accessing this local or global variable. node_func_arg_pith = make_node_name_load( name=var_basename, node_sibling=node) # Else, this target variable is *NOT* a simple local or global variable. # # If this target variable is an instance or class variable... elif isinstance(node_target, Attribute): #FIXME: Insufficient. Attributes can contain arbitrary nested child #nodes, including other attributes and/or names. Thankfully, the #only reason to even bother attempting to do this is to rigorously #sanitize line and column numbers -- which doesn't appear to be #particularly necessary or even desirable for dynamically generated #code. For now, we simply shallowly reuse the existing "value" node. # # Child node referencing the object containing this instance or # # class variable (e.g., the "self" in "self.attr: str = 'Attr!'"). # node_func_arg_pith_obj = Name( # node_target.value.id, ctx=NODE_CONTEXT_LOAD) # copy_node_metadata(node_src=node, node_trg=node_func_arg_pith_obj) # Child node referencing this instance or class variable. node_func_arg_pith = make_node_object_attr_load( node_obj=node_target.value, attr_name=node_target.attr, node_sibling=node, ) # If the Python interpreter targets Python >= 3.9, the standard # "ast" module provides the unparse() function "unparsing" (i.e., # obtaining the machine-readable representations of) arbitrary # nodes. In this case... if IS_PYTHON_AT_LEAST_3_9: # Defer version-specific imports. from ast import unparse # type: ignore[attr-defined] # Unqualified basename of this variable in this lexical scope, # defined by "unparsing" this child node. # # Note that the parent object of this attribute is described by # the external node "node_target.value", encapsulating an # arbitrarily complex Python expression. "Unparsing" this # expression manually is *ABSOLUTELY* infeasible. var_basename = unparse(node_target.value) # Else, the Python interpreter targets Python 3.8. In this case, # "ast" fails to provides the unparse() function. Therefore... else: # Unqualified basename of this variable in this lexical scope, # defined by trivially ignoring the arbitrarily complex Python # expression providing the parent object of this attribute. var_basename = node_target.attr # Else, this target variable is *NOT* an instance or class variable. In # this case, this target variable is currently unsupported by this node # transformer for automated type-checking. Simply preserve and return # this node as is. # # Examples include: # * "ast.Subscripted", in which case this target variable is an item of # a container. It is unclear whether PEP 526 even supports annotated # variable assignments of container items *OR* whether any @beartype # users even annotate variable assignments of container items. Ergo, # this node transformer currently ignores this odd edge case. else: return node # List of all nodes encapsulating keyword arguments passed to # die_if_unbearable(), defaulting to the empty list and thus *NO* such # keyword arguments. node_func_kwargs = [] # If the current beartype configuration is *NOT* the default beartype # configuration, this configuration is a user-defined beartype # configuration which *MUST* be passed as well. In this case... if self._conf_beartype != BEARTYPE_CONF_DEFAULT: # type: ignore[attr-defined] # Child node encapsulating the passing of this configuration as the # "conf" keyword argument to die_if_unbearable(). node_func_kwarg_conf = self._make_node_keyword_conf_beartype( # type: ignore[attr-defined] node_sibling=node) # Append this node to the list of all keyword arguments passed to # die_if_unbearable(). node_func_kwargs.append(node_func_kwarg_conf) # Else, this configuration is simply the default beartype configuration. # In this case, avoid passing that configuration to the beartype # decorator for both efficiency and simplicity. # If the lexical scope of this parent node is module scope, this node # encapsulates a global variable assignment. In this case... if self._is_scope_module_beartype: # type: ignore[attr-defined] # Fully-qualified name of this global variable. var_name = f'{self._module_name_beartype}.{var_basename}' # type: ignore[attr-defined] # Human-readable label prefixing this exception message. exception_prefix = f'Global variable "{color_attr_name(var_name)}" ' # Else, the lexical scope of this parent node is *NOT* module scope. # However, by above, this scope is also *NOT* class scope. By # elimination, this scope *MUST* thus be a callable scope. In this # case... else: # Fully-qualified name of the callable defining this local variable. callable_name = f'{self._scope_name_beartype}()' # type: ignore[attr-defined] # Human-readable label prefixing this exception message. exception_prefix = ( f'Callable {color_attr_name(callable_name)} ' f'local variable "{color_attr_name(var_basename)}" ' ) # print(f'PEP 526 exception_prefix: {exception_prefix}') # Child node encapsulating this label as a string literal. node_exception_prefix = make_node_str( text=exception_prefix, node_sibling=node) # Child node encapsulating the passing of this exception prefix as the # "exception_prefix" keyword argument to die_if_unbearable(). node_func_kwarg_exception_prefix = make_node_kwarg( kwarg_name='exception_prefix', kwarg_value=node_exception_prefix, node_sibling=node, ) # Append this node to the list of all keyword arguments passed to # die_if_unbearable(). node_func_kwargs.append(node_func_kwarg_exception_prefix) # Child node type-checking this newly assigned attribute against the # type hint annotating this assignment via our die_if_unbearable() # type-checker. node_func = make_node_call_expr( func_name=BEARTYPE_RAISER_FUNC_NAME, nodes_args=[ # Child node passing the value newly assigned to this # attribute by this assignment as the first parameter. node_func_arg_pith, # Child node passing the type hint annotating this assignment as # the second parameter. node.annotation, ], nodes_kwargs=node_func_kwargs, node_sibling=node, ) # Return a list comprising these two adjacent nodes. # # Note that order is *EXTREMELY* significant. This order ensures that # this attribute is type-checked after being assigned to, as expected. return [node, node_func] beartype-0.18.5/beartype/claw/_ast/pep/clawastpep695.py000066400000000000000000000354261461113517100226660ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype :pep:`695`-compliant **abstract syntax tree (AST) transformers** (i.e., low-level classes instrumenting :pep:`695`-compliant ``type`` alias statements in well-typed third-party modules with runtime type-checking dynamically generated by the :func:`beartype.beartype` decorator). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... #FIXME: CPython's current implementation of PEP 695 type aliases is #fundamentally broken with respect to unquoted relative forward references. #Please submit an upstream issue describing this patent failure. On doing so, #please also publicly declare that PEP 695 appears to have been poorly tested. #As evidence, note that PEP 695 itself advises use of the following idiom: # # A type alias that includes a forward reference # type AnimalOrVegetable = Animal | "Vegetable" # #*THAT DOES NOT ACTUALLY WORK AT RUNTIME.* Nobody tested that. This is why I #facepalm. Notably, PEP 604-compliant new-style unions prohibit strings. They #probably shouldn't, but they've *ALWAYS* behaved that way, and nobody's updated #them to behave more intelligently -- probably because doing so would require #updating the isinstance() builtin (which also accepts PEP 604-compliant #new-style unions) to behave more intelligently and ain't nobody goin' there: # $ python3.12 # >>> type AnimalOrVegetable = "Animal" | "Vegetable" # >>> AnimalOrVegetable.__value__ # Traceback (most recent call last): # Cell In[3], line 1 # AnimalOrVegetable.__value__ # Cell In[2], line 1 in AnimalOrVegetable # type AnimalOrVegetable = "Animal" | "Vegetable" # TypeError: unsupported operand type(s) for |: 'str' and 'str' # #However, even ignoring that obvious syntactic issue, PEP 695 still fails to #actually support forward references -- because exceptions are *NOT* forward #references. Forward references are proxy objects that refer to other objects #that have yet to be defined at runtime. Notably: # $ python3.12 # # This is a forward reference. # >>> type VegetableRef = 'Vegetable' # >>> VegetableRef.__value__ # 'Vegetable' # # # So is this. # >>> from typing import ForwardRef # >>> type FruityRef = ForwardRef('Fruit') # >>> FruityRef.__value__ # ForwardRef('Fruit') # # # This is *NOT* a forward reference. # >>> type AnimalOrAnimals = Animal # >>> AnimalOrAnimals.__value__ # Traceback (most recent call last): # Cell In[2], line 1 # AnimalRef.__value__ # Cell In[1], line 1 in AnimalRef # type AnimalRef = Animal # NameError: name 'Animal' is not defined # #*FACEPALM* #FIXME: *BIG YIKES.* CPython's low-level C-based implementation of PEP #695-compliant type aliases currently fails to properly resolve unquoted #relative forward references defined in a local rather than global scope. I #tried literally everything to get this to work via AST transformations -- but #whatever arcane type alias machinery it is that they've implemented simply does #*NOT* behave as expected at local scope. That said, we've verified this #*SHOULD* work via this simple snippet: # def foo(): # type bar = wut # globals()['wut'] = str # print(bar.__value__) # foo() # #That behaves as expected -- until you actually then define the expected class #at local scope: # def foo(): # type bar = wut # globals()['wut'] = str # print(bar.__value__) # class wut(object): pass # <-- this causes madness; WTF!?!?!? # #The above print() statement now raises non-human readable exceptions #resembling: # NameError: cannot access free variable 'wut' where it is not associated # with a value in enclosing scope # #Clearly, this is madness. At the point at which the print() statement is run, #the "wut" class has yet to be redefined as a class. This constitutes a profound #CPython bug. Please submit us up the F-F-F-bomb. # ....................{ IMPORTS }.................... from ast import ( AST, Assign, # Constant, For, # JoinedStr, Subscript, ) from beartype.claw._clawmagic import ( BEARTYPE_HINT_PEP695_FORWARDREF_ITER_FUNC_NAME) from beartype._data.hint.datahinttyping import NodeVisitResult from beartype._data.ast.dataast import NODE_CONTEXT_STORE from beartype._util.ast.utilastmunge import copy_node_metadata from beartype._util.ast.utilastmake import ( make_node_object_attr_load, make_node_call, # make_node_call_expr, # make_node_fstr_field, make_node_name_load, make_node_name_store, ) # ....................{ SUBCLASSES }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: To improve forward compatibility with the superclass API over which # we have *NO* control, avoid accidental conflicts by suffixing *ALL* private # and public attributes of this subclass by "_beartype". #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! class BeartypeNodeTransformerPep695Mixin(object): ''' Beartype :pep:`695`-compliant **abstract syntax tree (AST) node transformer** (i.e., visitor pattern recursively transforming *all* :pep:`695`-compliant ``type`` alias statements in the AST tree passed to the :meth:`visit` method of the :class:`beartype.claw._ast.clawastmain.BeartypeNodeTransformer` subclass also subclassing this mixin). ''' # ..................{ VISITORS ~ pep : 695 }.................. def visit_TypeAlias(self, node: 'ast.TypeAlias') -> NodeVisitResult: # type: ignore[name-defined] ''' Add new sibling nodes following the passed **type alias statement** (i.e., node signifying the definition of a :pep:`695`-compliant ``type`` alias) iteratively defining one **forward reference proxy** (i.e., :class:`beartype._check.forward.reference.fwdrefabc.BeartypeForwardRefABC` subclass) for each unquoted relative forward reference in this statement. Doing so is required, as :pep:`695` fails to actually support unquoted relative forward references despite publicly claiming to do so. Notably, :pep:`695`-compliant type aliases raise non-human-readable :exc:`NameError` and :exc:`UnboundLocalError` exceptions when attempting to resolve type aliases containing one or more unquoted relative forward references. Clearly, exceptions are *not* valid forward references. Forward references are proxy objects that refer to other objects that have yet to be defined at runtime. Notably, this is very awful: .. code-block:: pycon >>> type AnimalOrAnimals = Animal | list[Animal] >>> AnimalOrAnimals.__value__ Traceback (most recent call last): Cell In[2], line 1 AnimalOrAnimals.__value__ Cell In[1], line 1 in AnimalOrAnimals type AnimalOrAnimals = Animal | list[Animal] NameError: name 'Animal' is not defined Circumventing this patent oversight on the part of both Guido and PEP 695 authors requires transforming this AST to inject new sibling nodes encapsulating each unquoted relative forward reference in this alias with a new forward reference proxy and then redefining this alias to forcefully uncache this alias. Parameters ---------- node : TypeAlias Type alias to be transformed. Returns ------- NodeVisitResult A list comprising (in order): #. This type alias node as is. #. New sibling nodes encapsulating each unquoted relative forward reference in this alias with a new forward reference proxy. #. This type alias node recapitulated to undo any prior caching of this type alias. ''' # Recursively transform *ALL* child nodes of this type alias node. self.generic_visit(node) # type: ignore[attr-defined] # If this type alias is declared at module scope, generate efficient # code permissible *ONLY* at module scope for optimally iteratively # defining one forward reference proxy for each unquoted relative # forward reference in this type alias. Notably, generate this: # for _ in __iter_hint_pep695_forwardref_beartype__({alias_name}): # globals()[_.__name_beartype__] = _ # # Else, this type alias is *NOT* declared at module scope and is thus # declared at a lower scope (e.g., class, callable). In this case, # fallback to generating inefficient code globally permissible at all # possible scopes. Notably, generate this: # for _ in __iter_hint_pep695_forwardref_beartype__({alias_name}): # exec(f'{_.__name_beartype__} = _') # Child nodes both accessing and assigning this type alias as a global # or local variable. node_alias_var_name_load = make_node_name_load( name=node.name.id, node_sibling=node) # node_alias_var_name_store = make_node_name_store( # name=node.name.id, node_sibling=node) # Child nodes both accessing and assigning the standard "_" scratch # (i.e., placeholder) local variable. node_scratch_var_name_load = make_node_name_load( name='_', node_sibling=node) node_scratch_var_name_store = make_node_name_store( name='_', node_sibling=node) # Child node accessing the unqualified basename of the current forward # reference proxy to be defined in the current lexical scope via the # "BeartypeForwardRefABC.__name_beartype__" class variable of this # proxy, which is currently stored in the "_" scratch local variable. # Notably, "_" is a subclass of the "BeartypeForwardRefABC" superclass. node_forwardref_name_load = make_node_object_attr_load( node_obj=node_scratch_var_name_load, attr_name='__name_beartype__', node_sibling=node, ) # Child node passing this iterator this type alias, which then returns a # C-based generator object via the standard Python idiom for # "yield"-specific generators. node_forwardref_iter_call = make_node_call( func_name=BEARTYPE_HINT_PEP695_FORWARDREF_ITER_FUNC_NAME, nodes_args=[node_alias_var_name_load], node_sibling=node, ) # Child node defining a new global or local variable whose: # * Name is the unqualified basename of the undefined attribute referred # to by the currently iterated forward reference proxy. # * Value is that proxy. node_forwardref_define: AST = None # type: ignore[assignment] # Child node subscripting the... node_forwardref_global_store = Subscript( # Dictionary of all currently defined global variables returned by a # call to the builtin globals() function. Thankfully, this # dictionary is efficiently modifiable and behaves in the typical # way when directly modified. # # Note that the same *CANNOT* be said for the builtin locals() # function, whose behaviour is effectively non-deterministic. Ergo, # the inefficient fallback approach adopted below. value=make_node_call(func_name='globals', node_sibling=node), # Assign the key of the returned dictionary whose name is given by # the "BeartypeForwardRefABC.__name_beartype__" class variable of # this proxy, stored in the scratch variable. slice=node_forwardref_name_load, ctx=NODE_CONTEXT_STORE, ) # Child node efficiently defining this proxy as a new global, # implemented as an assignment to... node_forwardref_define = Assign( # The global variable whose name is the unqualified basename of the # undefined attribute referred to by the currently iterated forward # reference proxy. targets=[node_forwardref_global_store], # Assigned the value of the scratch variable, which is a subclass of # the "BeartypeForwardRefABC" superclass. value=node_scratch_var_name_load, ) # Child node iterating over all forward reference proxies generated by # this iterator and, for each such proxy: # * Locally assigning that proxy to the standard "_" scratch (i.e., # placeholder) local variable. # * Defining a new global or local variable whose: # * Name is the unqualified basename of the undefined attribute # referred to by that proxy. # * Value is that proxy. node_forwardrefs_define = For( target=node_scratch_var_name_store, iter=node_forwardref_iter_call, body=[node_forwardref_define], ) # Copy all source code metadata from this type alias node onto *ALL* # sibling nodes created above. copy_node_metadata(node_src=node, node_trg=( node_forwardref_global_store, node_forwardref_define, node_forwardrefs_define, )) # Return a list comprising these adjacent nodes. # # Note that order is *EXTREMELY* significant. return [ # Initial definition of this type alias preserved as is. node, # For loop iteratively defining one forward reference proxy global # or local variable for each undefined attribute in this type alias. node_forwardrefs_define, # Intentionally redefine this alias. Although this appears to be an # inefficient noop, this is in fact an essential operation. Why? # Because the prior successful access of the "__value__" dunder # variable of this type alias in the iter_hint_pep695_forwardrefs() # iterator called above silently cached and thus froze the value of # this alias. However, alias values are *NOT* necessarily safely # freezable at alias definition time. A canonical example of alias # values that are *NOT* safely freezable at alias definition time # is mutually recursive aliases (i.e., aliases whose values # circularly refer to one another): e.g., # type a = b # type b = a # # PEP 695 provides no explicit means of uncaching alias values. Our # only recourse is to repetitiously redefine this alias. It sucks. node, ] beartype-0.18.5/beartype/claw/_clawmagic.py000066400000000000000000000117751461113517100206340ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **magic** (i.e., global constants widely leveraged throughout submodules of the :mod:`beartype.claw` subpackage). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from ast import ( Load, Store, ) from beartype.meta import ( NAME, VERSION, ) # ....................{ AST }.................... NODE_CONTEXT_LOAD = Load() ''' **Node context load singleton** (i.e., object suitable for passing as the ``ctx`` keyword parameter accepted by the ``__init__()`` method of various abstract syntax tree (AST) node classes). ''' NODE_CONTEXT_STORE = Store() ''' **Node context store singleton** (i.e., object suitable for passing as the ``ctx`` keyword parameter accepted by the ``__init__()`` method of various abstract syntax tree (AST) node classes). ''' # ....................{ STRINGS }.................... BEARTYPE_OPTIMIZATION_MARKER = f'{NAME}{VERSION.replace(".", "v")}' ''' **Beartype optimization marker** (i.e., placeholder substring suffixing the ``optimization`` parameter passed to the magical hidden :func:`importlib._bootstrap_external.cache_from_source` function with metadata unique to the currently installed package name and version of :mod:`beartype`). This marker uniquifies the filename of bytecode files compiled under beartype import hooks to the abstract syntax tree (AST) transformation applied by this version of :mod:`beartype`. Why? Because external callers can trivially enable and disable that transformation for any module by either calling or not calling beartype import hooks that accept package name arguments (e.g., :func:`beartype.claw.beartype_package`) with the name of a package transitively containing that module. Compiling a beartyped variant of that module to the same bytecode file as the non-beartyped variant of that module would erroneously persist beartyping to that module -- even *after* removing the relevant call to the :func:`beartype.claw.beartype_package` function! Clearly, that's awful. Enter @agronholm's phenomenal patch, stage left. Caveats ------- **Python requires all optimization markers to be alphanumeric strings.** If this or *any* other optimization marker contains a non-alphanumeric character, Python raises a fatal exception resembling: ValueError: '-beartype-0.14.2' is not alphanumeric Ergo, this string globally replaces *all* non-alphanumeric characters that are otherwise commonly present in the version specifier for this version of :mod:`beartype` by the arbitrary character ``"v`"" (which is *not* present in the name of this package and thus suitable as a machine-readable delimiter). ''' # ....................{ STRINGS ~ names }.................... BEARTYPE_DECORATOR_FUNC_NAME = '__beartype__' ''' Unqualified basename of the beartype decorator as imported into the current user-defined module being imported and thus transformed by the :class:`beartype.claw._ast.clawastmain.BeartypeNodeTransformer` subclass. ''' BEARTYPE_RAISER_FUNC_NAME = '__die_if_unbearable_beartype__' ''' Unqualified basename of the beartype exception-raiser as imported into the current user-defined module being imported and thus transformed by the :class:`beartype.claw._ast.clawastmain.BeartypeNodeTransformer` subclass. ''' # ....................{ STRINGS ~ names ~ claw }.................... BEARTYPE_CLAW_STATE_OBJ_NAME = '__claw_state_beartype__' ''' Unqualified basename of the beartype import hook state as imported into the current user-defined module being imported and thus transformed by the :class:`beartype.claw._ast.clawastmain.BeartypeNodeTransformer` subclass. ''' BEARTYPE_CLAW_STATE_CONF_CACHE_VAR_NAME = 'module_name_to_beartype_conf' ''' Unqualified basename of the **hooked module beartype configuration cache** (i.e., dictionary mapping from the fully-qualified name of each previously imported submodule of each package previously registered in our global package trie to the beartype configuration configuring type-checking by the :func:`beartype.beartype` decorator of that submodule) relative to the beartype import hook state, which contains this cache. ''' # ....................{ STRINGS ~ names : pep : 695 }.................... BEARTYPE_HINT_PEP695_FORWARDREF_ITER_FUNC_NAME = ( '__iter_hint_pep695_forwardref_beartype__') ''' Unqualified basename of the :pep:`695`-compliant **type alias unqualified relative forward reference iterator** (i.e., generator iteratively creating and yielding one forward reference proxy for each unqualified relative forward reference in the passed :pep:`695`-compliant type alias as imported into the current user-defined module being imported and thus transformed by the :class:`beartype.claw._ast.clawastmain.BeartypeNodeTransformer` subclass. ''' beartype-0.18.5/beartype/claw/_clawmain.py000066400000000000000000000456021461113517100204740ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype import hooks** (i.e., public-facing functions integrating high-level :mod:`importlib` machinery required to implement :pep:`302`- and :pep:`451`-compliant import hooks with the abstract syntax tree (AST) transformations defined by the low-level :mod:`beartype.claw._ast.clawastmain` submodule). This private submodule is the main entry point for this subpackage. Nonetheless, this private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... #FIXME: Improve the beartype_package() and beartype_packages() functions to emit #non-fatal warnings when the passed package or packages have already been #imported (i.e., are in the "sys.modules" list). # ....................{ IMPORTS }.................... from beartype.roar import BeartypeClawHookUnpackagedException from beartype.claw._pkg.clawpkgenum import BeartypeClawCoverage from beartype.claw._pkg.clawpkghook import hook_packages from beartype.typing import Iterable from beartype._cave._cavefast import CallableFrameType from beartype._conf.confcls import ( BEARTYPE_CONF_DEFAULT, BeartypeConf, ) from beartype._data.module.datamodpy import SCRIPT_MODULE_NAME from beartype._util.func.utilfuncfile import get_func_filename_or_none from beartype._util.func.utilfuncframe import ( get_frame, get_frame_module_name, get_frame_package_name, ) from pathlib import PurePath # ....................{ HOOKERS }.................... def beartype_all( # Optional keyword-only parameters. *, conf: BeartypeConf = BEARTYPE_CONF_DEFAULT, ) -> None: ''' Register a new **universal beartype import path hook** (i.e., callable inserted to the front of the standard :mod:`sys.path_hooks` list recursively decorating *all* annotated callables, classes, and variable assignments across *all* submodules of *all* packages on the first importation of those submodules with the :func:`beartype.beartype` decorator, wrapping those callables and classes with performant runtime type-checking). This function is the runtime equivalent of a full-blown static type checker like ``mypy`` or ``pyright``, enabling full-stack runtime type-checking of the current app -- including submodules defined by both: * First-party proprietary packages directly authored for this app. * Third-party open-source packages authored and maintained elsewhere. This function is thread-safe. Usage ----- This function is intended to be called from module scope as the first statement of the top-level ``__init__`` submodule of the top-level package of an app to be fully type-checked by :mod:`beartype`. This function then registers an import path hook type-checking *all* annotated callables, classes, and variable assignments across *all* submodules of *all* packages on the first importation of those submodules: e.g., .. code-block:: python # At the very top of "muh_package.__init__": from beartype.claw import beartype_all beartype_all() # <-- beartype all subsequent imports, yo # Import submodules *AFTER* calling beartype_all(). from muh_package._some_module import muh_function # <-- @beartype it! from yer_package.other_module import muh_class # <-- @beartype it! Caveats ------- **This function is not intended to be called from intermediary APIs, libraries, frameworks, or other middleware.** This function is *only* intended to be called from full stack end-user applications as a convenient alternative to manually passing the names of all packages to be type-checked to the more granular :func:`.beartype_packages` function. This function imposes runtime type-checking on downstream reverse dependencies that may not necessarily want, expect, or tolerate runtime type-checking. This function should typically *only* be called by proprietary packages not expected to be reused by others. Open-source packages are advised to call other functions instead. **tl;dr:** *Only call this function in non-reusable end-user apps.* Parameters ---------- conf : BeartypeConf, optional **Beartype configuration** (i.e., dataclass configuring the :mod:`beartype.beartype` decorator for *all* decoratable objects recursively decorated by the path hook added by this function). Defaults to ``BeartypeConf()``, the default :math:`O(1)` configuration. Raises ------ BeartypeClawHookException If the passed ``conf`` parameter is *not* a beartype configuration (i.e., :class:`BeartypeConf` instance). ''' # The advantage of one-liners is the vantage of vanity. hook_packages(claw_coverage=BeartypeClawCoverage.PACKAGES_ALL, conf=conf) def beartype_this_package( # Optional keyword-only parameters. *, conf: BeartypeConf = BEARTYPE_CONF_DEFAULT, ) -> None: ''' Register a new **current package beartype import path hook** (i.e., callable inserted to the front of the standard :mod:`sys.path_hooks` list recursively applying the :func:`beartype.beartype` decorator to *all* annotated callables, classes, and variable assignments across *all* submodules of the current user-defined package calling this function on the first importation of those submodules). This function is thread-safe. Usage ----- This function is intended to be called from module scope as the first statement of the top-level ``__init__`` submodule of any package to be type-checked by :mod:`beartype`. This function then registers an import path hook type-checking *all* annotated callables, classes, and variable assignments across *all* submodules of that package on the first importation of those submodules: e.g., .. code-block:: python # At the very top of "muh_package.__init__": from beartype.claw import beartype_this_package beartype_this_package() # <-- beartype all subsequent imports, yo # Import package submodules *AFTER* calling beartype_this_package(). from muh_package._some_module import muh_function # <-- @beartype it! from muh_package.other_module import muh_class # <-- @beartype it! Parameters ---------- conf : BeartypeConf, optional **Beartype configuration** (i.e., dataclass configuring the :mod:`beartype.beartype` decorator for *all* decoratable objects recursively decorated by the path hook added by this function). Defaults to ``BeartypeConf()``, the default :math:`O(1)` configuration. Raises ------ BeartypeClawHookException If the passed ``conf`` parameter is *not* a beartype configuration (i.e., :class:`.BeartypeConf` instance). BeartypeClawHookUnpackagedException If this function is called from outside any package structure (e.g., top-level module or executable script). ''' # Stack frame encapsulating the user-defined lexical scope directly calling # this import hook. # # Note that: # * This call is guaranteed to succeed without error. Why? Because: # * The current call stack *ALWAYS* contains at least one stack frame. # Ergo, get_frame(0) *ALWAYS* succeeds without error. # * The call to this import hook guaranteeably adds yet another stack # frame to the current call stack. Ergo, get_frame(1) also *ALWAYS* # succeeds without error in this context. # * This and the following logic *CANNOT* reasonably be isolated to a new # private helper function. Why? Because this logic itself calls existing # private helper functions assuming the caller to be at the expected # position on the current call stack. frame_caller: CallableFrameType = get_frame(1) # type: ignore[assignment,misc] # Fully-qualified name of the parent package of the child module defining # that caller if that module resides in some package *OR* the empty string # otherwise (i.e., if that module is a top-level module or script residing # outside any package structure). frame_package_name = get_frame_package_name(frame_caller) # print(f'beartype_this_package: {frame_caller_package_name}') # print(f'beartype_this_package: {repr(frame_caller)}') #FIXME: Is "pragma: no cover" accurate here? Is this condition untestable? # If that module has *NO* parent package, raise an exception. Why? Because # this function uselessly (but silently) reduces to a noop when called from # a top-level module or script residing outside any package. Why? Because # this function installs an import hook applicable only to subsequently # imported submodules of the current package. By definition, a top-level # module or script has *NO* package and thus *NO* sibling submodules and # thus *NO* meaningful imports to be hooked. To avoid unwanted confusion, we # intentionally notify the user with a loud exception. if not frame_package_name: # pragma: no cover # Exception message to be raised below. exception_message: str = None # type: ignore[assignment] # Fully-qualified name of the module encapsulating the caller. frame_module_name = get_frame_module_name( frame=frame_caller, exception_cls=BeartypeClawHookUnpackagedException, ) # If the caller is a script rather than a module, this name is the # useless magic string "__main__". In this case... if frame_module_name == SCRIPT_MODULE_NAME: # Absolute filename of this script if this script physically resides # on the local filesystem *OR* "None" otherwise (i.e., if this # script is dynamically defined in-memory). frame_filename = get_func_filename_or_none(frame_caller) # If this script physically exists... if frame_filename: # Prefix this message appropriately. exception_message_prefix = ( f'Top-level script "{frame_filename}" ') # Else, this script only exists in memory. In this case... else: # Prefix this message appropriately. exception_message_prefix = 'In-memory script ' # Fabricate an arbitrary filename. Just do it! frame_filename = 'scripts/main.py' # Path object encapsulating this filename. frame_path = PurePath(frame_filename) # Basename of the parent directory containing this script, defined # as either... frame_package_basename = ( # If this filename contains at least two basenames, then: # * The last basename is that of this script. # * The second-to-last basename is that of the parent directory # containing this script. frame_path.parts[-2] if len(frame_path.parts) >= 2 else # Else, this filename contains only the basename of this script. # In this case, fabricate an arbitrary basename. Just do it! 'scripts' ) # Exception message to be raised below. exception_message = ( f'{exception_message_prefix}resides outside package structure. ' f'Consider calling another "beartype.claw" import hook. ' f'However, note that only other modules will be type-checked. ' f'"{frame_filename}" itself will remain unchecked. ' f'All business logic should reside in submodules ' f'subsequently imported by "{frame_filename}": e.g.,\n' f' # Instead of this at the top of "{frame_filename}"...\n' f' from beartype.claw import beartype_this_package # <-- you are here\n' f' beartype_this_package() # <-- feels bad\n' f'\n' f' # ...pass the basename of the "{frame_package_basename}/" subdirectory explicitly.\n' f' from beartype.claw import beartype_package # <-- you want to be here\n' f' beartype_package("{frame_package_basename}") # <-- feels good\n' f'\n' f' from {frame_package_basename}.main_submodule import main_func # <-- still feels good\n' f' main_func() # <-- *GOOD*! "beartype.claw" type-checks this\n' f' some_global: str = 0xFEEDFACE # <-- *BAD*! "beartype.claw" ignores this\n' f'This has been a message from your friendly neighbourhood bear.' ) # Else, the caller is a module with a useful name. In this case, define # an exception message. # # Note that this edge case implies that this is a top-level module # residing outside a package that was *NOT* run as a script. Since this # should *BASICALLY* never occur, there isn't terribly much we can do. else: exception_message = ( f'Top-level module "{frame_module_name}" ' f'resides outside package structure but was ' f'*NOT* directly run as a script. ' f'"beartype.claw" import hooks require that modules either ' f'reside inside a package structure or be ' f'directly run as scripts. ' f'Since neither applies here, you are now off the deep end. ' f'@beartype no longer has any idea what is going on, sadly. ' f'Consider directly decorating classes and functions by the ' f'@beartype.beartype decorator instead: e.g.,\n' f' # Instead of this at the top of "{frame_module_name}"...\n' f' from beartype.claw import beartype_this_package # <-- you are here\n' f' beartype_this_package() # <-- feels bad\n' f'\n' f" # ...go old-school like it's 2017 and you just don't care.\n" f' from beartype import beartype # <-- you want to be here\n' f' @beartype # <-- feels good, yet kinda icky at same time\n' f' def spicy_func() -> str: ... # <-- *GOOD*! @beartype type-checks this\n' f' some_global: str = 0xFEEDFACE # <-- *BAD*! @beartype ignores this, but what can you do\n' f'For your safety, @beartype will now crash and burn.' ) # Raise an exception. raise BeartypeClawHookUnpackagedException(exception_message) # Else, that module has a parent package. # Add a new import path hook beartyping this package. hook_packages( claw_coverage=BeartypeClawCoverage.PACKAGES_ONE, package_name=frame_package_name, conf=conf, ) #FIXME: Add a "Usage" docstring section resembling that of the docstring for the #beartype_this_package() function. def beartype_package( # Mandatory parameters. package_name: str, # Optional keyword-only parameters. *, conf: BeartypeConf = BEARTYPE_CONF_DEFAULT, ) -> None: ''' Register a new **single package beartype import path hook** (i.e., callable inserted to the front of the standard :mod:`sys.path_hooks` list recursively applying the :func:`beartype.beartype` decorator to *all* annotated callables, classes, and variable assignments across *all* submodules of the package with the passed names on the first importation of those submodules). This function is thread-safe. Parameters ---------- package_name : str Fully-qualified name of the package to be type-checked. conf : BeartypeConf, optional **Beartype configuration** (i.e., dataclass configuring the :mod:`beartype.beartype` decorator for *all* decoratable objects recursively decorated by the path hook added by this function). Defaults to ``BeartypeConf()``, the default :math:`O(1)` configuration. Raises ------ BeartypeClawHookException If either: * The passed ``conf`` parameter is *not* a beartype configuration (i.e., :class:`BeartypeConf` instance). * The passed ``package_name`` parameter is either: * *Not* a string. * The empty string. * A non-empty string that is *not* a valid **package name** (i.e., ``"."``-delimited concatenation of valid Python identifiers). ''' # Add a new import path hook beartyping this package. hook_packages( claw_coverage=BeartypeClawCoverage.PACKAGES_ONE, package_name=package_name, conf=conf, ) #FIXME: Add a "Usage" docstring section resembling that of the docstring for the #beartype_this_package() function. def beartype_packages( # Mandatory parameters. package_names: Iterable[str], # Optional keyword-only parameters. *, conf: BeartypeConf = BEARTYPE_CONF_DEFAULT, ) -> None: ''' Register a new **multiple package beartype import path hook** (i.e., callable inserted to the front of the standard :mod:`sys.path_hooks` list recursively applying the :func:`beartype.beartype` decorator to *all* annotated callables, classes, and variable assignments across *all* submodules of all packages with the passed names on the first importation of those submodules). This function is thread-safe. Parameters ---------- package_names : Iterable[str] Iterable of the fully-qualified names of one or more packages to be type-checked. conf : BeartypeConf, optional **Beartype configuration** (i.e., dataclass configuring the :mod:`beartype.beartype` decorator for *all* decoratable objects recursively decorated by the path hook added by this function). Defaults to ``BeartypeConf()``, the default :math:`O(1)` configuration. Raises ------ BeartypeClawHookException If either: * The passed ``conf`` parameter is *not* a beartype configuration (i.e., :class:`BeartypeConf` instance). * The passed ``package_names`` parameter is either: * Non-iterable (i.e., fails to satisfy the :class:`collections.abc.Iterable` protocol). * An empty iterable. * A non-empty iterable containing at least one item that is either: * *Not* a string. * The empty string. * A non-empty string that is *not* a valid **package name** (i.e., ``"."``-delimited concatenation of valid Python identifiers). ''' # Add a new import path hook beartyping these packages. hook_packages( claw_coverage=BeartypeClawCoverage.PACKAGES_MANY, package_names=package_names, conf=conf, ) beartype-0.18.5/beartype/claw/_clawstate.py000066400000000000000000000152551461113517100206710ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **import hook state** (i.e., data class singletons safely centralizing *all* global state maintained by beartype import hooks, enabling each external unit test in our test suite to trivially reset that state after completion of that test). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.claw._importlib.clawimpcache import ModuleNameToBeartypeConf from beartype.claw._pkg.clawpkgtrie import PackagesTrie from beartype.typing import Optional from beartype._data.hint.datahinttyping import ImportPathHook from threading import RLock # ....................{ CLASSES }.................... class BeartypeClawState(object): ''' **Beartype import hook state** (i.e., non-thread-safe singleton safely centralizing global state maintained by beartype import hooks, enabling each external unit test in our test suite to trivially reset that state after completion of that test). Attributes ---------- beartype_pathhook : Optional[ImportPathHook] Either: * If the :func:`beartype.claw._importlib.clawimppath.add_beartype_pathhook` function has been previously called at least once under the active Python interpreter and the :func:`beartype.claw._importlib.clawimppath.add_beartype_pathhook` function has not been called more recently, the **Beartype import path hook singleton** (i.e., factory closure creating and returning a new :class:`importlib.machinery.FileFinder` instance itself creating and leveraging a new :class:`.BeartypeSourceFileLoader` instance). * Else, :data:`None` otherwise. Initialized to :data:`None`. module_name_to_beartype_conf : ModuleNameToBeartypeConf **Hooked module beartype configuration cache** (i.e., non-thread-safe dictionary mapping from the fully-qualified name of each previously imported submodule of each package previously registered in our global package trie to the beartype configuration configuring type-checking by the :func:`beartype.beartype` decorator of that submodule). packages_trie : PackagesTrie **Package configuration trie** (i.e., non-thread-safe recursively nested dictionary implementing a prefix tree such that each key-value pair maps from the unqualified basename of each subpackage to be type-checked on the first importation of that subpackage to another instance of the :class:`.PackagesTrie` class similarly describing the sub-subpackages of that subpackage). ''' # ..................{ CLASS VARIABLES }.................. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Subclasses declaring uniquely subclass-specific instance # variables *MUST* additionally slot those variables. Subclasses violating # this constraint will be usable but unslotted, which defeats our purposes. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Slot all instance variables defined on this object to minimize the time # complexity of both reading and writing variables across frequently called # cache dunder methods. Slotting has been shown to reduce read and write # costs by approximately ~10%, which is non-trivial. __slots__ = ( 'beartype_pathhook', 'module_name_to_beartype_conf', 'packages_trie', ) # ....................{ INITIALIZERS }.................... def __init__(self) -> None: # Nullify the proper subset of instance variables requiring # nullification *BEFORE* reinitializing this singleton. self.beartype_pathhook: Optional[ImportPathHook] = None # Reinitialize this singleton safely. self._reinit_safe() def _reinit_safe(self) -> None: ''' Reinitialize *all* beartype import hook state encapsulated by this data class back to their initial defaults, trivially clearing *all* metadata pertaining to previously hooked packages and configurations installed by previously called beartype import hooks. This method performs the subset of reinitialization that is safe to be called from the :meth:`__init__` method. ''' # One one-liner to reinitialize them all. self.module_name_to_beartype_conf = ModuleNameToBeartypeConf() self.packages_trie = PackagesTrie(package_basename=None) def reinit(self) -> None: ''' Reinitialize *all* beartype import hook state encapsulated by this data class back to their initial defaults, trivially clearing *all* metadata pertaining to previously hooked packages and configurations installed by previously called beartype import hooks. ''' # Avoid circular import dependencies. from beartype.claw._importlib.clawimppath import ( remove_beartype_pathhook) # Perform the subset of reinitialization that is safe to be called from # the __init__() method. self._reinit_safe() # Perform the remainder of reinitialization that is unsafe to be called # from the __init__() method. # # Remove our beartype import path hook if this path hook has already # been added (e.g., by a prior call to an import hook) *OR* silently # reduce to a noop otherwise. remove_beartype_pathhook() # ..................{ DUNDERS }.................. def __repr__(self) -> str: return '\n'.join(( f'{self.__class__.__name__}(\n', f' beartype_pathhook={repr(self.beartype_pathhook)},\n', f' module_name_to_beartype_conf={repr(self.module_name_to_beartype_conf)},\n', f' packages_trie={repr(self.packages_trie)},\n', f')', )) # ....................{ GLOBALS }.................... claw_lock = RLock() ''' Reentrant reusable thread-safe context manager gating access to the otherwise non-thread-safe :data:`.claw_state` global. ''' claw_state = BeartypeClawState() ''' **Beartype import hook state** (i.e., non-thread-safe singleton safely centralizing *all* global state maintained by beartype import hooks, enabling each external unit test in our test suite to trivially reset that state after completion of that test). ''' beartype-0.18.5/beartype/claw/_importlib/000077500000000000000000000000001461113517100203215ustar00rootroot00000000000000beartype-0.18.5/beartype/claw/_importlib/__init__.py000066400000000000000000000000001461113517100224200ustar00rootroot00000000000000beartype-0.18.5/beartype/claw/_importlib/_clawimpload.py000066400000000000000000000623441461113517100233370ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **import hook module loaders** (i.e., :mod:`importlib`-compliant classes dynamically decorating all typed callables and classes of all submodules of all packages previously registered in our global package trie by the :func:`beartype.beartype` decorator via abstract syntax tree (AST) transformers defined by the :mod:`beartype.claw._ast.clawastmain` submodule). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from ast import PyCF_ONLY_AST from beartype.claw._ast.clawastmain import BeartypeNodeTransformer from beartype.claw._importlib.clawimpcache import ( # type: ignore[attr-defined] cache_from_source_beartype, cache_from_source_original, ) from beartype.roar import BeartypeClawImportAstException from beartype.typing import Optional from beartype._conf.confcls import BeartypeConf from beartype._util.ast.utilastget import get_node_repr_indented from beartype._util.text.utiltextlabel import label_exception from importlib import ( # type: ignore[attr-defined] _bootstrap_external, # pyright: ignore ) from importlib.machinery import SourceFileLoader from importlib.util import decode_source from types import CodeType # ....................{ CLASSES }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: To improve forward compatibility with the superclass API over which # we have *NO* control, avoid accidental conflicts by suffixing *ALL* private # and public attributes of this subclass by "_beartype". #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! #FIXME: Unit test us up, please. class BeartypeSourceFileLoader(SourceFileLoader): ''' **Beartype source file loader** implementing :mod:`importlib` machinery loading a **sourceful Python package or module** (i.e., package or module backed by a ``.py``-suffixed source file) into a **module spec** (i.e., in-memory :class:`importlib._bootstrap.ModuleSpec` instance describing the importation of that package or module, complete with a reference back to this originating loader). The :func:`beartype_package` function injects a low-level **import path hook** (i.e., factory closure instantiating this class as an item of the standard :mod:`sys.path_hooks` list) to the front of that list. When called by a higher-level parent **import metapath hook** (i.e., object suitable for use as an item of the standard :mod:`sys.meta_path` list), that closure: #. Instantiates one instance of the standard :class:`importlib._bootstrap_external.FileFinder` class for each **imported Python package** (i.e., package on the :mod:`sys.path` list). The :meth:``importlib._bootstrap_external.FileFinder.find_spec` method of that instance then returns this :class:`BeartypeSourceFileLoader` class uninstantiated for each **imported Python package submodule** (i.e., submodule directly contained in that package). #. Adds a new key-value pair to the standard :mod:`sys.path_importer_cache` dictionary, whose: * Key is the package of that module. * Value is that instance of this class. Motivation ---------- This loader was intentionally implemented so as to exclusively leverage the lower-level :attr:`sys.path_hooks` mechanism for declaring import hooks rather than both that *and* the higher-level :attr:`sys.meta_path` mechanism. All prior efforts in the Python ecosystem to transform the abstract syntax trees (ASTs) of modules at importation time via import hooks leverage both mechanisms. This includes: * :mod:`pytest`, which rewrites test assertion statements via import hooks leveraging both mechanisms. * :mod:`typeguard`, which implicitly applies the runtime type-checking :func:`typguard.typechecked` decorator via import hooks leveraging both mechanisms. * :mod:`ideas`, which applies arbitrary caller-defined AST transformations via (...wait for it) import hooks leveraging both mechanisms. Beartype subverts this long-storied tradition by *only* leveraging the lower-level :attr:`sys.path_hooks` mechanism. Doing so reduces maintenance burden, code complexity, and inter-package conflicts. The latter is particularly salient. AST transformations applied by both :mod:`typeguard` and :mod:`ideas` accidentally conflict with those applied by :mod:`pytest`. Why? Because (in order): #. When run as a test suite runner, :mod:`pytest` necessarily runs first and thus prepends its import hook as the new first item of the :attr:`sys.meta_path` list. #. When imported during subsequent unit and/or integration testing under that test suite runner, :mod:`typeguard` and :mod:`ideas` then install their own import hooks as the new first item of the :attr:`sys.meta_path` list. The import hook previously prepended by :mod:`pytest` then becomes the second item of the :attr:`sys.meta_path` list. Python consults both the :attr:`sys.meta_path` and :attr:`sys.path_hooks` lists in a first-come-first-served manner. The first hook on each list satisfying a request to find and import a module being imported terminates that request; no subsequent hooks are consulted. Both :mod:`typeguard` and :mod:`ideas` fail to iteratively consult subsequent hooks (e.g., with a piggybacking scheme of some sort). Both squelch the hook previously installed by :mod:`pytest` that rewrote assertions. That is bad. Attributes ---------- _module_conf_beartype : Optional[BeartypeConf] Either: * If the most recent call to the :meth:`get_code` method (which loads a module by creating and return the code object underlying that module) was passed the fully-qualified name of a module with a transitive parent package previously registered by a call to a public :mod:`beartype.claw` import hook factory (e.g., :func:`beartype.claw.beartype_package`), the beartype configuration with which to type-check that module. * Else, :data:`None`. This instance variable enables our override of the parent :meth:`.get_code` method to communicate this configuration to the child :meth:`.source_to_code` method, which fails to accept and thus has *no* access to this module name. The superclass implementation of the :meth:`.get_code` method then internally calls our override of the :meth:`.source_to_code` method, which accesses this instance variable to decide whether and how to type-check that module. Ordinarily, this approach would be fraught with fragility. For example, what if something *other* than the :meth:`get_code` method called the :meth:`.source_to_code` method? Thankfully, that is *not* a concern here. :meth:`.source_to_code` is only called by :meth:`get_code` in the :mod:`importlib` codebase. Ergo, :meth:`source_to_code` should ideally have been privatized (e.g., as ``_source_to_code()``). _module_name_beartype : str Fully-qualified name of the module currently being imported by the :meth:`.get_code` method for subsequent reference in the lower-level :meth:`.source_to_code` method transitively called by the former. See Also -------- * The `comparable "typeguard.importhook" submodule `__ implemented by the incomparable `@agronholm (Alex Grönholm) `__, whose intrepid solutions strongly inspired this subpackage. `Typeguard's import hook infrastructure `__ is a significant improvement over the prior state of the art in Python and a genuine marvel of concise, elegant, and portable abstract syntax tree (AST) transformation. .. _agronholm: https://github.com/agronholm .. _typeguard import hook: https://github.com/agronholm/typeguard/blob/master/src/typeguard/importhook.py ''' # ..................{ INITIALIZERS }.................. def __init__(self, *args, **kwargs) -> None: ''' Initialize this beartype source file loader. All passed parameters are passed as is to the superclass method, which then calls our lower-level :meth:`source_to_code` subclass method overridden below. ''' # Initialize our superclass with all passed parameters. super().__init__(*args, **kwargs) # Nullify all subclass-specific instance variables for safety. self._module_conf_beartype: Optional[BeartypeConf] = None self._module_name_beartype: str = None # type: ignore[assignment] # ..................{ LOADER API }.................. # The importlib._bootstrap_external.*Loader API declares the low-level # exec_module() method, which accepts an "importlib._bootstrap.ModuleSpec" # instance created and returned by a prior call to the higher-level # find_spec() method documented above; the exec_module() method then uses # that module spec to create and return a fully imported module object # (i.e., "types.ModuleType" instance). To do so: # * The default exec_module() implementation internally calls the # lower-level get_code() method returning an in-memory Python code object # deserialized from the on-disk or in-memory bytes underlying that module. # * The default get_code() implementation internally calls the # lower-level source_to_code() method returning an in-memory Python code # object dynamically compiled from the passed in-memory bytes. def get_code(self, fullname: str) -> Optional[CodeType]: ''' Create and return the code object underlying the module with the passed name. This override of the superclass :meth:`SourceLoader.get_code` method internally follows one of two distinct code paths, conditionally depending on whether a parent package transitively containing that module has been previously registered with the :mod:`beartype.claw._pkg.clawpkghook` submodule (e.g., by a call to the :func:`beartype.claw.beartype_package` function). Specifically: * If *no* parent package transitively containing that module has been registered, this method fully defers to the superclass :meth:`SourceLoader.get_code` method. * Else, one or more parent packages transitively containing that module have been registered. In this case, this method (in order): #. Temporarily monkey-patches (i.e., replaces) the private :func:`importlib._bootstrap_external.cache_from_source` function with our beartype-specific :func:`_cache_from_source_beartype` variant. #. Calls the superclass :meth:`SourceLoader.get_code` method, which: #. Calls our override of the lower-level superclass :meth:`SourceLoader.source_to_code` method. #. Restores the :func:`importlib._bootstrap_external.cache_from_source` function to its original implementation. Motivation ---------- The temporary monkey-patch applied by this method is strongly inspired by a suspiciously similar temporary monkey-patch applied by the external :meth:`typeguard._importhook.TypeguardLoader.exec_module` method authored by the incomparable @agronholm (Alex Grönholm), who writes: Use a custom optimization marker – the import lock should make this monkey patch safe The aforementioned "custom optimization marker" is, in fact, a beartype-specific constant embedded in the filename of the cached Python bytecode file to which that module is byte-compiled. This filename typically resembles ``__pycache__/{module_basename}.{optimization_markers}.pyc``, where: * ``{module_basename}`` is the unqualified basename of that module. * ``{optimization_markers}`` is a ``"-"``-delimited string of **optimization markers** (i.e., arbitrary alphanumeric labels uniquifying this bytecode file to various bytecode-specific metadata, including the name and version of the active Python interpreter). This monkey-patch suffixes ``{optimization_markers}`` by :data:`.BEARTYPE_OPTIMIZATION_MARKER`, which additionally uniquifies the filename of this bytecode file to the abstract syntax tree (AST) transformation applied by this version of :mod:`beartype`. Why? Because external callers can trivially enable and disable that transformation for any module by either calling or not calling the :func:`beartype.claw.beartype_package` function with the name of a package transitively containing that module. Compiling a beartyped variant of that module to the same bytecode file as the non-beartyped variant of that module would erroneously persist beartyping to that module -- even *after* removing the relevant call to the :func:`beartype.claw.beartype_package` function! Clearly, that's awful. Enter @agronholm's phenomenal patch, stage left. We implicitly trust @agronholm to get that right in a popular project stress-tested across hundreds of open-source projects over the past several decades. So, we avoid explicit thread-safe locking here. Lastly, note there appears to be *no* other means of safely implementing this behaviour *without* violating Don't Repeat Yourself (DRY). Specifically, doing so would require duplicating most of the entirety of the *extremely* non-trivial nearly 100 line-long :meth:`importlib._bootstrap_external.SourceLoader.get_code` method. Since duplicating non-trivial and fragile code inherently tied to a specific CPython version is considerably worse than applying a trivial one-line monkey-patch, first typeguard and now @beartype strongly prefer this monkey-patch. Did we mention that @agronholm is amazing? Because that really bears repeating. May the name of Alex Grönholm live eternal! Caveats ------- This getter intentionally avoids all dangerous attempts to recursively type-check the :mod:`beartype` package by the :func:`beartype.beartype` decorator. Doing so would be: * **Fundamentally unnecessary.** The entirety of the :mod:`beartype` package already religiously guards against type violations with a laborious slew of type checks littered throughout the codebase -- including assertions of the form ``"assert isinstance({arg}, {type}), ..."``. Further decorating *all* :mod:`beartype` callables with automated type-checking only needlessly reduces the runtime efficiency of the :mod:`beartype` package. * **Fundamentally dangerous**, which is the greater concern. For example, the :meth:`beartype.claw._ast.clawastmain.BeartypeNodeTransformer.visit_Module` method dynamically inserts a module-scoped import of the :func:`beartype._decor.decorcore.beartype_object_nonfatal` decorator at the head of the module currently being imported. But if the :mod:`beartype._decor.decorcore` submodule itself is being imported, then that importation would destructively induce an infinite circular import! Could that ever happen? **YES.** Conceivably, an external caller could force reimportation of all modules by emptying the :mod:`sys.modules` cache. Note this edge case is surprisingly common. The public :func:`beartype.claw.beartype_all` function implicitly registers *all* packages (including :mod:`beartype` itself by default) for decoration by the :func:`beartype.beartype` decorator. Parameters ---------- fullname : str Fully-qualified name of the module currently being imported. Returns ------- Optional[CodeType] Code object underlying that module. ''' # Avoid circular import dependencies. from beartype.claw._clawstate import claw_state from beartype.claw._pkg.clawpkgtrie import get_package_conf_or_none # Beartype configuration with which to type-check that module if that # module is hooked *OR* "None" otherwise (i.e., if that module is # unhooked), defined as either... conf = ( # If that module is either the top-level "beartype" package *OR* a # subpackage or submodule of that package, "None". This effectively # silently ignores this dangerous attempt to recursively type-check # the "beartype" package by the @beartype.beartype decorator. See # the method docstring for further commentary. None if ( fullname == 'beartype' or fullname.startswith('beartype.') ) else # Else, that module is neither our top-level "beartype" package # *NOR* a subpackage or submodule of that package. In this case, the # beartype configuration with which to type-check that module if # that module is hooked under its fully-qualified name *OR* # "None" otherwise (i.e., if that module is unhooked). get_package_conf_or_none(fullname) ) # print(f'Imported module "{fullname}" package "{package_name}" conf: {repr(self._module_conf_beartype)}') # If that module is unhooked, preserve that module as is by simply # deferring to the superclass method *WITHOUT* monkey-patching # cache_from_source(). This isn't only an optimization, though it is # that as well. This is critical. Why? Because modules *NOT* being # beartyped should remain compiled under their standard non-beartyped # bytecode filenames. if conf is None: # print(f'Importing module "{fullname}" without beartyping...') return super().get_code(fullname) # Else, that module has been hooked. In this case... # # Note that the logic below requires inefficient exception handling (as # well as a potentially risky monkey-patch) and is thus performed *ONLY* # when absolutely necessary. # Classify local attributes as instance variables for subsequent # reference in the lower-level source_to_code() method transitively # called by this higher-level method. self._module_conf_beartype = conf self._module_name_beartype = fullname # Expose this configuration to the "beartype.claw._ast" subpackage. claw_state.module_name_to_beartype_conf[fullname] = conf # Temporarily monkey-patch away the cache_from_source() function. # # Note that @agronholm (Alex Grönholm) claims that "the import lock # should make this monkey patch safe." We're trusting you here, man! _bootstrap_external.cache_from_source = cache_from_source_beartype # Attempt to defer to the superclass method. try: return super().get_code(fullname) # After doing so (and regardless of whether doing so raises an # exception), restore the original cache_from_source() function. finally: _bootstrap_external.cache_from_source = ( cache_from_source_original) # Note that we explicitly ignore mypy override complaints here. For unknown # reasons, mypy believes that "importlib.machinery.SourceFileLoader" # subclasses comply with the "importlib.abc.InspectLoader" abstract base # class (ABC). Naturally, that is *NOT* the case. Ergo, we entirely ignore # mypy complaints here with respect to signature matching. def source_to_code( # type: ignore[override] self, # Mandatory parameters. data: bytes, path: str, # Optional keyword-only parameters. *, _optimize: int =-1, ) -> CodeType: ''' Code object dynamically compiled from the **sourceful Python package or module** (i.e., package or module backed by a ``.py``-suffixed source file) with the passed undecoded contents and filename, efficiently transformed in-place by our abstract syntax tree (AST) transformation automatically applying the :func:`beartype.beartype` decorator to all applicable objects of that package or module. The higher-level :meth:`get_code` superclass method internally calls this lower-level subclass method. Parameters ---------- data : bytes **Byte array** (i.e., undecoded list of bytes) of the Python package or module to be decoded and dynamically compiled into a code object. path : str Absolute or relative filename of that Python package or module. _optimize : int, optional **Optimization level** (i.e., numeric integer signifying increasing levels of optimization under which to compile that Python package or module). Defaults to -1, implying the current interpreter-wide optimization level with which the active Python process was initially invoked (e.g., via the ``-o`` command-line option). Returns ------- CodeType Code object dynamically compiled from that Python package or module. Raises ------ BeartypeClawImportAstException If our **beartype node transformer** (i.e., :class:`.BeartypeNodeTransformer` instance) dynamically transforms the original valid abstract syntax tree (AST) governing that Python package or module into a new invalid AST. ''' # If that module has *NOT* been registered for type-checking, preserve # that module as is by simply deferring to the superclass method. if self._module_conf_beartype is None: return super().source_to_code( # type: ignore[call-arg] data=data, path=path, _optimize=_optimize) # pyright: ignore # Else, that module has been registered for type-checking. # Plaintext decoded contents of that module. module_source = decode_source(data) # Abstract syntax tree (AST) parsed from these contents. module_ast = compile( module_source, path, 'exec', PyCF_ONLY_AST, # Prevent these contents from inheriting the effects of any # "from __future__ import" statements in effect in beartype itself. dont_inherit=True, optimize=_optimize, ) # AST transformer decorating typed callables and classes by @beartype. ast_beartyper = BeartypeNodeTransformer( module_name_beartype=self._module_name_beartype, conf_beartype=self._module_conf_beartype, ) # Abstract syntax tree (AST) modified by this transformer. module_ast_beartyped = ast_beartyper.visit(module_ast) #FIXME: Conditionally perform this logic if "conf.is_debug", please. #Note that printing to "stderr" is pivotal. For some reason, Python #fails to forward printing to "stdout" across subprocesses even when we #explicitly tell it to. Look. I don't even know. Just roll with it! # from sys import stderr # print( # ( # f'Module "{self._module_name_beartype}" abstract syntax tree (AST) ' # f'transformed by @beartype to:\n\n' # f'{get_node_repr_indented(module_ast_beartyped)}' # ), # file=stderr, # ) # Attempt to... try: # Code object compiled from this transformed AST. module_codeobj = compile( module_ast_beartyped, path, 'exec', # Prevent these contents from inheriting the effects of any # "from __future__ import" statements in effect in the beartype # codebase itself. dont_inherit=True, optimize=_optimize, ) # If doing so raises *ANY* exception whatsoever, wrap that low-level # exception with a higher-level exception exhibiting the exact issue. # Doing so enables users to submit meaningful issues to our tracker. except Exception as exception: raise BeartypeClawImportAstException( f'Module "{self._module_name_beartype}" unimportable, as ' f'@beartype generated invalid ' f'abstract syntax tree (AST):\n\n' f'{get_node_repr_indented(module_ast_beartyped)}\n\n' f'ast.compile() exception (when passed the above AST):\n\t' f'{label_exception(exception)}' ) from exception # Return this code object. return module_codeobj beartype-0.18.5/beartype/claw/_importlib/clawimpcache.py000066400000000000000000000276261461113517100233300ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **import hook module caches** (i.e., private dictionary singletons enabling relevant metadata including beartype configurations associated with submodules of all packages previously registered in our global package trie to be efficiently stored and retrieved based on various criteria). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.claw._clawmagic import BEARTYPE_OPTIMIZATION_MARKER from beartype.roar import BeartypeClawImportConfException from beartype.typing import Dict from beartype._conf.confcls import BeartypeConf from pprint import pformat # Original cache_from_source() function defined by the private (*gulp*) # "importlib._bootstrap_external" submodule, preserved *BEFORE* temporarily # replacing that function with our beartype-specific variant in the # "beartype.claw._importlib._clawimpload" submodule. from importlib.util import cache_from_source as cache_from_source_original # ....................{ CLASSES }.................... class ModuleNameToBeartypeConf(Dict[str, 'BeartypeConf']): ''' Non-thread-safe **hooked module beartype configuration cache** (i.e., dictionary mapping from the fully-qualified name of each previously imported submodule of each package previously registered in our global package trie to the beartype configuration configuring type-checking by the :func:`beartype.beartype` decorator of that submodule). This dictionary subclass improves the human readability of exceptions raised by dunder methods of the :class:`dict` superclass (e.g., the :meth:`dict.__getitem__` dunder method), whose C-based implementations raise non-human-readable exceptions in common use cases encountered by end users leveraging beartype import hooks: e.g., .. code-block:: python # Otherwise syntactically and semantically correct PEP 526-compliant # annotated assignment expressions like this previously raised spurious # non-human-readable exceptions from this dictionary resembling: # KeyError: 'muh_module' # <-- what does this even mean!?!? loves_philosophy: float = len('The fountains mingle with the river') Motivation ---------- This cache provides an efficient ``O(1)`` alternative to the comparatively less efficient :func:`beartype.claw._pkg.clawpkgtrie.get_package_conf_or_none` function, which exhibits worst-case runtime complexity of ``O(k)`` for ``k`` the maximum depth of our global package trie. Doing so enables the :mod:`beartype.claw._ast.clawastmain` submodule implementing our abstract syntax tree (AST) node transformer to trivially inject efficient code looking up the current beartype configuration associated with the currently transformed module into the body of that module, which would otherwise be quite non-trivial. Caveats ---------- **This cache is non-thread-safe.** The caller is responsible for guaranteeing thread-safety on writes to this cache. However, Note that reads of this cache are implicitly thread-safe. The :meth:`BeartypeConf.__new__` instantiator thread-safely stores strong references to the currently instantiated beartype configuration in both this and other caches. Since these caches and thus *all* configurations persist for the lifetime of the active Python interpreter, reads are effectively thread-safe. ''' # ....................{ DUNDERS }.................... def __getitem__(self, module_name: str) -> 'BeartypeConf': ''' Return the previously instantiated beartype configuration associated with the module with the passed name. Parameters ---------- module_name : str Fully-qualified name of the module associated with the beartype configuration to be returned. Returns ---------- beartype.BeartypeConf Beartype configuration associated with this module. Raises ---------- BeartypeClawImportConfException If no beartype configuration with this module has been previously instantiated. ''' # Attempt to defer to the superclass implementation. try: return super().__getitem__(module_name) # If doing so fails with a low-level non-human-readable exception... except KeyError as exception: # pragma: no cover #FIXME: Also, consider dropping the parallel #"BeartypeSourceFileLoader._main_module_name_beartype" attribute. #Does this nonsense supercede that nonsense? Probably. Which leads #us directly to... # If the module to be inspected is the "__main__" pseudo-module # signifying the main entry-point into the active Python process... if module_name == '__main__': # from sys import argv # print(f'Python arguments: "{repr(argv)}"') # print(f'Main module spec: "{__main__.__spec__}"') # Import this pseudo-module. # # Note that: # * Note that Python guarantees this pseudo-module to *ALWAYS* # be safely importable, regardless of whether a main module # actually was imported as an entry-point or not. # * This import *MUST* be delayed as long as feasible. In fact, # this need to delay this import as long as feasible is why # this import is performed here; this block is actually the # last possible code path that this import can be delayed to. # # Ideally, this import would be performed earlier (e.g., in the # BeartypeSourceFileLoader.__init__() method defined in the # "beartype.claw._importlib._clawimpload" submodule) for # debuggability, efficiency, and idempotency; for this reason, a # prior implementation of the aforementioned method performed # this import. # # Pragmatically, doing so: # * Succeeded in some common edge cases, including execution of # a third-party "muh_package" package invoked at the command # line as "python -m muh_package", containing: # * A "muh_package.__init__" submodule calling our # beartype.claw.beartype_this_package() import hook. # * A "muh_package.__main__" submodule. # * Failed in other common edge cases, including execution of a # third-party "muh_package.muh_submodule" submodule invoked at # the command line as "python -m muh_package.muh_submodule", # containing: # * A "muh_package.__init__" submodule calling our # beartype.claw.beartype_this_package() import hook. # * *NO* "muh_package.__main__" submodule. # # Why the discrepancy? Because CPython itself (specifically, # CPython's "runpy" architecture responsible for bootstrapping # CPython at process startup) is buggy. Due to non-trivial # "runpy" implementation details that are ultimately irrelevant # to @beartype, CPython inconsistently alters the values of # various critical system globals necessarily introspected by # the "beartype.claw" API, including erroneously reporting that: # * In the aforementioned "muh_package.__init__" submodule: # * The "sys.argv" global is just "[-m]", thus truncating the # trailing module name. # * The "__main__" pseudo-module is empty and thus effectively # unimportable for all intents and purposes. # * In the aforementioned "muh_package.__main__" and # "muh_package.muh_submodule" submodules: # * The "sys.argv" global is just "['muh_package']" and # "['muh_package.muh_submodule']" (respectively), thus # truncating the leading argument "-m". # * The "__main__" pseudo-module is non-empty and thus # importable for all intents and purposes. # # CPython blatantly lies about both the "sys.argv" global *AND* # "__main__" pseudo-module in top-level "muh_package.__init__" # submodules when CPython is passed the "-m" command-line # option. Ergo, beartype has *NO* means of introspecting either # object from any call in the call stack called by a top-level # "muh_package.__init__" submodule -- including any call to any # "beartype.claw" import hook. Instead, beartype *MUST* defer # that introspection to the last possible time... here. # # See also this relevant StackOverflow question on the topic, # which is nearly a decade-old as of this commit (2023 Q3) but # remains unresolved in even the live git version of CPython: # https://stackoverflow.com/questions/42076706/sys-argv-behavior-with-python-m import __main__ # Return the fully-qualified name of the actual user-defined # module encapsulated by the "__main__" pseudo-module. # # Note that the value of the "__main__.__name__" dunder # attribute is *ALWAYS* "__main__", yet another blatant lie that # only obfuscates the truth. Thankfully, the low-level # "importlib"-specific spec object publishes the fully-qualified # name of this actual user-defined module. I sleep now. *zzzzzz* return super().__getitem__(__main__.__spec__.name) # Raise a high-level human-readable exception instead. raise BeartypeClawImportConfException( f'Beartype configuration associated with ' f'module "{module_name}" hooked by ' f'"beartype.claw" not found. ' f'Existing beartype configurations associated with ' f'hooked modules include:\n\t{pformat(self)}' ) from exception # ....................{ CACHERS }.................... #FIXME: Unit test us up, please. def cache_from_source_beartype(*args, **kwargs) -> str: ''' Beartype-specific variant of the :func:`importlib._bootstrap_external.cache_from_source` function applying a beartype-specific optimization marker to that function. This, in turn, ensures that submodules residing in packages registered by a prior call to the :func:`beartype_package` function are compiled to files with the filetype ``".pyc{optimization}_{BEARTYPE_OPTIMIZATION_MARKER}"``, where ``{optimization}`` is the original ``optimization`` parameter passed to this function call. ''' # Original optimization parameter passed to this function call if any *OR* # the empty string otherwise. NONBEARTYPE_OPTIMIZATION_MARKER = kwargs.get('optimization', '') # New optimization parameter applied by this monkey-patch of that function, # uniquifying that parameter with a beartype-specific suffix. kwargs['optimization'] = ( f'{NONBEARTYPE_OPTIMIZATION_MARKER}{BEARTYPE_OPTIMIZATION_MARKER}') # Defer to the implementation of the original cache_from_source() function. return cache_from_source_original(*args, **kwargs) beartype-0.18.5/beartype/claw/_importlib/clawimppath.py000066400000000000000000000220561461113517100232110ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype all-at-once low-level package name cache.** This private submodule caches package names on behalf of the higher-level :func:`beartype.claw.beartype_package` function. Beartype import path hooks internally created by that function subsequently lookup these package names from this cache when deciding whether or not (and how) to decorate a submodule being imported with :func:`beartype.beartype`. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.claw._importlib._clawimpload import BeartypeSourceFileLoader from importlib import invalidate_caches from importlib.machinery import ( BYTECODE_SUFFIXES, SOURCE_SUFFIXES, FileFinder, ExtensionFileLoader, SourcelessFileLoader, ) from sys import ( path_hooks, path_importer_cache, ) # Intentionally violate privacy encapsulate in the standard Python library, # because there is *NO* valid alternative. This low-level private getter # function returns a tuple of the filetypes of *ALL* C extensions supported by # the current platform (e.g., as shared libraries). from _imp import extension_suffixes # ....................{ ADDERS }.................... #FIXME: Unit test us up, please. def add_beartype_pathhook() -> None: ''' Add our **beartype import path hook singleton** (i.e., single callable guaranteed to be inserted at most once to the front of the standard :mod:`sys.path_hooks` list recursively applying the :func:`beartype.beartype` decorator to all well-typed callables and classes defined by all submodules of all packages previously registered by a call to a public :func:`beartype.claw` function) if this path hook has yet to be added *or* silently reduce to a noop otherwise (i.e., if this path hook has already been added). Caveats ---------- **This function is non-thread-safe.** For both simplicity and efficiency, the caller is expected to provide thread-safety through a higher-level locking primitive managed by the caller. See Also ---------- https://stackoverflow.com/a/43573798/2809027 StackOverflow answer strongly inspiring the low-level implementation of this function with respect to inscrutable :mod:`importlib` machinery. ''' # Avoid circular import dependencies. from beartype.claw._clawstate import claw_state # If this function has already been called under the active Python # interpreter, silently reduce to a noop. if claw_state.beartype_pathhook is not None: return # Else, this function has *NOT* yet been called under this interpreter. # Closure instantiating a new "FileFinder" instance invoking this loader. # # Note that we intentionally ignore mypy complaints here. Why? Because mypy # erroneously believes this method accepts 2-tuples whose first items are # loader *INSTANCES* (e.g., "Tuple[Loader, List[str]]"). In fact, this # method accepts 2-tuples whose first items are loader *TYPES* (e.g., # "Tuple[Type[Loader], List[str]]"). This is why we can't have nice things. loader_factory = FileFinder.path_hook(*_LOADERS_DETAILS) # type: ignore[arg-type] # Prepend a new path hook (i.e., factory closure encapsulating this loader) # *BEFORE* all other path hooks. path_hooks.insert(0, loader_factory) # path_hooks.append(loader_factory) #FIXME: Uncomment as needed to debug the contents of the "path_hooks" list. # print(f'path_hooks: {path_hooks}') # for path_hook in path_hooks: # try: # file_finder = path_hook('/usr/lib/python3.11') # print(f'file_finder: {file_finder} [{file_finder._loaders}]') # except: # pass # Prevent subsequent calls to this function from erroneously re-adding # duplicate copies of this path hook immediately *AFTER* successfully adding # the first such path hook. # # Note that we intentionally avoid globalizing this path hook until *AFTER* # successfully having done so. Why? Negligible safety. The companion # remove_beartype_pathhook() function raises a non-human-readable exception # if this global is non-"None" but *NOT* in the "path_hooks" list. claw_state.beartype_pathhook = loader_factory # Lastly, clear *ALL* import path hook caches for safety. _clear_importlib_caches() # ....................{ REMOVERS }.................... #FIXME: Unit test us up, please. def remove_beartype_pathhook() -> None: ''' Remove our **beartype import path hook singleton** (i.e., single callable guaranteed to be inserted at most once to the front of the standard :mod:`sys.path_hooks` list recursively applying the :func:`beartype.beartype` decorator to all well-typed callables and classes defined by all submodules of all packages previously registered by a call to a public :func:`beartype.claw` function) if this path hook has already been added *or* silently reduce to a noop otherwise (i.e., if this path hook has yet to be added). Caveats ---------- **This function is non-thread-safe.** For both simplicity and efficiency, the caller is expected to provide thread-safety through a higher-level locking primitive managed by the caller. ''' # Avoid circular import dependencies. from beartype.claw._clawstate import claw_state # If the add_beartype_pathhook() function has *NOT* yet been called under # the active Python interpreter, silently reduce to a noop. if claw_state.beartype_pathhook is None: return # Else, that function has already been called under this interpreter. # Remove the prior path hook added by that function *OR* raise a # non-human-readable "ValueError" exception if this global is non-"None" but # *NOT* in the "path_hooks" list (which should *NEVER* happen, but it will). path_hooks.remove(claw_state.beartype_pathhook) # Allow subsequent calls to the add_beartype_pathhook() to re-add a new # instance of this path hook immediately *AFTER* successfully removing the # first such path hook. claw_state.beartype_pathhook = None # Lastly, clear *ALL* import path hook caches for safety. _clear_importlib_caches() # ....................{ PRIVATE ~ globals }.................... _LOADERS_DETAILS = ( # Beartype-agnostic C extension loader details. Since C extensions *CANNOT* # (by definition) be decompiled into an abstract syntax tree (AST), beartype # has *NO* means of decorating C extensions. Ergo, we necessarily defer to # Python's default C extension loader. (ExtensionFileLoader, extension_suffixes()), # Beartype-specific **source module loader** (i.e., file loader loading # uncompiled pure-Python modules of the filetype ".py"). (BeartypeSourceFileLoader, SOURCE_SUFFIXES), #FIXME: Generalize this into a beartype-specific bytecode module loader. #How? By leveraging the third-party "astor" package, which provides a #code_to_ast() function decompiling arbitrary code objects into ASTs. See #also this relevant StackOverflow answer by myself: # https://stackoverflow.com/a/76641537/2809027 # Beartype-agnostic **bytecode module loader** (i.e., file loader loading # precompiled pure-Python modules from bytecode files compiled in # "__pycache__/" subdirectories that lack corresponding uncompiled # pure-Python modules of the filetype ".py"). (SourcelessFileLoader, BYTECODE_SUFFIXES), ) ''' Tuple of all **file-based module loader details** (i.e., 2-tuple ``(file_loader, filetypes)`` of the undocumented format expected by the :meth:`FileFinder.path_hook` class method called by the :func:`beartype.claw._importlib.clawimppath.add_beartype_pathhook` function, associating each file-based module loader with the platform-specific filetypes of all modules loaded by that module). We didn't do it. Don't blame the bear. See Also ---------- :func:`importlib.machinery._get_supported_file_loaders` Low-level private getter function strongly inspiring the definition of this global, which implements nearly identical functionality (albeit in a :mod:`beartype`-specific manner). ''' # ....................{ PRIVATE ~ cachers }.................... #FIXME: Unit test us up, please. def _clear_importlib_caches() -> None: ''' Clear *all* :mod:`sys`- and :mod:`importlib`-specific caches pertaining to **import path hooks** (i.e., the standard :mod:`sys.path_hooks` list). This function is typically called immediately *after* our beartype import path hook singleton is either added to or removed from the path hooks list. ''' # Uncache *ALL* competing loaders cached by prior importations. Just do it! path_importer_cache.clear() # Clear *ALL* "importlib" caches as well for safety. invalidate_caches() beartype-0.18.5/beartype/claw/_pkg/000077500000000000000000000000001461113517100171015ustar00rootroot00000000000000beartype-0.18.5/beartype/claw/_pkg/__init__.py000066400000000000000000000000001461113517100212000ustar00rootroot00000000000000beartype-0.18.5/beartype/claw/_pkg/_clawpkgmake.py000066400000000000000000000216211461113517100221020ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **import hook factories** (i.e., low-level utility functions creating and returning objects of interest to higher-level import hook functions). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.claw._pkg.clawpkgenum import BeartypeClawCoverage from beartype.roar import ( BeartypeClawDecorWarning, BeartypeClawHookException, ) from beartype.typing import ( Iterable, Optional, ) from beartype._conf.confcls import BeartypeConf from beartype._util.text.utiltextidentifier import die_unless_identifier from collections.abc import Iterable as IterableABC # ....................{ PRIVATE ~ factories }.................... #FIXME: Unit test us up, please. def make_conf_hookable(conf: BeartypeConf) -> BeartypeConf: ''' New **hookable beartype configuration** (i.e., beartype configuration suitable for use in import hooks, sanitized from the passed beartype configuration which is typically unsuitable for use in import hooks). This getter creates and returns a new configuration permuted from the passed configuration, forcefully enabling these parameters required by import hooks: * :attr:`beartype.BeartypeConf.warning_cls_on_decorator_exception` to the :class:`beartype.roar.BeartypeClawDecorWarning` warning category. Doing so instructs the :func:`beartype.beartype` decorator to emit non-fatal warnings rather than raise fatal exceptions at decoration time when implicitly decorating callables and classes defined by modules hooked by our import hooks, substantially improving the robustness and usability of those hooks. Returns ---------- Optional[Iterable[str]] Iterable of the fully-qualified names of one or more packages to be either hooked or unhooked by the parent call. Raises ---------- BeartypeClawHookException If the passed ``conf`` parameter is *not* a beartype configuration (i.e., :class:`BeartypeConf` instance). See Also ---------- :func:`.hook_packages` Further details. ''' # If the "conf" parameter is *NOT* a configuration, raise an exception. if not isinstance(conf, BeartypeConf): raise BeartypeClawHookException( f'Beartype configuration {repr(conf)} invalid (i.e., not ' f'"beartype.BeartypeConf" instance).' ) # Else, the "conf" parameter is a configuration. # If the caller did *NOT* explicitly set the # "warning_cls_on_decorator_exception" configuration parameter governing the # reduction of fatal exceptions to non-fatal warnings at @beartype # decoration-time... if not conf._is_warning_cls_on_decorator_exception_set: # Keyword dictionary with which to instantiate a new configuration # reducing fatal exceptions to non-fatal warnings of a warning category # specific to beartype import hooks. conf_kwargs = conf.kwargs.copy() conf_kwargs['warning_cls_on_decorator_exception'] = ( BeartypeClawDecorWarning) # Replace this configuration with this new configuration. conf = BeartypeConf(**conf_kwargs) # type: ignore[arg-type] # Else, this caller already explicitly set the # "warning_cls_on_decorator_exception" configuration parameter governing the # reduction of fatal exceptions to non-fatal warnings at @beartype # decoration-time. In this case, preserve this user-defined reduction as is. # Return this possibly new configuration. return conf #FIXME: Unit test us up, please. def make_package_names_from_args( # Keyword-only arguments. *, # Mandatory keyword-only arguments. claw_coverage: BeartypeClawCoverage, conf: BeartypeConf, # Optional keyword-only arguments. package_name: Optional[str] = None, package_names: Optional[Iterable[str]] = None, ) -> Optional[Iterable[str]]: ''' Validate all parameters passed by the caller to the parent :func:`.hook_packages` or :func:`.unhook_packages` function. Returns ---------- Optional[Iterable[str]] Iterable of the fully-qualified names of one or more packages to be either hooked or unhooked by the parent call. Raises ---------- BeartypeClawHookException If the passed ``package_names`` parameter is either: * Neither a string nor an iterable (i.e., fails to satisfy the :class:`collections.abc.Iterable` protocol). * An empty string or iterable. * A non-empty string that is *not* a valid **package name** (i.e., ``"."``-delimited concatenation of valid Python identifiers). * A non-empty iterable containing at least one item that is either: * *Not* a string. * The empty string. * A non-empty string that is *not* a valid **package name** (i.e., ``"."``-delimited concatenation of valid Python identifiers). See Also ---------- :func:`.hook_packages` Further details. ''' assert isinstance(conf, BeartypeConf), f'{repr(conf)} not configuration.' assert isinstance(claw_coverage, BeartypeClawCoverage), ( f'{repr(claw_coverage)} not beartype claw coverage.') # If the caller requested all-packages coverage... if claw_coverage is BeartypeClawCoverage.PACKAGES_ALL: # If the caller improperly passed a package name despite requesting # all-packages coverage, raise an exception. if package_name is not None: raise BeartypeClawHookException( f'Coverage {repr(BeartypeClawCoverage.PACKAGES_ALL)} ' f'but package name {repr(package_name)} passed.' ) # Else, the caller properly passed *NO* package name. # # If the caller improperly passed multiple package names despite # requesting all-packages coverage, raise an exception. elif package_names is not None: raise BeartypeClawHookException( f'Coverage {repr(BeartypeClawCoverage.PACKAGES_ALL)} ' f'but package names {repr(package_names)} passed.' ) # Else, the caller properly passed *NO* package names. # Else, the caller did *NOT* request all-packages coverage. In this case, # the caller requested coverage over only a subset of packages. else: # If the caller requested mono-package coverage... if claw_coverage is BeartypeClawCoverage.PACKAGES_ONE: # If the caller improperly passed *NO* package name despite # requesting mono-package coverage, raise an exception. if package_name is None: raise BeartypeClawHookException( f'beartype_package() ' f'package name {repr(package_name)} invalid.' ) # Else, the caller properly passed a package name. # Wrap this package name in a 1-tuple containing only this name. # Doing so unifies logic below. package_names = (package_name,) # Else, the caller requested multi-package coverage. # elif coverage is BeartypeClawCoverage.PACKAGES_MANY: # If this package names is *NOT* iterable, raise an exception. if not isinstance(package_names, IterableABC): raise BeartypeClawHookException( f'beartype_packages() ' f'package names {repr(package_name)} not iterable.' ) # Else, this package names is iterable. # # If *NO* package names were passed, raise an exception. elif not package_names: raise BeartypeClawHookException( 'beartype_packages() package names empty.') # Else, one or more package names were passed. # For each such package name... for package_name in package_names: # If this package name is *NOT* a string, raise an exception. if not isinstance(package_name, str): raise BeartypeClawHookException( f'Package name {repr(package_name)} not string.') # Else, this package name is a string. # # If this package name is *NOT* a valid Python identifier, raise an # exception. else: die_unless_identifier( text=package_name, exception_cls=BeartypeClawHookException, exception_prefix='Package name ', ) # Else, this package name is a valid Python identifier. # Return the iterable of the fully-qualified names of one or more packages # to be either hooked or unhooked by the parent call. return package_names beartype-0.18.5/beartype/claw/_pkg/clawpkgcontext.py000066400000000000000000000150351461113517100225140ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **import path hook context managers** (i.e., data structure caching package names on behalf of the higher-level :func:`beartype.claw._clawmain` submodule, which beartype import path hooks internally created by that submodule subsequently lookup when deciding whether or not (and how) to decorate by :func:`beartype.beartype` the currently imported user-specific submodule). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.claw._clawstate import ( claw_lock, claw_state, ) from beartype.claw._pkg.clawpkgtrie import ( die_if_packages_trie, remove_beartype_pathhook_unless_packages_trie, ) from beartype.typing import ( Iterator, Optional, ) from beartype._conf.confcls import ( BEARTYPE_CONF_DEFAULT, BeartypeConf, ) from contextlib import contextmanager # ....................{ CONTEXTS }.................... #FIXME: Unit test us up, please. @contextmanager def beartyping( # Optional keyword-only parameters. *, conf: BeartypeConf = BEARTYPE_CONF_DEFAULT, ) -> Iterator[None]: ''' Context manager temporarily registering a new **universal beartype import path hook** (i.e., callable inserted to the front of the standard :mod:`sys.path_hooks` list recursively decorating *all* typed callables and classes of *all* submodules of *all* packages on the first importation of those submodules with the :func:`beartype.beartype` decorator, wrapping those callables and classes with performant runtime type-checking). Specifically, this context manager (in order): #. Temporarily registers this hook by calling the public :func:`beartype.claw.beartype_all` function. #. Runs the body of the caller-defined ``with beartyping(...):`` block. #. Unregisters the hook registered by the prior call to that function. This context manager is thread-safe. Parameters ---------- conf : BeartypeConf, optional **Beartype configuration** (i.e., dataclass configuring the :mod:`beartype.beartype` decorator for *all* decoratable objects recursively decorated by the path hook added by this function). Defaults to ``BeartypeConf()``, the default :math:`O(1)` configuration. Yields ------ None This context manager yields *no* objects. Raises ------ BeartypeClawHookException If the passed ``conf`` parameter is *not* a beartype configuration (i.e., :class:`.BeartypeConf` instance). See Also -------- :func:`beartype.claw.beartype_all` Arguably unsafer alternative to this function globalizing the effect of this function to *all* imports performed anywhere. ''' # Avoid circular import dependencies. from beartype.claw import beartype_all # Prior global beartype configuration registered by a prior call to the # beartype_all() function if any *OR* "None" otherwise. packages_trie_conf_if_hooked_old: Optional[BeartypeConf] = None # Attempt to... try: # With a "beartype.claw"-specific thread-safe reentrant lock... with claw_lock: # Store the prior global beartype configuration if any. packages_trie_conf_if_hooked_old = ( claw_state.packages_trie.conf_if_hooked) # Prevent the beartype_all() function from raising an exception on # conflicting registrations of beartype configurations. claw_state.packages_trie.conf_if_hooked = None # Globalize the passed beartype configuration. beartype_all(conf=conf) # Defer to the caller body of the parent "with beartyping(...):" block. yield # After doing so (regardless of whether doing so raised an exception)... finally: # With a "beartype.claw"-specific thread-safe reentrant lock... with claw_lock: # If the current global beartype configuration is still the passed # beartype configuration, then the caller's body of the parent "with # beartyping(...):" block has *NOT* itself called the beartype_all() # function with a conflicting beartype configuration. In this # case... if claw_state.packages_trie.conf_if_hooked == conf: # Restore the prior global beartype configuration if any. claw_state.packages_trie.conf_if_hooked = ( packages_trie_conf_if_hooked_old) # Possibly remove our beartype import path hook added by the # above call to beartype_all() if *NO* packages are registered. remove_beartype_pathhook_unless_packages_trie() # Else, the caller's body of the parent "with beartyping(...):" # block has itself called the beartype_all() function with a # conflicting beartype configuration. In this case, preserve that # configuration as is. #FIXME: Unit test us up, please. @contextmanager def packages_trie_cleared() -> Iterator[None]: ''' Test-specific context manager reverting (i.e., clearing, resetting) the :data:`beartype.claw._pkg.clawpkgtrie.packages_trie` global back to its initial state *after* running the body of the caller-defined ``with beartyping(...):`` block. This context manager is thread-safe. Caveats ------- **This context manager is intentionally hidden from users as a private attribute of this submodule** rather than publicly exported. Why? Because this context manager is *only* intended to be invoked by unit and integration tests in our test suite. Yields ------ None This context manager yields *no* objects. ''' # If one or more packages are still registered by a prior call to a beartype # import hook, raise an exception. die_if_packages_trie() # Else, *NO* packages are still registered. # Perform the caller-defined body of the parent "with" statement. try: yield # After doing so, regardless of whether doing so raised an exception... finally: # print(f'claw_state [after test]: {repr(claw_state)}') # With a submodule-specific thread-safe reentrant lock, reset our import # hook state back to its initial defaults. with claw_lock: claw_state.reinit() beartype-0.18.5/beartype/claw/_pkg/clawpkgenum.py000066400000000000000000000042451461113517100217750ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype import hook enumerations** (i.e., :class:`enum.Enum` subclasses enumerating various kinds of divergent strategies and processes specific to import hooks defined by the :mod:`beartype.claw` subpackage). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... # from beartype.typing import Literal from enum import ( Enum, auto as next_enum_member_value, unique as die_unless_enum_member_values_unique, ) # ....................{ ENUMS }.................... @die_unless_enum_member_values_unique class BeartypeClawCoverage(Enum): ''' Enumeration of all kinds of **import hook coverage** (i.e., competing package scopes over which to apply import hooks defined by the :mod:`beartype.claw` subpackage, each with concomitant tradeoffs with respect to runtime complexity and quality assurance). Attributes ---------- PACKAGES_ALL : EnumMemberType **All-packages coverage** (i.e, hooking imports into *all* packages, including both third-party packages *and* standard packages bundled with Python in the standard library). This coverage is typically applied by a caller calling the :func:`beartype.claw.beartype_all` import hook. PACKAGES_MANY : EnumMemberType **Many-packages coverage** (i.e, hooking imports into two or more explicitly specified packages). This coverage is typically applied by a caller calling the :func:`beartype.claw.beartype_packages` import hook. PACKAGES_ONE : EnumMemberType **One-package coverage** (i.e, hooking imports into only one explicitly specified package). This coverage is typically applied by a caller calling the :func:`beartype.claw.beartype_package` import hook. ''' PACKAGES_ALL = next_enum_member_value() PACKAGES_MANY = next_enum_member_value() PACKAGES_ONE = next_enum_member_value() beartype-0.18.5/beartype/claw/_pkg/clawpkghook.py000066400000000000000000000433671461113517100220010ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **import hook managers** (i.e., lower-level private-facing functions internally driving the higher-level public facing import hooks exported by the :mod:`beartype.claw._clawmain` submodule). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.claw._pkg.clawpkgenum import BeartypeClawCoverage from beartype.claw._pkg.clawpkgtrie import ( PackagesTrie, iter_packages_trie, remove_beartype_pathhook_unless_packages_trie, ) from beartype.claw._pkg._clawpkgmake import ( make_conf_hookable, make_package_names_from_args, ) from beartype.claw._importlib.clawimppath import ( add_beartype_pathhook, # remove_beartype_pathhook, ) from beartype.roar import ( BeartypeClawHookException, ) from beartype.typing import ( Iterable, Optional, ) from beartype._conf.confcls import BeartypeConf # ....................{ (UN)HOOKERS }.................... #FIXME: Unit test us up, please. def hook_packages( # Keyword-only arguments. *, # Mandatory keyword-only arguments. claw_coverage: BeartypeClawCoverage, conf: BeartypeConf, # Optional keyword-only arguments. package_name: Optional[str] = None, package_names: Optional[Iterable[str]] = None, ) -> None: ''' Register a new **beartype package import path hook** (i.e., callable inserted to the front of the standard :mod:`sys.path_hooks` list recursively applying the :func:`beartype.beartype` decorator to all typed callables and classes of all submodules of all packages with the passed names on the first importation of those submodules). Parameters ---------- claw_coverage : BeartypeClawCoverage **Import hook coverage** (i.e., competing package scope over which to apply the path hook added by this function, each with concomitant tradeoffs with respect to runtime complexity and quality assurance). conf : BeartypeConf, optional **Beartype configuration** (i.e., dataclass configuring the :mod:`beartype.beartype` decorator for *all* decoratable objects recursively decorated by the path hook added by this function). package_name : Optional[str] Either: * If ``coverage`` is :attr:`BeartypeClawCoverage.PACKAGES_ONE`, the fully-qualified name of the package to be type-checked. * Else, ignored. Defaults to :data:`None`. package_names : Optional[Iterable[str]]] Either: * If ``coverage`` is :attr:`BeartypeClawCoverage.PACKAGES_MANY`, an iterable of the fully-qualified names of one or more packages to be type-checked. * Else, ignored. Defaults to :data:`None`. Raises ------ BeartypeClawHookException If either: * The passed ``package_names`` parameter is either: * Neither a string nor an iterable (i.e., fails to satisfy the :class:`collections.abc.Iterable` protocol). * An empty string or iterable. * A non-empty string that is *not* a valid **package name** (i.e., ``"."``-delimited concatenation of valid Python identifiers). * A non-empty iterable containing at least one item that is either: * *Not* a string. * The empty string. * A non-empty string that is *not* a valid **package name** (i.e., ``"."``-delimited concatenation of valid Python identifiers). * The passed ``conf`` parameter is *not* a beartype configuration (i.e., :class:`BeartypeConf` instance). See Also -------- https://stackoverflow.com/a/43573798/2809027 StackOverflow answer strongly inspiring the low-level implementation of this function with respect to inscrutable :mod:`importlib` machinery. ''' # Avoid circular import dependencies. from beartype.claw._clawstate import ( claw_lock, claw_state, ) # Replace this beartype configuration (which is typically unsuitable for # usage in import hooks) with a new beartype configuration suitable for # usage in import hooks. conf = make_conf_hookable(conf) # Iterable of the passed fully-qualified names of all packages to be hooked. package_names = make_package_names_from_args( claw_coverage=claw_coverage, conf=conf, package_name=package_name, package_names=package_names, ) # With a submodule-specific thread-safe reentrant lock... with claw_lock: # If the caller requested all-packages coverage... if claw_coverage is BeartypeClawCoverage.PACKAGES_ALL: # Beartype configuration currently associated with *ALL* packages by # a prior call to this function if any *OR* "None" (i.e., if this # function has yet to be called under this Python interpreter). conf_curr = claw_state.packages_trie.conf_if_hooked # If the higher-level beartype_all() function (calling this # lower-level adder) has yet to be called under this interpreter, # associate this configuration with *ALL* packages. if conf_curr is None: claw_state.packages_trie.conf_if_hooked = conf # Else, beartype_all() was already called under this interpreter. # # If the caller passed a different configuration to that prior call # than that passed to this current call, raise an exception. elif conf_curr != conf: raise BeartypeClawHookException( f'beartype_all() previously passed ' f'conflicting beartype configuration:\n' f'\t----------( OLD "conf" PARAMETER )----------\n' f'\t{repr(conf_curr)}\n' f'\t----------( NEW "conf" PARAMETER )----------\n' f'\t{repr(conf)}\n' ) # Else, the caller passed the same configuration to that prior call # than that passed to the current call. # Else, the caller requested coverage over a subset of packages. In this # case... else: # For the fully-qualified name of each package to be registered... for package_name in package_names: # type: ignore[union-attr] # List of each unqualified basename comprising this name, split # from this fully-qualified name on "." delimiters. Note that # the "str.split('.')" and "str.rsplit('.')" calls produce the # exact same lists under all possible edge cases. We arbitrarily # call the former rather than the latter for simplicity. package_basenames = package_name.split('.') # Current subtrie of the global package trie describing the # currently iterated basename of this package, initialized to # the global trie configuring all top-level packages. subpackages_trie = claw_state.packages_trie # For each unqualified basename comprising the directed path from # the root parent package of that package to that package... for package_basename in package_basenames: # Current subtrie of that trie describing that parent package if # that parent package was registered by a prior call to the # hook_packages() function *OR* "None" (i.e., if that parent # package has yet to be registered). subpackages_subtrie = subpackages_trie.get(package_basename) # If this is the first registration of that parent package, # register a new subtrie describing that parent package. # # Note that this test could be obviated away by refactoring our # "PackagesTrie" subclass from the "collections.defaultdict" # superclass rather than the standard "dict" class. Since doing # so would obscure erroneous attempts to access non-existing # keys, however, this test is preferable to inviting even *MORE* # bugs into this bug-riddled codebase. Just kidding! There are # absolutely no bugs in this codebase. *wink* if subpackages_subtrie is None: subpackages_subtrie = \ subpackages_trie[package_basename] = \ PackagesTrie(package_basename=package_basename) # Else, that parent package was already registered by a prior # call to this function. # Iterate the current subtrie one subpackage deeper. subpackages_trie = subpackages_subtrie # Since the "package_basenames" list contains at least one basename, # the above iteration set the currently examined subdictionary # "subpackages_trie" to at least one subtrie of the global package # trie. Moreover, that subtrie is guaranteed to describe the current # (sub)package being registered. # print(f'Hooked package "{package_name}" subpackage trie {repr(subpackages_trie)}...') # Beartype configuration currently associated with that package by a # prior call to this function if any *OR* "None" (i.e., if that # package has yet to be registered by a prior call to this # function). conf_curr = subpackages_trie.conf_if_hooked # If that package has yet to be registered, associate this # configuration with that package. if conf_curr is None: subpackages_trie.conf_if_hooked = conf # Else, that package was already registered by a previous call to # this function. # # If the caller passed a different configuration to that prior call # than that passed to this current call, raise an exception. elif conf_curr != conf: raise BeartypeClawHookException( f'Beartype import hook ' f'(e.g., beartype.claw.beartype_*() function) ' f'previously passed ' f'conflicting beartype configuration for ' f'package name "{package_name}":\n' f'\t----------( OLD "conf" PARAMETER )----------\n' f'\t{repr(conf_curr)}\n' f'\t----------( NEW "conf" PARAMETER )----------\n' f'\t{repr(conf)}\n' ) # Else, the caller passed the same configuration to that prior call # than that passed to the current call. In this case, silently # ignore this redundant request to reregister that package. # Lastly, if our beartype import path hook singleton has *NOT* already # been added to the standard "sys.path_hooks" list, do so now. # # Note that we intentionally: # * Do so in a thread-safe manner *INSIDE* this lock. # * Defer doing so until *AFTER* the above iteration has successfully # registered the desired packages with our global trie. Why? This path # hook subsequently calls the companion get_package_conf_or_none() # function, which accesses this trie. add_beartype_pathhook() #FIXME: Unit test us up, please. def unhook_packages( # Keyword-only arguments. *, # Mandatory keyword-only arguments. claw_coverage: BeartypeClawCoverage, conf: BeartypeConf, # Optional keyword-only arguments. package_name: Optional[str] = None, package_names: Optional[Iterable[str]] = None, ) -> None: ''' Unregister a previously registered **beartype package import path hook** (i.e., callable inserted to the front of the standard :mod:`sys.path_hooks` list recursively applying the :func:`beartype.beartype` decorator to all typed callables and classes of all submodules of all packages with the passed names on the first importation of those submodules). See Also -------- :func:`.hook_packages` Further details. ''' # Avoid circular import dependencies. from beartype.claw._clawstate import ( claw_lock, claw_state, ) # Replace this beartype configuration (which is typically unsuitable for # usage in import hooks) with a new beartype configuration suitable for # usage in import hooks. conf = make_conf_hookable(conf) # Iterable of the passed fully-qualified names of all packages to be # unhooked. package_names = make_package_names_from_args( claw_coverage=claw_coverage, conf=conf, package_name=package_name, package_names=package_names, ) # With a submodule-specific thread-safe reentrant lock... with claw_lock: # If the caller requested all-packages coverage... if claw_coverage is BeartypeClawCoverage.PACKAGES_ALL: # Unhook the beartype configuration previously associated with *ALL* # packages by a prior call to the beartype_all() function. claw_state.packages_trie.conf_if_hooked = None # Else, the caller requested coverage over a subset of packages. In this # case... else: # For the fully-qualified names of each package to be # unregistered... for package_name in package_names: # type: ignore[union-attr] # List of all subpackages tries describing each parent package # transitively containing the passed package (as well as that of # that package itself). subpackages_tries = list(iter_packages_trie(package_name)) # Reverse this list in-place, such that: # * The first item of this list is the subpackages trie # describing that package itself. # * The last item of this list is the subpackages trie # describing the root package of that package. subpackages_tries.reverse() # Unhook the beartype configuration previously associated with # that package by a prior call to the hook_packages() function. subpackages_tries[0].conf_if_hooked = None # Child sub-subpackages trie of the currently iterated # subpackages trie, describing the child subpackage of the # current parent package transitively containing that package. subsubpackages_trie = None # For each subpackages trie describing a parent package # transitively containing that package... for subpackages_trie in subpackages_tries: # If this is *NOT* the first iteration of this loop (in # which case this subpackages trie is a parent package # rather than that package itself) *AND*... if subsubpackages_trie is not None: # If this child sub-subpackages trie describing this # child sub-subpackage has one or more children, then # this child sub-subpackages trie still stores # meaningful metadata and is thus *NOT* safely # deletable. Moreover, this implies that: # * *ALL* parent subpackages tries of this child # sub-subpackages trie also still store meaningful # metadata and are thus also *NOT* safely deletable. # * There exists no more meaningful work to be performed # by this iteration. Ergo, we immediately halt this # iteration now. if subsubpackages_trie: break # Else, this child sub-subpackages trie describing this # child sub-subpackage has *NO* children, implying this # child sub-subpackages trie no longer stores any # meaningful metadata and is thus safely deletable. # Unqualified basename of this child sub-subpackage. subsubpackage_basename = ( subsubpackages_trie.package_basename) # Delete this child sub-subpackages trie from this # parent subpackages trie. del subpackages_trie[subsubpackage_basename] # pyright: ignore # Else, this is the first iteration of this loop. # Treat this parent subpackages trie as the child # sub-subpackages trie in the next iteration of this loop. subsubpackages_trie = subpackages_trie # Lastly, if *ALL* meaningful metadata has now been removed from our # global trie, remove our beartype import path hook singleton from the # standard "sys.path_hooks" list. # # Note that we intentionally: # * Do so in a thread-safe manner *INSIDE* this lock. # * Defer doing so until *AFTER* the above iteration has successfully # unregistered the desired packages with our global trie. remove_beartype_pathhook_unless_packages_trie() beartype-0.18.5/beartype/claw/_pkg/clawpkgtrie.py000066400000000000000000000410471461113517100217750ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype import path hook trie** (i.e., data structure caching package names on behalf of the higher-level :func:`beartype.claw._clawmain` submodule, which beartype import path hooks internally created by that submodule subsequently lookup when deciding whether or not (and how) to decorate by :func:`beartype.beartype` the currently imported user-specific submodule). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.claw._importlib.clawimppath import remove_beartype_pathhook from beartype.roar import BeartypeClawHookException from beartype.typing import ( Dict, Iterable, # Iterator, Optional, ) from beartype._cave._cavemap import NoneTypeOr from beartype._conf.confcls import BeartypeConf # from pprint import pformat # ....................{ CLASSES }.................... #FIXME: Unit test us up, please. class PackagesTrie( #FIXME: Use "beartype.typing.Self" here instead once we backport that. Dict[str, Optional['PackagesTrie']]): ''' **(Sub)package configuration (sub)trie** (i.e., recursively nested dictionary mapping from the unqualified basename of each subpackage of the current package to be runtime type-checked on the first importation of that subpackage to another instance of this class similarly describing the sub-subpackages of that subpackage). This (sub)cache is suitable for caching as the values of: * The :data:`.packages_trie` global dictionary. * Each (sub)value mapped to by that global dictionary. Motivation ---------- This dictionary is intentionally implemented as a nested trie data structure rather than a trivial non-nested flat dictionary. Why? Efficiency. Consider this flattened set of package names: .. code-block:: python package_names = {'a.b', 'a.c', 'd'} Deciding whether an arbitrary package name is in this set requires worst-case ``O(n)`` iteration across the set of ``n`` package names. Consider instead this nested dictionary whose keys are package names split on ``"."`` delimiters and whose values are either recursively nested dictionaries of the same format *or* the :data:`None` singleton (terminating the current package name): .. code-block:: python package_names_trie = {'a': {'b': None, 'c': None}, 'd': None} Deciding whether an arbitrary package name is in this dictionary only requires worst-case ``O(h)`` iteration across the height ``h`` of this dictionary (equivalent to the largest number of ``"."`` delimiters for any fully-qualified package name encapsulated by this dictionary). ``h <<<< n``, so this dictionary offers *much* faster worst-case lookup than that set. Moreover, in the worst case: * That set requires one inefficient string prefix test for each item. * This dictionary requires *only* one efficient string equality test for each nested key-value pair while descending towards the target package name. Let's do this, fam. Caveats ------- **This dictionary is only safely accessible in a thread-safe manner from within a** ``with claw_lock:`` **context manager.** Equivalently, this dictionary is *not* safely accessible outside that manager. Examples -------- An example instance of this dictionary hooked on submodules of the root ``package_z`` package, the child ``package_a.subpackage_k`` submodule, and the ``package_a.subpackage_b.subpackage_c`` and ``package_a.subpackage_b.subpackage_d`` submodules: >>> packages_trie = PackagesTrie({ ... 'package_a': PackagesTrie({ ... 'subpackage_b': PackagesTrie({ ... 'subpackage_c': None, ... 'subpackage_d': None, ... }), ... 'subpackage_k': None, ... }), ... 'package_z': None, ... }) Attributes ---------- conf_if_hooked : Optional[BeartypeConf] Either: * If this (sub)package has been explicitly registered by a prior call to the :func:`add_package_names` function, the **beartype configuration** (i.e., dataclass encapsulating all settings configuring type-checking for this (sub)package). * Else, :data:`None`. package_basename : Optional[str] Either: * If this (sub)trie is the global trie :data:`.packages_trie`, :data:`None`. * Else, the unqualified basename of the (sub)package configured by this (sub)trie. ''' # ..................{ CLASS VARIABLES }.................. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Subclasses declaring uniquely subclass-specific instance # variables *MUST* additionally slot those variables. Subclasses violating # this constraint will be usable but unslotted, which defeats our purposes. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Slot all instance variables defined on this object to minimize the time # complexity of both reading and writing variables across frequently called # cache dunder methods. Slotting has been shown to reduce read and write # costs by approximately ~10%, which is non-trivial. __slots__ = ( 'package_basename', 'conf_if_hooked', ) # ..................{ INITIALIZERS }.................. def __init__( self, package_basename : Optional[str], *args, **kwargs ) -> None: ''' Initialize this package name (sub)cache. Parameters ---------- basename : Optional[str] Either: * If this (sub)trie is the global trie :data:`.packages_trie`, :data:`None`. * Else, the unqualified basename of the (sub)package configured by this (sub)trie. All remaining passed parameters are passed as is to the superclass :meth:`dict.__init__` method. ''' assert isinstance(package_basename, NoneTypeOr[str]), ( f'{repr(package_basename)} neither string nor "None".') # Initialize our superclass with all passed parameters. super().__init__(*args, **kwargs) # Classify all remaining passed parameters. self.package_basename = package_basename # Nullify all subclass-specific parameters for safety. self.conf_if_hooked: Optional[BeartypeConf] = None # ..................{ DUNDERS }.................. def __repr__(self) -> str: return '\n'.join(( f'{self.__class__.__name__}(', f' package_basename={repr(self.package_basename)},', f' conf_if_hooked={repr(self.conf_if_hooked)},', f' dict={super().__repr__()},', f')', )) # ....................{ RAISERS }.................... def die_if_packages_trie() -> None: ''' Raise an exception if one or more packages have been registered by a prior call to the :func:`beartype.claw._pkg.clawpkghook.hook_packages` function. Raises ------ BeartypeClawHookException If one or more packages have been registered by a prior call to the :func:`beartype.claw._pkg.clawpkghook.hook_packages` function. ''' # If one or more packages have been registered... if is_packages_trie(): # Avoid circular import dependencies. from beartype.claw._clawstate import claw_state # If a global configuration has been added by a prior call to the public # beartype.claw.beartype_all() function, raise an exception. if claw_state.packages_trie.conf_if_hooked is not None: raise BeartypeClawHookException( f'Prior call to package-agnostic import hook ' f'beartype.claw.beartype_all() already registered all packages ' f'for type-checking under global beartype configuration ' f'{repr(claw_state.packages_trie.conf_if_hooked)}.' ) # Else, or more package-specific configurations have been added by prior # calls to public beartype.claw.beartype_*() functions. In this case, # raise another exception. else: raise BeartypeClawHookException( f'Prior call to package-specific import hook ' f'beartype.claw.beartype_*() already registered some packages ' f'for type-checking under beartype configurations:\n\t' f'{repr(claw_state.packages_trie)}' ) # ....................{ TESTERS }.................... #FIXME: Unit test us up, please. def is_packages_trie() -> bool: ''' :data:`True` only if one or more packages have been registered by a prior call to the :func:`beartype.claw._pkg.clawpkghook.hook_packages` function. Returns ------- bool :data:`True` only if one or more packages have been registered. ''' # Avoid circular import dependencies. from beartype.claw._clawstate import claw_state # Return true only if either... return ( # A global configuration has been added by a prior call to the public # beartype.claw.beartype_all() function *OR*... claw_state.packages_trie.conf_if_hooked is not None or # One or more package-specific configurations have been added by prior # calls to public beartype.claw.beartype_*() functions. bool(claw_state.packages_trie) ) # ....................{ GETTERS }.................... #FIXME: Unit test us up, please. def get_package_conf_or_none(package_name: str) -> Optional[BeartypeConf]: ''' Beartype configuration with which to type-check the package with the passed name if that package *or* a parent package of that package was registered by a prior call to the :func:`.hook_packages` function *or* :data:`None` otherwise (i.e., if neither that package *nor* a parent package of that package was registered by such a call). Parameters ---------- package_name : str Fully-qualified name of the package to be inspected. Returns ------- Optional[BeartypeConf] Either: * If that package or a parent package of that package was registered by a prior call to the :func:`.hook_packages` function, the beartype configuration with which to type-check that package. * Else, :data:`None`. ''' # Avoid circular import dependencies. from beartype.claw._clawstate import claw_state # Beartype configuration registered for the currently iterated package, # defaulting to the beartype configuration registered for the global trie # applicable to *ALL* packages if an external caller previously called the # public beartype.claw.beartype_all() function *OR* "None" otherwise (i.e., # if that function has yet to be called). subpackage_conf = claw_state.packages_trie.conf_if_hooked # For each subpackages trie describing each parent package transitively # containing this package (as well as that of that package itself)... for subpackages_trie in iter_packages_trie(package_name): # Beartype configuration registered with either... subpackage_conf = ( # That parent package if any *OR*... # # Since that parent package is more granular (i.e., unique) than # any transitive parent package of that parent package, the # former takes precedence over the latter when defined. subpackages_trie.conf_if_hooked or # A transitive parent package of that parent package if any. subpackage_conf ) # Return this beartype configuration if any *OR* "None" otherwise. return subpackage_conf # ....................{ ITERATORS }.................... #FIXME: Unit test us up, please. def iter_packages_trie(package_name: str) -> Iterable[PackagesTrie]: ''' Generator iteratively yielding one **(sub)package configuration (sub)trie** (i.e., :class:`PackagesTrie` instance) describing each transitive parent package of the package with the passed name if that package *or* a parent package of that package was registered by a prior call to the :func:`beartype.claw._pkg.clawpkghook..hook_packages` function *or* the empty iterable otherwise otherwise (i.e., if neither that package *nor* a parent package of that package was registered by such a call). Specifically, this generator yields (in order): #. The subtrie of that trie configuring the root package of the passed (sub)package. #. And so on, until eventually yielding... #. The subsubtrie of that subtrie configuring the passed (sub)package itself. This generator intentionally avoids yielding the global trie :data:`.packages_trie`, which is already accessible via that global. Parameters ---------- package_name : str Fully-qualified name of the package to be inspected. Yields ------ PackagesTrie (Sub)package configuration (sub)trie describing the currently iterated transitive parent package of the package with this name. ''' assert isinstance(package_name, str), f'{repr(package_name)} not string.' # Avoid circular import dependencies. from beartype.claw._clawstate import ( claw_lock, claw_state, ) # List of each unqualified basename comprising this name, split from this # fully-qualified name on "." delimiters. Note that the "str.split('.')" and # "str.rsplit('.')" calls produce the exact same lists under all possible # edge cases. We arbitrarily call the former rather than the latter for # simplicity and readability. package_basenames = package_name.split('.') # With a submodule-specific thread-safe reentrant lock... with claw_lock: # Current subtrie of the global trie describing the currently iterated # basename of this package, initialized to this global trie itself. subpackages_trie: Optional[PackagesTrie] = claw_state.packages_trie # For each unqualified basename of each parent package transitively # containing this package (as well as that of that package itself)... for package_basename in package_basenames: # Current subtrie of that trie describing that parent package if # that parent package was registered by a prior call to the # hook_packages() function *OR* "None" otherwise (i.e., if that # parent package has yet to be registered). subpackages_trie = subpackages_trie.get(package_basename) # type: ignore[union-attr] # If that parent package has yet to be registered, halt iteration. if subpackages_trie is None: break # Else, that parent package was previously registered. # Yield the current subtrie describing that parent package. yield subpackages_trie # ....................{ REMOVERS }.................... #FIXME: Unit test us up, please. def remove_beartype_pathhook_unless_packages_trie() -> None: ''' Remove our **beartype import path hook singleton** (i.e., single callable guaranteed to be inserted at most once to the front of the standard :mod:`sys.path_hooks` list recursively applying the :func:`beartype.beartype` decorator to all well-typed callables and classes defined by all submodules of all packages previously registered by a call to a public :func:`beartype.claw` function) if this path hook has already been added and all previously registered packages have been unregistered *or* silently reduce to a noop otherwise (i.e., if either this path hook has yet to be added or one or more packages are still registered). Caveats ------- **This function is non-thread-safe.** For both simplicity and efficiency, the caller is expected to provide thread-safety through a higher-level locking primitive managed by the caller. ''' # If all previously registered packages have been unregistered, safely # remove our import path hook from the "sys.path_hooks" list. if not is_packages_trie(): remove_beartype_pathhook() beartype-0.18.5/beartype/door/000077500000000000000000000000001461113517100161765ustar00rootroot00000000000000beartype-0.18.5/beartype/door/__init__.py000066400000000000000000000056731461113517100203220ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype Decidedly Object-Oriented Runtime-checking (DOOR) API.** This subpackage provides an object-oriented type hint class hierarchy, encapsulating the crude non-object-oriented type hint declarative API standardized by the :mod:`typing` module. ''' # ....................{ TODO }.................... #FIXME: Create one unique "TypeHint" subclass *FOR EACH UNIQUE KIND OF TYPE #HINT.* We're currently simply reusing the same #"_TypeHintOriginIsinstanceableArgs*" family of concrete subclasses to #transparently handle these unique kinds of type hints. That's fine as an #internal implementation convenience. Sadly, that's *NOT* fine for users #actually trying to introspect types. That's the great disadvantage of standard #"typing" types, after all; they're *NOT* introspectable by type. Ergo, we need #to explicitly define subclasses like: #* "beartype.door.ListTypeHint". #* "beartype.door.MappingTypeHint". #* "beartype.door.SequenceTypeHint". # #And so on. There are a plethora, but ultimately a finite plethora, which is all #that matters. Do this for our wonderful userbase, please. # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To avoid polluting the public module namespace, external attributes # should be locally imported at module scope *ONLY* under alternate private # names (e.g., "from argparse import ArgumentParser as _ArgumentParser" rather # than merely "from argparse import ArgumentParser"). #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype.door._cls.doorsuper import ( TypeHint as TypeHint) from beartype.door._doorcheck import ( die_if_unbearable as die_if_unbearable, is_bearable as is_bearable, is_subhint as is_subhint, ) from beartype.door._cls.pep.doorpep484604 import ( UnionTypeHint as UnionTypeHint) from beartype.door._cls.pep.doorpep586 import ( LiteralTypeHint as LiteralTypeHint) from beartype.door._cls.pep.doorpep593 import ( AnnotatedTypeHint as AnnotatedTypeHint) from beartype.door._cls.pep.pep484.doorpep484class import ( ClassTypeHint as ClassTypeHint) from beartype.door._cls.pep.pep484.doorpep484newtype import ( NewTypeTypeHint as NewTypeTypeHint) from beartype.door._cls.pep.pep484.doorpep484typevar import ( TypeVarTypeHint as TypeVarTypeHint) from beartype.door._cls.pep.pep484585.doorpep484585callable import ( CallableTypeHint as CallableTypeHint) #FIXME: Actually, let's *NOT* publicly expose this for the moment. Why? Because #we still need to split this into fixed and variadic tuple subclasses. # from beartype.door._cls.pep.pep484585.doorpep484585tuple import ( # _TupleTypeHint as _TupleTypeHint) beartype-0.18.5/beartype/door/_cls/000077500000000000000000000000001461113517100171165ustar00rootroot00000000000000beartype-0.18.5/beartype/door/_cls/__init__.py000066400000000000000000000000001461113517100212150ustar00rootroot00000000000000beartype-0.18.5/beartype/door/_cls/doormeta.py000066400000000000000000000340631461113517100213100ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype Decidedly Object-Oriented Runtime-checking (DOOR) metaclass hierarchy** (i.e., metaclass hierarchy driving our object-oriented type hint class hierarchy, especially with respect to instantiation, mapping, and memoization). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from abc import ABCMeta from beartype.typing import Any from beartype._cave._cavefast import NoneType from beartype._util.cache.map.utilmapbig import CacheUnboundedStrong from beartype._util.hint.utilhinttest import is_hint_uncached from threading import RLock # ....................{ METACLASSES }.................... #FIXME: Unit test us up, please. class _TypeHintMeta(ABCMeta): ''' **Singleton abstract base class (ABC) metaclass** (i.e., the standard :class:`abc.ABCMeta` metaclass augmented with caching to implement the singleton design pattern). This metaclass is superior to the usual approach of implementing the singleton design pattern: overriding the :meth:`__new__` method of a singleton class to conditionally create a new instance of that class only if an instance has *not* already been created. Why? Because that approach unavoidably re-calls the :meth:`__init__` method of a previously initialized singleton instance on each instantiation of that class. Doing so is generally considered harmful. This metaclass instead guarantees that the :meth:`__init__` method of a singleton instance is only called exactly once on the first instantiation of that class. Attributes ---------- __singleton : Optional[type] Either: * If the current singleton abstract base class (ABC) has been initialized (i.e., if the :meth:`__init__` method of this metaclass initializing that class with metaclass-specific logic) but a singleton instance of that class has *not* yet been instantiated (i.e., if the :meth:`__call__` method of this metaclass calling the :meth:`__new__` and :meth:`__init__` methods of that class (in that order) has been called), ``None``. * Else, the current singleton ABC has been initialized and a singleton instance of that class has been instantiated. In this case, that instance. For forward compatibility with future :class:`ABCMeta` changes, the name of this instance variable is prefixed by ``"__"`` and thus implicitly obfuscated by Python to be externally inaccessible. See Also ---------- https://stackoverflow.com/a/8665179/2809027 StackOverflow answers strongly inspiring this implementation. ''' # ..................{ INSTANTIATORS }.................. def __call__(cls: '_TypeHintMeta', hint: object) -> Any: # type: ignore[override] ''' Factory constructor magically instantiating and returning a singleton instance of the concrete subclass of the :class:`beartype.door.TypeHint` abstract base class (ABC) appropriate for handling the passed low-level type hint. Parameters ---------- cls : _TypeHintMeta The :class:`beartype.door.TypeHint` ABC. hint : object Low-level type hint to be wrapped by a singleton :class:`beartype.door.TypeHint` instance. Raises ---------- BeartypeDoorNonpepException If this class does *not* currently support the passed hint. BeartypeDecorHintPepSignException If the passed hint is *not* actually a PEP-compliant type hint. ''' # print(f'!!!!!!!!!!!!! [ in _TypeHintMeta.__call__(cls={repr(cls)}, hint={repr(hint)}) ] !!!!!!!!!!!!!!!') # ................{ IMPORTS }................ # Avoid circular import dependencies. from beartype.door._cls.doorsuper import TypeHint # ................{ TRIVIALITIES }................ # If this class is a concrete subclass of the "TypeHint" abstract base # class (ABC) rather than ABC itself, instantiate that subclass in the # standard way. if cls is not TypeHint: # print('!!!!!!!!!!!!! [ _TypeHintMeta.__call__ ] instantiating subclass... !!!!!!!!!!!!!!!') return super().__call__(hint) # Else, this class is that ABC. In this case, instantiate that ABC in a # non-standard way. # # If this low-level type hint is already a high-level type hint wrapper, # return this wrapper as is. This guarantees the following constraint: # >>> TypeHint(TypeHint(hint)) is TypeHint(hint) # True elif isinstance(hint, TypeHint): # print('!!!!!!!!!!!!! [ _TypeHintMeta.__call__ ] reducing to noop... !!!!!!!!!!!!!!!') return hint # Else, this hint is *NOT* already a wrapper. # ................{ CACHING }................ # Key uniquely identifying this hint, defined as either... hint_key = ( # If this hint is *NOT* self-caching (i.e., *NOT* already internally # cached by its parent class or module), the machine-readable # representation of this hint. Computing this string consumes more # time and space and is thus performed *ONLY* where required, which # is for hints that are *NOT* already reduced to singleton objects. # # Note that this is *NOT* merely an optimization concern. Some # PEP-compliant type hints have arbitrary caller-defined and thus # possibly ambiguous representations. Ergo, the machine-readable # representation of an arbitrary hint does *NOT* uniquely identify # that hint in general and thus *CANNOT* be used to cache that hint. # Class factories producing hints with such names include: # * "typing.ParamSpec". # * "typing.TypeVar". repr(hint) if is_hint_uncached(hint) else # Else, this hint is self-caching and thus already reduced to a # singleton object. In this case, the identifier identifying this # singleton object. id(hint) ) # Type hint wrapper wrapping this hint, efficiently cached such that # each hint that evaluates to the same key is wrapped by the same # instance of the "TypeHint" class under this Python interpreter. wrapper = ( _HINT_KEY_TO_WRAPPER.cache_or_get_cached_func_return_passed_arg( # Cache this wrapper singleton under this key. key=hint_key, # If a wrapper singleton has yet to be instantiated for this # hint, do so by calling this private factory method... value_factory=cls._make_wrapper, # ...with this hint passed as the sole parameter to that method. arg=hint, )) # Return this wrapper. return wrapper # ..................{ PRIVATE }.................. def _make_wrapper(cls: '_TypeHintMeta', hint: object) -> object: ''' **Type hint wrapper factory** (i.e., low-level private method creating and returning a new :class:`beartype.door.TypeHint` instance wrapping the passed type hint), intended to be called by the :meth:`CacheUnboundedStrong.cache_or_get_cached_func_return_passed_arg` method to create a new type hint wrapper singleton for the passed hint. Parameters ---------- cls : _TypeHintMeta The :class:`beartype.door.TypeHint` ABC. hint : object Low-level type hint to be wrapped by a singleton :class:`beartype.door.TypeHint` instance. Raises ---------- BeartypeDoorNonpepException If this class does *not* currently support the passed hint. BeartypeDecorHintPepSignException If the passed hint is *not* actually a PEP-compliant type hint. ''' # ................{ IMPORTS }................ # Avoid circular import dependencies. from beartype.door._doordata import get_typehint_subclass # ................{ REDUCTION }................ # Reduce this hint to a more amenable form suitable for mapping to a # concrete "TypeHint" subclass if desired. # # Note that this reduction intentionally ignores the entire # "beartype._check.convert" subpackage. Although submodules of that # subpackage do perform various coercions, reductions, and sanitizations # of low-level PEP-compliant type hints, they do so only for the express # purpose of dynamic code generation. That subpackage is *NOT* # general-purpose and is, in fact, harmful in this context. Why? Because # that subpackage erodes the semantic meaning from numerous type hints # that this subpackage necessarily preserves. # # ................{ REDUCTION ~ pep 484 : none }................ # If this is the PEP 484-compliant "None" singleton, reduce this hint to # the type of that singleton. While *NOT* explicitly defined by the # "typing" module, PEP 484 explicitly supports this singleton: # When used in a type hint, the expression None is considered # equivalent to type(None). # # The "None" singleton is used to type callables lacking an explicit # "return" statement and thus absurdly common. Ergo, detect this early. if hint is None: hint = NoneType # pyright: ignore[reportGeneralTypeIssues] # Else, this is *NOT* the PEP 484-compliant "None" singleton. # ................{ INSTANTIATION }................ # Concrete "TypeHint" subclass handling this hint if this hint is # supported by an existing "TypeHint" subclass *OR* raise an exception # otherwise (i.e., if this hint is currently unsupported). wrapper_subclass = get_typehint_subclass(hint) # print(f'!!!!!!!!!!!!! [ in {repr(cls)}.__new__() ] !!!!!!!!!!!!!!!') # Type hint wrapper wrapping this hint as a new singleton instance of # this subclass. wrapper = wrapper_subclass(hint) # wrapper = super(_TypeHintMeta, wrapper_subclass).__call__(hint) # print('!!!!!!!!!!!!! [ _TypeHintMeta.__call__ ] caching and returning singleton... !!!!!!!!!!!!!!!') # Return this wrapper. return wrapper # ....................{ PRIVATE ~ mappings }.................... _HINT_KEY_TO_WRAPPER = CacheUnboundedStrong( # Prefer the slower reentrant lock type for safety. As the subpackage name # implies, the DOOR API is fundamentally recursive and requires reentrancy. lock_type=RLock, ) ''' **Type hint wrapper cache** (i.e., non-thread-safe cache mapping from the machine-readable representations of all type hints to cached singleton instances of concrete subclasses of the :class:`beartype.door.TypeHint` abstract base class (ABC) wrapping those hints). Design -------------- **This dictionary is intentionally thread-safe.** Why? Because this dictionary is used to ensure that :class:`beartype.door.TypeHint` instances are singletons, enabling callers to reliably implement higher-level abstractions memoized (i.e., cached) against these singletons. Those abstractions could be module-scoped and thus effectively global. To prevent race conditions between competing threads contending over those globals, this dictionary *must* be thread-safe. **This dictionary is intentionally designed as a naive dictionary rather than a robust LRU cache,** for the same reasons that callables accepting hints are memoized by the :func:`beartype._util.cache.utilcachecall.callable_cached` rather than the :func:`functools.lru_cache` decorator. Why? Because: * The number of different type hints instantiated across even worst-case codebases is negligible in comparison to the space consumed by those hints. * The :attr:`sys.modules` dictionary persists strong references to all callables declared by previously imported modules. In turn, the ``func.__annotations__`` dunder dictionary of each such callable persists strong references to all type hints annotating that callable. In turn, these two statements imply that type hints are *never* garbage collected but instead persisted for the lifetime of the active Python process. Ergo, temporarily caching hints in an LRU cache is pointless, as there are *no* space savings in dropping stale references to unused hints. **This dictionary intentionally caches machine-readable representation strings hashes rather than alternative keys** (e.g., actual hashes). Why? Disambiguity. Although comparatively less efficient in both space and time to construct than hashes, the :func:`repr` strings produced for two dissimilar type hints *never* ambiguously collide unless an external caller maliciously modified one or more identifying dunder attributes of those hints (e.g., the ``__module__``, ``__qualname__``, and/or ``__name__`` dunder attributes). That should *never* occur in production code. Meanwhile, the :func:`hash` values produced for two dissimilar type hints *commonly* ambiguously collide. This is why hashable containers (e.g., :class:`dict`, :class:`set`) explicitly handle hash table collisions and why we are *not* going to do so. Likewise, this dictionary intentionally caches machine-readable representations of low-level type hints rather than those hints themselves. Since increasingly many hints are no longer self-caching (e.g., PEP 585-compliant type hints like "list[str]"), the latter *CANNOT* be guaranteed to be singletons and thus safely used as cache keys. Also: ''' beartype-0.18.5/beartype/door/_cls/doorsub.py000066400000000000000000000161721461113517100211540ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **Decidedly Object-Oriented Runtime-checking (DOOR) middle-men subclasses** (i.e., abstract subclasses of the object-oriented type hint class hierarchy simplifying the definition of concrete subclasses of this hierarchy). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from abc import abstractmethod from beartype.door._cls.doorsuper import TypeHint from beartype.roar import BeartypeDoorException # from beartype.typing import ( # Any, # ) # from beartype._util.cache.utilcachecall import property_cached # from beartype._util.cls.utilclstest import is_type_subclass # ....................{ SUBCLASSES }.................... #FIXME: Excise us up, please. Globally replace all instances of #"_TypeHintSubscripted" with simply "TypeHint". class _TypeHintSubscripted(TypeHint): ''' **Subscripted type hint wrapper** (i.e., high-level object encapsulating a low-level parent type hint subscripted (indexed) by one or more equally low-level children type hints). ''' pass # ....................{ SUBCLASSES ~ isinstanceable }.................... class _TypeHintOriginIsinstanceable(_TypeHintSubscripted): ''' **Isinstanceable type hint wrapper** (i.e., high-level object encapsulating a low-level parent type hint subscripted (indexed) by exactly one or more low-level child type hints originating from isinstanceable classes such that *all* objects satisfying those hints are instances of those class). ''' # ..................{ PRIVATE ~ properties }.................. @property @abstractmethod def _args_len_expected(self) -> int: ''' Number of child type hints that this instance of a concrete subclass of this abstract base class (ABC) is expected to be subscripted (indexed) by. ''' pass # ..................{ PRIVATE ~ factories }.................. def _make_args(self) -> tuple: # Tuple of the zero or more low-level child type hints subscripting # (indexing) the low-level parent type hint wrapped by this wrapper. args = super()._make_args() # If this hint was subscripted by an unexpected number of child hints... if len(args) != self._args_len_expected: #FIXME: This seems sensible, but currently provokes test failures. #Let's investigate further at a later time, please. # # If this hint was subscripted by *NO* parameters, comply with PEP # # 484 standards by silently pretending this hint was subscripted by # # the "typing.Any" fallback for all missing parameters. # if len(self._args) == 0: # return (Any,)*self._args_len_expected #FIXME: Consider raising a less ambiguous exception type, yo. #FIXME: Consider actually testing this. This *IS* technically #testable and should thus *NOT* be marked as "pragma: no cover". # In most cases it will be hard to reach this exception, since most # of the typing library's subscripted type hints will raise an # exception if constructed improperly. raise BeartypeDoorException( # pragma: no cover f'{type(self)} type must have {self._args_len_expected} ' f'argument(s), but got {len(args)}.' ) # Else, this hint was subscripted by the expected number of child hints. # Return these child hints. return args # ..................{ PRIVATE ~ testers }.................. # Note that this redefinition of the superclass _is_equal() method is # technically unnecessary, as that method is already sufficiently # general-purpose to suffice for *ALL* possible subclasses (including this # subclass). Nonetheless, we wrote this method first. More importantly, this # method is *SUBSTANTIALLY* faster than the superclass method. Although # efficiency is typically *NOT* a pressing concern for the DOOR API, # discarding faster working code would be senseless. def _is_equal(self, other: TypeHint) -> bool: # If *ALL* of the child type hints subscripting both of these parent # type hints are ignorable, return true only if these parent type hints # both originate from the same isinstanceable class. if self._is_args_ignorable and other._is_args_ignorable: return self._origin == other._origin # Else, one or more of the child type hints subscripting either of these # parent type hints are unignorable. # # If either... elif ( # These hints have differing signs *OR*... self._hint_sign is not other._hint_sign or # These hints have a differing number of child type hints... len(self._args_wrapped_tuple) != len(other._args_wrapped_tuple) ): # Then these hints are unequal. return False # Else, these hints share the same sign and number of child type hints. # Return true only if all child type hints of these hints are equal. return all( this_child == that_child #FIXME: Probably more efficient and maintainable to write this as: # for this_child in self # for that_child in other for this_child, that_child in zip( self._args_wrapped_tuple, other._args_wrapped_tuple) ) class _TypeHintOriginIsinstanceableArgs1(_TypeHintOriginIsinstanceable): ''' **1-argument isinstanceable type hint wrapper** (i.e., high-level object encapsulating a low-level parent type hint subscripted (indexed) by exactly one low-level child type hint originating from an isinstanceable class such that *all* objects satisfying that hint are instances of that class). ''' @property def _args_len_expected(self) -> int: return 1 class _TypeHintOriginIsinstanceableArgs2(_TypeHintOriginIsinstanceable): ''' **2-argument isinstanceable type hint wrapper** (i.e., high-level object encapsulating a low-level parent type hint subscripted (indexed) by exactly two low-level child type hints originating from isinstanceable classes such that *all* objects satisfying those hints are instances of those classes). ''' @property def _args_len_expected(self) -> int: return 2 class _TypeHintOriginIsinstanceableArgs3(_TypeHintOriginIsinstanceable): ''' **3-argument isinstanceable type hint wrapper** (i.e., high-level object encapsulating a low-level parent type hint subscripted (indexed) by exactly three low-level child type hints originating from isinstanceable classes such that *all* objects satisfying those hints are instances of those classes). ''' @property def _args_len_expected(self) -> int: return 3 beartype-0.18.5/beartype/door/_cls/doorsuper.py000066400000000000000000001117431461113517100215210ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **Decidedly Object-Oriented Runtime-checking (DOOR) superclass** (i.e., root of the object-oriented type hint class hierarchy encapsulating the non-object-oriented type hint API standardized by the :mod:`typing` module). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... #FIXME: Slot "TypeHint" attributes for lookup efficiency, please. #FIXME: Privatize most (...or perhaps all) public instance variables, please. # ....................{ IMPORTS }.................... from beartype.door._doorcheck import ( die_if_unbearable, is_bearable, ) from beartype.door._cls.doormeta import _TypeHintMeta from beartype.door._doortest import die_unless_typehint from beartype.typing import ( Any, FrozenSet, Generic, Iterable, Tuple, overload, ) from beartype._conf.confcls import ( BEARTYPE_CONF_DEFAULT, BeartypeConf, ) from beartype._data.hint.datahinttyping import T from beartype._util.cache.utilcachecall import ( method_cached_arg_by_id, property_cached, ) from beartype._util.hint.pep.utilpepget import ( get_hint_pep_args, get_hint_pep_origin_type_or_none, get_hint_pep_sign_or_none, ) from beartype._util.hint.utilhinttest import is_hint_ignorable from beartype._util.utilobject import get_object_type_basename # ....................{ SUPERCLASSES }.................... #FIXME: Subclass all applicable "collections.abc" ABCs for explicitness, please. #FIXME: Document all public and private attributes of this class, please. class TypeHint(Generic[T], metaclass=_TypeHintMeta): ''' Abstract base class (ABC) of all **type hint wrapper** (i.e., high-level object encapsulating a low-level type hint augmented with a magically object-oriented Pythonic API, including equality and rich comparison testing) subclasses. Sorting ------- **Type hint wrappers are partially ordered** with respect to one another. Type hints wrappers support all binary comparators (i.e., ``==``, ``!=``, ``<``, ``<=``, ``>``, and ``>=``) such that for any three type hint wrappers ``a``, ``b`, and ``c``: * ``a ≤ a`` (i.e., **reflexivity**). * If ``a ≤ b`` and ``b ≤ c``, then ``a ≤ c`` (i.e., **transitivity**). * If ``a ≤ b`` and ``b ≤ a``, then ``a == b`` (i.e., **antisymmetry**). **Type hint wrappers are not totally ordered,** however. Like unordered sets, type hint wrappers do *not* satisfy **totality** (i.e., either ``a ≤ b`` or ``b ≤ a``, which is *not* necessarily the case for incommensurable type hint wrappers that *cannot* reasonably be compared with one another). Type hint wrappers are thus usable in algorithms and data structures requiring at most a partial ordering over their input. Examples -------- .. code-block:: pycon >>> from beartype.door import TypeHint >>> hint_a = TypeHint(Callable[[str], list]) >>> hint_b = TypeHint(Callable[Union[int, str], Sequence[Any]]) >>> hint_a <= hint_b True >>> hint_a > hint_b False >>> hint_a.is_subhint(hint_b) True >>> list(hint_b) [TypeHint(typing.Union[int, str]), TypeHint(typing.Sequence[typing.Any])] Attributes ---------- _args : Tuple[object, ...] Tuple of the zero or more low-level child type hints subscripting (indexing) the low-level parent type hint wrapped by this wrapper. _hint : T Low-level type hint wrapped by this wrapper. _hint_sign : beartype._data.hint.pep.sign.datapepsigncls.HintSign | None Either: * If this hint is PEP-compliant and thus uniquely identified by a :mod:`beartype`-specific sign, that sign. * Else (i.e., if this hint is an isinstanceable class), :data:`None`. _origin: type Either: * If this hint originates from an **isinstanceable class** such that all objects satisfying this hint are instances of that class, that class. * Else, the root superclass :class:`object` of *all* classes, guaranteeing sanity when this instance variable is passed as either the first or second parameters to the :func:`issubclass` builtin. ''' # ..................{ INITIALIZERS }.................. def __init__(self, hint: T) -> None: ''' Initialize this type hint wrapper from the passed low-level type hint. Parameters ---------- hint : object Low-level type hint to be wrapped by this wrapper. ''' # Classify all passed parameters. Note that this type hint is guaranteed # to be a type hint by validation performed by this metaclass __init__() # method. self._hint = hint # Sign uniquely identifying this and that hint if any *OR* "None" self._hint_sign = get_hint_pep_sign_or_none(hint) # Isinstance class originating this hint if any *OR* "None" otherwise, # defined as either... self._origin: type = ( # If this hint originates from an origin type, that type; get_hint_pep_origin_type_or_none(hint) or # Else, this hint does *NOT* originate from an origin type. In this # case, the root superclass "object" of *ALL* classes, guaranteeing # sanity when this instance variable is passed as either the first # or second parameters to the issubclass() builtin. object ) # Tuple of all low-level child type hints of this hint. self._args = self._make_args() # ..................{ DUNDERS }.................. def __hash__(self) -> int: ''' Hash of the low-level immutable type hint wrapped by this immutable wrapper. Defining this method satisfies the :class:`collections.abc.Hashable` abstract base class (ABC), enabling this wrapper to be used as in hashable containers (e.g., dictionaries, sets). ''' return hash(self._hint) def __repr__(self) -> str: ''' Machine-readable representation of this type hint wrapper. ''' # Unqualified name of the concrete subclass wrapping this hint. hint_wrapper_basename = get_object_type_basename(self) # If this concrete subclass is currently private, deviously hide this # implementation detail by defaulting to the unqualified name of this # public "TypeHint" superclass instead. if hint_wrapper_basename[0] == '_': hint_wrapper_basename = 'TypeHint' # Else, this concrete subclass is public. # Return this machine-readable representation. return f'{hint_wrapper_basename}({repr(self._hint)})' # ..................{ DUNDERS ~ compare : equals }.................. # Note that we intentionally avoid typing this method as returning # "Union[bool, NotImplementedType]". Why? Because mypy in particular has # epileptic fits about "NotImplementedType". This is *NOT* worth the agony! @method_cached_arg_by_id def __eq__(self, other: object) -> bool: ''' ``True`` only if the low-level type hint wrapped by this wrapper is semantically equivalent to the other low-level type hint wrapped by the passed wrapper. This tester is memoized for efficiency, as Python implicitly calls this dunder method on hashable-based container lookups (e.g., :meth:`dict.get`) expected to be ``O(1)`` fast. Parameters ---------- other : object Other type hint to be tested against this type hint. Returns ------- bool ``True`` only if this type hint is equal to that other hint. ''' # If that object is *NOT* a type hint wrapper, defer to either: # * If the class of that object defines a similar __eq__() method # supporting the "TypeHint" API, that method. # * Else, Python's builtin C-based fallback equality comparator that # merely compares whether two objects are identical (i.e., share the # same object ID). if not isinstance(other, TypeHint): return NotImplemented # Else, that object is also a type hint wrapper. # Defer to the subclass-specific implementation of this test. return self._is_equal(other) def __ne__(self, other: object) -> bool: return not (self == other) # ..................{ DUNDERS ~ compare : rich }.................. def __le__(self, other: object) -> bool: ''' :data:`True` if this hint is a subhint of the passed hint. ''' if not isinstance(other, TypeHint): return NotImplemented return self.is_subhint(other) def __lt__(self, other: object) -> bool: ''' :data:`True` if this hint is a strict subhint of the passed hint. ''' if not isinstance(other, TypeHint): return NotImplemented return self.is_subhint(other) and self != other def __ge__(self, other: object) -> bool: ''' :data:`True` if this hint is a superhint of the passed hint. ''' if not isinstance(other, TypeHint): return NotImplemented return self.is_superhint(other) def __gt__(self, other: object) -> bool: ''' :data:`True` if this hint is a strict superhint of the passed hint. ''' if not isinstance(other, TypeHint): return NotImplemented return self.is_superhint(other) and self != other # ..................{ DUNDERS ~ iterable }.................. def __contains__(self, hint_child: 'TypeHint') -> bool: ''' :data:`True` only if the low-level type hint wrapped by the passed **type hint wrapper** (i.e., :class:`TypeHint` instance) is a child type hint originally subscripting the low-level parent type hint wrapped by this :class:`TypeHint` instance. ''' # Sgt. Pepper's One-liners GitHub Club Band. return hint_child in self._args_wrapped_frozenset def __iter__(self) -> Iterable['TypeHint']: ''' Generator iteratively yielding all **children type hint wrappers** (i.e., :class:`TypeHint` instances wrapping all low-level child type hints originally subscripting the low-level parent type hint wrapped by this :class:`TypeHint` instance). Defining this method satisfies the :class:`collections.abc.Iterable` abstract base class (ABC). ''' # For those who are about to one-liner, we salute you. yield from self._args_wrapped_tuple # ..................{ DUNDERS ~ iterable : item }.................. # Inform static type-checkers of the one-to-one correspondence between the # type of the object subscripting an instance of this class with the type of # the object returned by that subscription. Note this constraint is strongly # inspired by this erudite StackOverflow answer: # https://stackoverflow.com/a/71183076/2809027 @overload def __getitem__(self, index: int) -> 'TypeHint': ... @overload def __getitem__(self, index: slice) -> Tuple['TypeHint', ...]: ... # Note that the actual implementation of this overload is intentionally: # * *NOT* decorated by the standard @overload decorator. # * *NOT* annotated by type hints. By PEP 484, only the signatures of # @overload-decorated callables are annotated by type hints. def __getitem__(self, index): ''' Either: * If the passed object is an integer, then this type hint wrapper was subscripted by either a positive 0-based absolute index or a negative -1-based relative index. In either case, this dunder method returns the **child type hint wrapper** (i.e., :class:`TypeHint` instance wrapping a low-level child type hint originally subscripting the low-level parent type hint wrapped by this :class:`TypeHint` instance) with the same index. * If the passed object is a slice, then this type hint wrapper was subscripted by a range of such indices. In this case, this dunder method returns a tuple of the zero or more child type hint wrappers with the same indices. Parameters ---------- index : Union[int, slice] Either: * Positive 0-based absolute index or negative -1-based relative index of the child type hint originally subscripting the parent type hint wrapped by this :class:`TypeHint` instance to be returned wrapped by a new :class:`TypeHint` instance. * Slice of such indices of the zero or more child type hints originally subscripting the parent type hint wrapped by this :class:`TypeHint` instance to be returned in a tuple of these child type hints wrapped by new :class:`TypeHint` instances. Returns ------- Union['TypeHint', Tuple['TypeHint', ...]] Child type hint wrapper(s) at these ind(ex|ices), as detailed above. ''' # Defer validation of the correctness of the passed index or slice to # the low-level tuple.__getitem__() dunder method. Though we could (and # possibly should) perform that validation here, doing so is non-trivial # in the case of both a negative relative index *AND* a passed slice. # This trivial approach suffices for now. return self._args_wrapped_tuple[index] # ..................{ DUNDERS ~ iterable : sized }.................. #FIXME: Unit test us up, please. def __bool__(self) -> bool: ''' :data:`True` only if the low-level parent type hint wrapped by this wrapper was subscripted by at least one child type hint. ''' # See __len__() for further commentary. return bool(self._args_wrapped_tuple) #FIXME: Unit test us up, please. def __len__(self) -> int: ''' Number of low-level child type hints subscripting the low-level parent type hint wrapped by this wrapper. Defining this method satisfies the :class:`collections.abc.Sized` abstract base class (ABC). ''' # Return the exact length of the same iterable returned by the # __iter__() dunder method rather than the possibly differing length of # the "self._args" tuple, for safety. Theoretically, these two iterables # should exactly coincide in length. Pragmatically, it's best to assume # nothing in the murky waters we swim in. return len(self._args_wrapped_tuple) # ..................{ PROPERTIES ~ read-only }.................. # Read-only properties intentionally defining *NO* corresponding setter. #FIXME: Unit test us up, please. @property def args(self) -> tuple: ''' Tuple of the zero or more low-level child type hints subscripting (indexing) the low-level parent type hint wrapped by this wrapper. ''' # Who could argue with a working one-liner? Not you. Surely, not you. return self._args @property def hint(self) -> T: ''' **Original type hint** (i.e., low-level PEP-compliant type hint wrapped by this wrapper at :meth:`TypeHint.__init__` instantiation time). ''' # Q: Can one-liners solve all possible problems? A: Yes. return self._hint @property def is_ignorable(self) -> bool: ''' :data:`True` only if this type hint is **ignorable** (i.e., conveys *no* meaningful semantics despite superficially appearing to do so). While one might expect the set of all ignorable type hints to be both finite and small, this set is actually **countably infinite** in size. Countably infinitely many type hints are ignorable. This includes: * :attr:`typing.Any`, by design. * :class:`object`, the root superclass of all types. Ergo, parameters and return values annotated as :class:`object` unconditionally match *all* objects under :func:`isinstance`-based type covariance and thus semantically reduce to unannotated parameters and return values. * The unsubscripted :attr:`typing.Optional` singleton, which semantically expands to the implicit ``Optional[Any]`` type hint under :pep:`484`. Since :pep:`484` also stipulates that all ``Optional[t]`` type hints semantically expand to ``Union[t, type(None)]`` type hints for arbitrary arguments ``t``, ``Optional[Any]`` semantically expands to merely ``Union[Any, type(None)]``. Since all unions subscripted by :attr:`typing.Any` semantically reduce to merely :attr:`typing.Any`, the unsubscripted :attr:`typing.Optional` singleton also reduces to merely :attr:`typing.Any`. This intentionally excludes the ``Optional[type(None)]`` type hint, which the :mod:`typing` module reduces to merely ``type(None)``. * The unsubscripted :attr:`typing.Union` singleton, which semantically reduces to :attr:`typing.Any` by the same argument. * Any subscription of :attr:`typing.Union` by one or more ignorable type hints. There exists a countably infinite number of such subscriptions, many of which are non-trivial to find by manual inspection. The ignorability of a union is a transitive property propagated "virally" from child to parent type hints. Consider: * ``Union[Any, bool, str]``. Since :attr:`typing.Any` is ignorable, this hint is trivially ignorable by manual inspection. * ``Union[str, List[int], NewType('MetaType', Annotated[object, 53])]``. Although several child type hints of this union are non-ignorable, the deeply nested :class:`object` child type hint is ignorable by the argument above. It transitively follows that the ``Annotated[object, 53]`` parent type hint subscripted by :class:`object`, the :obj:`typing.NewType` parent type hint aliased to ``Annotated[object, 53]``, *and* the entire union subscripted by that :obj:`typing.NewType` are themselves all ignorable as well. * Any subscription of :attr:`typing.Annotated` by one or more ignorable type hints. As with :attr:`typing.Union`, there exists a countably infinite number of such subscriptions. (See the prior item.) * The :class:`typing.Generic` and :class:`typing.Protocol` superclasses, both of which impose no constraints *in and of themselves.* Since all possible objects satisfy both superclasses. Both superclasses are synonymous to the ignorable :class:`object` root superclass: e.g., .. code-block:: pycon >>> from typing as Protocol >>> isinstance(object(), Protocol) True >>> isinstance('wtfbro', Protocol) True >>> isinstance(0x696969, Protocol) True * Any subscription of either the :class:`typing.Generic` or :class:`typing.Protocol` superclasses, regardless of whether the child type hints subscripting those superclasses are ignorable or not. Subscripting a type that conveys no meaningful semantics continues to convey no meaningful semantics. For example, the type hints ``typing.Generic[typing.Any]`` and ``typing.Generic[str]`` are both equally ignorable – despite the :class:`str` class being otherwise unignorable in most type hinting contexts. * And frankly many more. And... *now we know why this tester exists.* This property is memoized for efficiency. Returns ------- bool :data:`True` only if this type hint is ignorable. ''' # Mechanic: Somebody set up us the bomb. return is_hint_ignorable(self._hint) # ..................{ CHECKERS }.................. def die_if_unbearable( self, # Mandatory flexible parameters. obj: object, # Optional keyword-only parameters. *, conf: BeartypeConf = BEARTYPE_CONF_DEFAULT, exception_prefix: str = 'die_if_unbearable() ', ) -> None: ''' Raise an exception if the passed arbitrary object violates this type hint under the passed beartype configuration. To configure the type of violation exception raised by this method, set the :attr:`.BeartypeConf.violation_door_type` option of the passed ``conf`` parameter accordingly. Parameters ---------- obj : object Arbitrary object to be tested against this hint. conf : BeartypeConf, optional **Beartype configuration** (i.e., self-caching dataclass encapsulating all settings configuring type-checking for the passed object). Defaults to ``BeartypeConf()``, the default ``O(1)`` constant-time configuration. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to a reasonably sensible string. Raises ------ ``conf.violation_door_type`` If this object violates this hint. beartype.roar.BeartypeDecorHintNonpepException If this hint is *not* PEP-compliant (i.e., complies with *no* Python Enhancement Proposals (PEPs) currently supported by :mod:`beartype`). beartype.roar.BeartypeDecorHintPepUnsupportedException If this hint is currently unsupported by :mod:`beartype`. Examples -------- .. code-block:: pycon >>> from beartype.door import TypeHint >>> TypeHint(list[str]).die_if_unbearable( ... ['And', 'what', 'rough', 'beast,'], ) >>> TypeHint(list[str]).die_if_unbearable( ... ['its', 'hour', 'come', 'round'], list[int]) beartype.roar.BeartypeDoorHintViolation: Object ['its', 'hour', 'come', 'round'] violates type hint list[int], as list index 0 item 'its' not instance of int. ''' # One-liner, one love, one heart. Let's get together and code alright. die_if_unbearable( obj=obj, hint=self._hint, conf=conf, exception_prefix=exception_prefix, ) def is_bearable( self, # Mandatory flexible parameters. obj: object, # Optional keyword-only parameters. *, conf: BeartypeConf = BEARTYPE_CONF_DEFAULT, ) -> bool: ''' :data:`True` only if the passed arbitrary object satisfies this type hint under the passed beartype configuration. Parameters ---------- obj : object Arbitrary object to be tested against this hint. conf : BeartypeConf, optional **Beartype configuration** (i.e., self-caching dataclass encapsulating all settings configuring type-checking for the passed object). Defaults to ``BeartypeConf()``, the default constant-time configuration. Returns ------- bool :data:`True` only if this object satisfies this hint. Raises ------ beartype.roar.BeartypeDecorHintForwardRefException If this hint contains one or more relative forward references, which this tester explicitly prohibits to improve both the efficiency and portability of calls to this tester. Examples -------- .. code-block:: python >>> from beartype.door import TypeHint >>> TypeHint(list[str]).is_bearable(['Things', 'fall', 'apart;']) True >>> TypeHint(list[int]).is_bearable( ... ['the', 'centre', 'cannot', 'hold;']) False ''' # One-liners justify their own existence. return is_bearable(obj=obj, hint=self._hint, conf=conf) # ..................{ TESTERS ~ subhint }.................. # Note that the @method_cached_arg_by_id rather than @callable_cached # decorator is *ABSOLUTELY* required here. Why? Because the @callable_cached # decorator internally caches the passed "other" argument as the key of a # dictionary. Subsequent calls to this method when passed the same argument # lookup that "other" in that dictionary. Since dictionary lookups # implicitly call other.__eq__() to resolve key collisions *AND* since the # TypeHint.__eq__() method calls TypeHint.is_subhint(), infinite recursion! @method_cached_arg_by_id def is_subhint(self, other: 'TypeHint') -> bool: ''' :data:`True` only if this type hint is a **subhint** of the passed type hint. This tester method is memoized for efficiency. Parameters ---------- other : TypeHint Other type hint to be tested against this type hint. Returns ------- bool :data:`True` only if this type hint is a subhint of that other hint. See Also -------- :func:`beartype.door.is_subhint` Further details. ''' # If the passed object is *NOT* a type hint wrapper, raise an exception. die_unless_typehint(other) # Else, that object is a type hint wrapper. # Return true only if either... return ( # That other hint is the "typing.Any" catch-all. By definition, that # hint is the superhint of *ALL* hints -- including "typing.Any" # itself. Ergo, this hint is necessarily a subhint of that hint. other._hint is Any or # Else, that other hint is *NOT* the "typing.Any" catch-all. In this # case, defer to the subclass-specific implementation of this test. self._is_subhint(other) ) def is_superhint(self, other: 'TypeHint') -> bool: ''' :data:`True` only if this type hint is a **superhint** of the passed type hint. This tester method is memoized for efficiency. Parameters ---------- other : TypeHint Other type hint to be tested against this type hint. Returns ------- bool :data:`True` only if this type hint is a superhint of that other hint. See Also -------- :func:`beartype.door.is_subhint` Further details. ''' # If the passed object is *NOT* a type hint wrapper, raise an exception. die_unless_typehint(other) # Else, that object is a type hint wrapper. # Return true only if this hint is a superhint of the passed hint. return other.is_subhint(self) # ..................{ PRIVATE }.................. # Subclasses are encouraged to override these concrete methods defaulting to # general-purpose implementations suitable for most subclasses. # ..................{ PRIVATE ~ factories }.................. def _make_args(self) -> tuple: ''' Tuple of the zero or more low-level child type hints subscripting (indexing) the low-level parent type hint wrapped by this wrapper, which the :meth:`TypeHint.__init__` method assigns to the :attr:`_args` instance variable of this wrapper. Subclasses are advised to override this method to set the :attr:`_args` instance variable of this wrapper in a subclass-specific manner. ''' # We are the one-liner. We are the codebase. return get_hint_pep_args(self._hint) # ..................{ PRIVATE ~ testers }.................. def _is_equal(self, other: 'TypeHint') -> bool: ''' :data:`True` only if the low-level type hint wrapped by this wrapper is semantically equivalent to the other low-level type hint wrapped by the passed wrapper. Subclasses are advised to override this method to implement the public :meth:`is_subhint` tester method (which internally defers to this private tester method) in a subclass-specific manner. Since the default implementation is guaranteed to suffice for *all* possible use cases, subclasses should override this method only for efficiency reasons; the default implementation calls the :meth:`is_subhint` method twice and is thus *not* necessarily the optimal implementation for subclasses. Notably, the default implementation exploits the well-known syllogism between two partially ordered items ``A`` and ``B``: * If ``A <= B`` and ``A >= B``, then ``A == B``. This private tester method is *not* memoized for efficiency, as the caller is guaranteed to be the public :meth:`__eq__` tester method, which is already memoized. Parameters ---------- other : TypeHint Other type hint to be tested against this type hint. Returns ------- bool :data:`True` only if this type hint is equal to that other hint. ''' # Return true only if both... # # Note that this conditional implements the trivial boolean syllogism # that we all know and adore: "If A <= B and B <= A, then A == B". return ( # This union is a subhint of that object. self.is_subhint(other) and # That object is a subhint of this union. other.is_subhint(self) ) # ..................{ PRIVATE ~ testers : subhint }.................. def _is_subhint(self, other: 'TypeHint') -> bool: ''' :data:`True` only if this type hint is a **subhint** of the passed type hint. Subclasses are advised to override this method to implement the public :meth:`is_subhint` tester method (which internally defers to this private tester method) in a subclass-specific manner. This private tester method is *not* memoized for efficiency, as the caller is guaranteed to be the public :meth:`is_subhint` tester method, which is already memoized. Parameters ---------- other : TypeHint Other type hint to be tested against this type hint. Returns ------- bool :data:`True` only if this type hint is a subhint of that other hint. See Also -------- :func:`beartype.door.is_subhint` Further details. ''' # Return true only if this hint is a subhint of *ANY* branch of that # other hint. return any( self._is_subhint_branch(other_branch) for other_branch in other._branches ) def _is_subhint_branch(self, branch: 'TypeHint') -> bool: ''' :data:`True` only if this type hint is a subhint of the passed branch of another type hint passed to a parent call of the :meth:`is_subhint` method, itself called by the :meth:`__le__` dunder method. Parameters ---------- branch : TypeHint Conditional branch of another type hint to be tested against. See Also -------- :meth:`__le__` Further details. ''' # If that branch is unsubscripted, assume that branch to have been # subscripted by "Any" and simply check for compatible origin types. if branch._is_args_ignorable: # print(f'is_subhint_branch({self}, {branch} [unsubscripted])') return issubclass(self._origin, branch._origin) # Else, that branch is subscripted. # Return true only if... return ( # That branch is also a type hint wrapper of the same concrete # subclass as this type hint wrapper *AND*... isinstance(branch, type(self)) and # The class originating this hint is a subclass of the class # originating that branch... issubclass(self._origin, branch._origin) and # All child type hints of this parent type hint are subhints of the # corresponding child type hints of that branch. all( self_child <= branch_child for self_child, branch_child in zip( self._args_wrapped_tuple, branch._args_wrapped_tuple) ) ) # ..................{ PRIVATE ~ properties : read-only }.................. # Read-only properties intentionally defining *NO* corresponding setter. @property # type: ignore @property_cached def _args_wrapped_tuple(self) -> Tuple['TypeHint', ...]: ''' Tuple of the zero or more high-level **child type hint wrappers** (i.e., :class:`TypeHint` instances) wrapping the low-level child type hints subscripting (indexing) the low-level parent type hint wrapped by this wrapper. This attribute is intentionally defined as a memoized property to minimize space and time consumption for use cases *not* accessing this attribute. ''' # One-liner, don't fail us now! return tuple(TypeHint(hint_child) for hint_child in self._args) @property # type: ignore @property_cached def _args_wrapped_frozenset(self) -> FrozenSet['TypeHint']: ''' Frozen set of the zero or more high-level child **type hint wrappers** (i.e., :class:`TypeHint` instances) wrapping the low-level child type hints subscripting (indexing) the low-level parent type hint wrapped by this wrapper. This attribute is intentionally defined as a memoized property to minimize space and time consumption for use cases *not* accessing this attribute. ''' return frozenset(self._args_wrapped_tuple) @property # type: ignore @property_cached def _branches(self) -> Iterable['TypeHint']: ''' Immutable iterable of all **branches** (i.e., high-level type hint wrappers encapsulating all low-level child type hints subscripting (indexing) the low-level parent type hint encapsulated by this high-level parent type hint wrappers if this is a union (and thus an instance of the :class:`UnionTypeHint` subclass) *or* the 1-tuple containing only this instance itself otherwise) of this type hint wrapper. This property enables the child type hints of both :pep:`484`- and :pep:`604`-compliant unions (e.g., :attr:`typing.Union`, :attr:`typing.Optional`, and ``|``-delimited type objects) to be handled transparently *without* special cases in subclass implementations. ''' # Default to returning the 1-tuple containing only this instance, as # *ALL* subclasses except "_HintTypeUnion" require this default. return (self,) @property # type: ignore @property_cached def _is_args_ignorable(self) -> bool: ''' :data:`True` only if this hint is effectively **unsubscripted** (i.e., either indexed by *no* child type hints or only indexed by ignorable child type hints). If :data:`True`, this hint can be trivially and efficiently evaluated by simply inspecting its :attr:`_origin` property. Relevant type hints include: * Unsubscripted type hint factories (e.g., ``Tuple``, ``Callable``). * Type hints subscripted only by ignorable child type hints (e.g., ``Tuple[Any, ...]``, ``Callable[..., Any]``). This boolean trivializes comparisons between syntactically unrelated type hints that are nonetheless semantically equivalent: e.g., .. code-block:: pycon >>> from beartype.door import TypeHint >>> from typing import Any, Tuple # These type hints are all semantically equivalent despite being # mostly syntactically unrelated. >>> TypeHint(tuple) == TypeHint(typing.Tuple) == \ ... TypeHint(typing.Tuple[Any, ...]) True Note that this property is *not* equivalent to the :meth:`is_ignorable` property. Although related, a non-ignorable parent type hint can trivially have ignorable child type hints (e.g., ``list[Any]``). ''' # Return true only if all child type hints subscripting this parent type # hint are themselves ignorable. # print(f'[_is_args_ignorable] {self}._args_wrapped_tuple: {self._args_wrapped_tuple}') return all( hint_child.is_ignorable for hint_child in self._args_wrapped_tuple) beartype-0.18.5/beartype/door/_cls/pep/000077500000000000000000000000001461113517100177025ustar00rootroot00000000000000beartype-0.18.5/beartype/door/_cls/pep/__init__.py000066400000000000000000000000001461113517100220010ustar00rootroot00000000000000beartype-0.18.5/beartype/door/_cls/pep/doorpep484604.py000066400000000000000000000073511461113517100224240ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Decidedly Object-Oriented Runtime-checking (DOOR) union type hint classes** (i.e., :class:`beartype.door.TypeHint` subclasses implementing support for :pep:`484`-compliant :attr:`typing.Optional` and :attr:`typing.Union` type hints and :pep:`604`-compliant ``|``-delimited union type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.door._cls.doorsub import _TypeHintSubscripted from beartype.door._cls.doorsuper import TypeHint from beartype.typing import Iterable # ....................{ SUBCLASSES }.................... class UnionTypeHint(_TypeHintSubscripted): ''' **Union type hint wrapper** (i.e., high-level object encapsulating a low-level :pep:`484`-compliant :attr:`typing.Optional` or :attr:`typing.Union` type hint *or* :pep:`604`-compliant ``|``-delimited union type hint). ''' # ..................{ PRIVATE ~ properties }.................. @property def _branches(self) -> Iterable[TypeHint]: return self._args_wrapped_tuple # ..................{ PRIVATE ~ testers }.................. def _is_subhint_branch(self, branch: TypeHint) -> bool: raise NotImplementedError('UnionTypeHint._is_subhint_branch() unsupported.') # pragma: no cover def _is_subhint(self, other: TypeHint) -> bool: # Return true only if *EVERY* child type hint of this union is a subhint # of at least one other child type hint of the passed other union. # # Note that this test has O(n**2) time complexity. Although non-ideal, # this is also unavoidable. Thankfully, since most real-world unions are # subscripted by only a small number of child type hints, this is also # mostly ignorable in practice. return all( # For each child type hint subscripting this union... ( # If that other type hint is itself a union, true only if... any( # For at least one other child type hint subscripting that # other union, this child type hint is a subhint of that # other child type hint. this_branch.is_subhint(that_branch) for that_branch in other._branches ) if isinstance(other, UnionTypeHint) else # Else, that other type hint is *NOT* a union. In this case, # true only if this child type hint is a subhint of that other # type hint. # # Note that this is a common edge case. Examples include: # * "TypeHint(Union[...]) <= TypeHint(Any)". Although "Any" is # *NOT* a union, *ALL* unions are subhints of "Any". # * "TypeHint(Union[A, B]) <= TypeHint(Union[A])" where "A" is # the superclass of "B". Since Python reduces "Union[A]" to # just "A", this is exactly equivalent to the comparison # "TypeHint(Union[A, B]) <= TypeHint(A)". Although "A" is # *NOT* a union, this example clearly demonstrates that a # union may be a subhint of a non-union that is *NOT* "Any" -- # contrary to intuition. Examples include: # * "TypeHint(Union[int, bool]) <= TypeHint(Union[int])". this_branch.is_subhint(other) ) for this_branch in self._branches ) beartype-0.18.5/beartype/door/_cls/pep/doorpep586.py000066400000000000000000000076301461113517100221750ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **Decidedly Object-Oriented Runtime-checking (DOOR) literal type hint classes** (i.e., :class:`beartype.door.TypeHint` subclasses implementing support for :pep:`586`-compliant :attr:`typing.Literal` type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.door._cls.doorsub import _TypeHintSubscripted from beartype.door._cls.doorsuper import TypeHint from beartype.typing import Tuple # ....................{ SUBCLASSES }.................... class LiteralTypeHint(_TypeHintSubscripted): ''' **Literal type hint wrapper** (i.e., high-level object encapsulating a low-level :pep:`586`-compliant :attr:`typing.Literal` type hint). ''' # ..................{ PRIVATE ~ properties }.................. @property def _args_wrapped_tuple(self) -> Tuple[TypeHint, ...]: # Return the empty tuple, thus presenting "Literal" type hints as having # *NO* child type hints. Why? Because the arguments subscripting a # Literal type hint are *NOT* generally PEP-compliant type hints and # thus *CANNOT* be safely wrapped by "TypeHint" instances. These # arguments are merely arbitrary values. # # Note that this property getter is intentionally *NOT* memoized with # @property_cached, as Python already efficiently guarantees the empty # tuple to be a singleton. return () @property def _is_args_ignorable(self) -> bool: return False # ..................{ PRIVATE ~ testers }.................. def _is_subhint(self, other: TypeHint) -> bool: # If the passed hint is also a literal, return true only if the set of # all child hints subscripting this literal is a subset of the set of # all child hints subscripting that literal. if isinstance(other, LiteralTypeHint): return all(self_arg in other._args for self_arg in self._args) # Else, the passed hint is *NOT* also a literal. # Return true only if either... return ( # The class of each child hint subscripting this literal is a # subhint (e.g., subclass) of the passed hint *OR*... # # Note that, unlike most type hints, each child hints subscripting # this literal is typically *NOT* a valid type hint in and of itself # (e.g., "Literal[True]" is a valid type hint, but "True" is not). # This test *CANNOT* be reduced to the simpler and sensible variant: # return all( # hint_child.is_subhint(other) # for hint_child in self._args_wrapped_tuple # ) all( TypeHint(type(literal_child)).is_subhint(other) for literal_child in self._args ) or # Else, the class of one or more child hints subscripting this # literal is *NOT* a subhint (e.g., subclass) of the passed hint. # # Defer to the superclass implementation of this method. Why? # Because this literal could still be a subhint of passed hint # according to standard typing semantics. Notably, this literal # could be a child type hint and thus a subhint of the passed type # hint - despite failing all of the above literal-specific subhint # tests: e.g., # # The call below handles this surprisingly common edge case. # >>> Literal[True] <= Union[Literal[True], Literal[False]] # True super()._is_subhint(other) ) beartype-0.18.5/beartype/door/_cls/pep/doorpep593.py000066400000000000000000000111771461113517100221740ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Decidedly Object-Oriented Runtime-checking (DOOR) annotated type hint classes** (i.e., :class:`beartype.door.TypeHint` subclasses implementing support for :pep:`593`-compliant :attr:`typing.Annotated` type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.door._cls.doorsuper import TypeHint from beartype._util.cache.utilcachecall import callable_cached from beartype._util.hint.pep.proposal.utilpep593 import ( get_hint_pep593_metadata, get_hint_pep593_metahint, ) from contextlib import suppress # ....................{ SUBCLASSES }.................... class AnnotatedTypeHint(TypeHint): ''' **Annotated type hint wrapper** (i.e., high-level object encapsulating a low-level :pep:`593`-compliant :attr:`typing.Annotated` type hint). Attributes (Private) -------- _metadata : tuple[object] **Metadata** (i.e., tuple of zero or more arbitrary low-level caller-defined objects annotating this :attr:`typing.Annotated` type hint, equivalent to all remaining arguments subscripting this hint). _metahint_wrapper : TypeHint **Metahint wrapper** (i.e., :class:`TypeHint` instance wrapping the child type hint annotated by this parent :attr:`typing.Annotated` type hint, equivalent to the first argument subscripting this hint). ''' # ..................{ INITIALIZERS }.................. def __init__(self, hint: object) -> None: # Initialize our superclass. super().__init__(hint) # Tuple of the zero or more arbitrary caller-defined arguments following # the first argument subscripting this hint. self._metadata = get_hint_pep593_metadata(hint) # Wrapper wrapping the first argument subscripting this hint. self._metahint_wrapper = TypeHint(get_hint_pep593_metahint(hint)) # ..................{ PRIVATE ~ properties }.................. @property def _is_args_ignorable(self) -> bool: # since Annotated[] must be used with at least two arguments, we are # never just the origin of the metahint return False # ..................{ PRIVATE ~ testers }.................. def _is_equal(self, other: TypeHint) -> bool: return ( isinstance(other, AnnotatedTypeHint) and self._metahint_wrapper == other._metahint_wrapper and self._metadata == other._metadata ) def _is_subhint_branch(self, branch: TypeHint) -> bool: # If the other type is not annotated, we ignore annotations on this # one and just check that the metahint is a subhint of the other. # e.g. Annotated[t.List[int], 'meta'] <= List[int] if not isinstance(branch, AnnotatedTypeHint): return self._metahint_wrapper.is_subhint(branch) # Else, that hint is a "typing.Annotated[...]" type hint. If either... if ( # The child type hint annotated by this parent hint does not subhint # the child type hint annotated by that parent hint *OR*... self._metahint_wrapper > branch._metahint_wrapper or # These hints are annotated by a differing number of objects... len(self._metadata) != len(branch._metadata) ): # This hint *CANNOT* be a subhint of that hint. Return false. return False # Attempt to... # # Note that the following iteration performs equality comparisons on # arbitrary caller-defined objects. Since these comparisons may raise # arbitrary caller-defined exceptions, we silently squelch any such # exceptions that arise by returning false below instead. with suppress(Exception): # Return true only if these hints are annotated by equivalent # objects. We avoid testing for a subhint relation here (e.g., with # the "<=" operator), as arbitrary caller-defined objects are *MUCH* # more likely to define a relevant equality comparison than a # relevant less-than-or-equal-to comparison. return self._metadata == branch._metadata # Else, one or more objects annotating these hints are incomparable. So, # this hint *CANNOT* be a subhint of that hint. Return false. return False # pragma: no cover beartype-0.18.5/beartype/door/_cls/pep/pep484/000077500000000000000000000000001461113517100207265ustar00rootroot00000000000000beartype-0.18.5/beartype/door/_cls/pep/pep484/__init__.py000066400000000000000000000000001461113517100230250ustar00rootroot00000000000000beartype-0.18.5/beartype/door/_cls/pep/pep484/doorpep484class.py000066400000000000000000000202441461113517100242400ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Decidedly Object-Oriented Runtime-checking (DOOR) class type hint classes** (i.e., :class:`beartype.door.TypeHint` subclasses implementing support for :pep:`484`-compliant type hints that are, in fact, simple classes). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.door._cls.doorsuper import TypeHint from beartype.typing import ( TYPE_CHECKING, Any, ) # ....................{ SUBCLASSES }.................... class ClassTypeHint(TypeHint): ''' **Class type hint wrapper** (i.e., high-level object encapsulating a low-level :pep:`484`-compliant type hint that is, in fact, a simple class). Caveats ---------- This wrapper also intentionally wraps :pep:`484`-compliant :data:``None` type hints as the simple type of the :data:``None` singleton, as :pep:`484` standardized the reduction of the former to the latter: When used in a type hint, the expression None is considered equivalent to type(None). Although a unique ``NoneTypeHint`` subclass of this class specific to the :data:`None` singleton *could* be declared, doing so is substantially complicated by the fact that numerous PEP-compliant type hints internally elide :data:``None` to the type of that singleton before the `beartype.door` API ever sees a distinction. Notably, this includes :pep:`484`-compliant unions subscripted by that singleton: e.g., .. code-block:: python >>> from typing import Union >>> Union[str, None].__args__ (str, NoneType) ''' # ..................{ STATIC }.................. # Squelch false negatives from static type checkers. if TYPE_CHECKING: _hint: type # ..................{ PRIVATE ~ properties }.................. @property def _is_args_ignorable(self) -> bool: # Unconditionally return true, as simple classes are unsubscripted and # could thus be said to only have ignorable arguments. Look. Semantics. return True # ..................{ PRIVATE ~ methods }.................. def _is_subhint_branch(self, branch: TypeHint) -> bool: # print(f'is_subhint({repr(self)}, {repr(branch)})?') # print(f'{repr(self)}._origin: {self._origin}') # # print(f'{repr(self)}._origin.__args__: {self._origin.__args__}') # print(f'{repr(self)}._origin.__parameters__: {self._origin.__parameters__}') # print(f'{repr(branch)}._origin: {branch._origin}') # # print(f'{repr(branch)}._origin.__args__: {branch._origin.__args__}') # print(f'{repr(branch)}._origin.__parameters__: {branch._origin.__parameters__}') # print(f'{repr(self)}._is_args_ignorable: {self._is_args_ignorable}') # print(f'{repr(branch)}._is_args_ignorable: {branch._is_args_ignorable}') #FIXME: *UGH.* This is redundant. Ideally: #* There should exist a concrete TypeHint._is_subhint_branch() # implementation performing this logic on behalf of *EVERY* subclass. #* TypeHint._is_subhint_branch() should then call a subclass-specific # abstract TypeHint._is_subhint_branch_override() method. #FIXME: Actually, TypeHint._is_subhint_branch() is only called in #exactly one place: by TypeHint._is_subhint(). So, the simpler solution #would be to simply implement the following tests there, please. # Everything is a subclass of "Any". if branch._hint is Any: return True #FIXME: *UHM.* Wat? Do we really currently wrap "typing.Any" with an #instance of this class? Why? That makes *NO* sense. "typing.Any" should #be wrapped by its own "TypeHintAny" subclass, please. *sigh* # "Any" is only a subclass of "Any". elif self._hint is Any: return False #FIXME: Actually, let's avoid the implicit numeric tower for now. #Explicit is better than implicit and we really strongly disagree with #this subsection of PEP 484, which does more real-world harm than good. # # Numeric tower: # # https://peps.python.org/pep-0484/#the-numeric-tower # if self._origin is float and branch._origin in {float, int}: # return True # if self._origin is complex and branch._origin in {complex, float, int}: # return True #FIXME: This simplistic logic fails to account for parametrized #generics. To do so, we'll probably want to: #* Define a new "beartype.door._cls._pep.pep484585.doorpep484585generic" # submodule. #* In that submodule: # * Define a new "GenericTypeHint" subclass initially simply # copy-pasted from this subclass. #* Incorporate that subclass into the "beartype.door._doordata" # submodule. #* Validate that tests still pass. #* Begin implementing custom generic-specific logic in the # "GenericTypeHint" subclass. Notably, this tester should be refactored # as follows: # # If this generic is *NOT* a subclass of that generic, then this generic # # is *NOT* a subhint of that generic. In this case, return false. # if not issubclass(self._hint, branch._hint): # return False # # Else, this generic is a subclass of that generic. Note, however, # # that this does *NOT* imply this generic to be a subhint of that # # generic. The issubclass() builtin ignores parametrizations and thus # # returns false positives for parametrized generics: e.g., # # >>> from typing import Generic, TypeVar # # >>> T = TypeVar('T') # # >>> class MuhGeneric(Generic[T]): pass # # >>> issubclass(MuhGeneric, MuhGeneric[int]) # # True # # # # Clearly, the unsubscripted generic "MuhGeneric" is a superhint # # (rather than a subhint) of the subscripted generic # # "MuhGeneric[int]". Further introspection is needed to decide how # # exactly these two generics interrelate. # # #FIXME: Do something intelligent here. In particular, we probably # #*MUST* expand unsubscripted generics like "MuhGeneric" to their # #full transitive subscriptions like "MuhGeneric[T]". Of course, # #"MuhGeneric" has *NO* "__args__" and only an empty "__parameters__"; # #both are useless. Ergo, we have *NO* recourse but to iteratively # #reconstruct the full transitive subscriptions for unsubscripted # #generics by iterating with the # #iter_hint_pep484585_generic_bases_unerased_tree() iterator. The idea # #here is that we want to iteratively inspect first the "__args__" and # #then the "__parameters__" of all superclasses of both "self" and # #"branch" until obtaining two n-tuples (where "n" is the number of # #type variables with which the root "Generic[...]" superclass was # #originally subscripted): # #* "self_args", the n-tuple of all types or type variables # # subscripting this generic. # #* "branch_args", the n-tuple of all types or type variables # # subscripting the "branch" generic. # # # #Once we have those two n-tuples, we can then decide the is_subhint() # #relation by simply iteratively subjecting each pair of items from # #both "self_args" and "branch_args" to is_subhint(). Notably, we # #return True if and only if is_subhint() returns True for *ALL* pairs # #of items of these two n-tuples. # Return true only if... return ( # This class is unsubscripted (and thus *NOT* a subscripted generic) # *AND*... branch._is_args_ignorable and # This class is a subclass of that class. issubclass(self._origin, branch._origin) ) beartype-0.18.5/beartype/door/_cls/pep/pep484/doorpep484newtype.py000066400000000000000000000065651461113517100246400ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Decidedly Object-Oriented Runtime-checking (DOOR) new-type type hint classes** (i.e., :class:`beartype.door.TypeHint` subclasses implementing support for :pep:`484`-compliant :attr:`typing.NewType` type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.door._cls.pep.pep484.doorpep484class import ClassTypeHint from beartype._util.cls.utilclsmake import make_type from beartype._util.hint.pep.proposal.pep484.utilpep484newtype import ( get_hint_pep484_newtype_alias) # ....................{ SUBCLASSES }.................... class NewTypeTypeHint(ClassTypeHint): ''' **New-type type hint wrapper** (i.e., high-level object encapsulating a low-level :pep:`484`-compliant :attr:`typing.NewType` type hint). ''' # ..................{ INITIALIZERS }.................. def __init__(self, hint: object) -> None: # Initialize the superclass with all passed parameters. super().__init__(hint) # Non-new type type hint encapsulated by this new type. hint_embedded = get_hint_pep484_newtype_alias(hint) # If this non-new type hint is a class... if isinstance(hint_embedded, type): #FIXME: Define a new get_hint_pep484_newtype_name() getter ala: # def get_hint_pep484_newtype_name( # hint: Any, exception_prefix: str = '') -> type: # #FIXME: Does this suffice? Does "NewType" guarantee the # #"__name__" instance variable to exist? No idea. *sigh* # return getattr(hint, '__name__') #Then, call that below in lieu of the "name = getattr(...)" call. # Unqualified basename of the new subclass of this class to be # created below. hint_name = getattr(hint, '__name__', str(hint)) # Dynamically synthesize a new subclass of this class with the name # of this new type, effectively fabricating a fake origin type # treating this new type as a subclass of this class. For example, # if this new type is "NewType("MyType", str)", then this logic # fabricates a fake origin type resembling: # class MyString(str): pass # # Note that this would typically be non-ideal due to explosive space # and time consumption. Thankfully, however, "TypeHint" wrappers are # cached; the "_TypeHintMeta" metaclass guarantees this __init__() # method to be called exactly once for each "NewType" type hint. self._origin = make_type( type_name=hint_name, type_bases=(hint_embedded,), # type: ignore[arg-type] ) # Else, this non-new type hint is a non-class (e.g., "Any"). In this # case, preserve this non-class as is. else: #FIXME: This can't be right. Isn't "self._origin" supposed to *ONLY* #be a class? Mypy complaints are probably justified here, frankly. self._origin = hint_embedded # type: ignore[assignment] beartype-0.18.5/beartype/door/_cls/pep/pep484/doorpep484typevar.py000066400000000000000000000120651461113517100246270ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **Decidedly Object-Oriented Runtime-checking (DOOR) type variable classes** (i.e., :class:`beartype.door.TypeHint` subclasses implementing support for :pep:`484`-compliant :attr:`typing.TypeVar` type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.door._cls.doorsuper import TypeHint from beartype.door._cls.pep.doorpep484604 import UnionTypeHint # from beartype.roar import BeartypeDoorPepUnsupportedException from beartype.typing import ( TYPE_CHECKING, Any, Tuple, TypeVar, ) from beartype._util.cache.utilcachecall import property_cached # ....................{ SUBCLASSES }.................... class TypeVarTypeHint(UnionTypeHint): ''' **Type variable wrapper** (i.e., high-level object encapsulating a low-level :pep:`484`-compliant :attr:`typing.TypeVar` type hint). ''' # ..................{ STATIC }.................. # Squelch false negatives from static type checkers. if TYPE_CHECKING: _hint: TypeVar # ..................{ PROPERTIES }.................. @property def is_ignorable(self) -> bool: # This type variable is ignorable only if this type variable is either: # * Unconstrained by any bounds or constraints (and thus effectively # bound only by the "typing.Any" catch-all). # * Bound by an ignorable bound: e.g., # TypeVar('T', bound=object) # * Constrained by one or more ignorable constraints. Since constraints # effectively build a union over those constraints, even a single # ignorable constraint suffices to render the entire type variable # ignorable: e.g., # TypeVar('T', object) return self._is_args_ignorable # ..................{ PRIVATE ~ properties }.................. #FIXME: *HMM.* We should arguably just define the _make_args() factory #method instead. That implementation would become quite a bit simpler as #well as generalize to cover more use cases. By defining this method, #"self._args" and "self._args_wrapped_tuple" are now desynchronized. *sigh* @property # type: ignore[misc] @property_cached def _args_wrapped_tuple(self) -> Tuple[TypeHint, ...]: #FIXME: Support covariance and contravariance, please. We don't #particularly care about either at the moment. Moreover, runtime type #checkers were *NEVER* expected to support either -- although we #eventually intend to do so. For now, raising a fatal exception here #would seem to be extreme overkill. Doing nothing is (probably) better #than doing something reckless and wild. # # Human-readable string describing the variance of this type variable if # # any *OR* "None" otherwise (i.e., if this type variable is invariant). # variance_str = None # if self._hint.__covariant__: # variance_str = 'covariant' # elif self._hint.__contravariant__: # variance_str = 'contravariant' # # # If this type variable is variant, raise an exception. # if variance_str: # raise BeartypeDoorPepUnsupportedException( # f'Type hint {repr(self._hint)} ' # f'variance "{variance_str}" currently unsupported.' # ) # # Else, this type variable is invariant. # TypeVars may only be bound or constrained, but not both. The # difference between the two has semantic meaning for static type # checkers but relatively little meaning for us. Ultimately, we're only # concerned with the set of compatible types present in either the bound # or the constraints. So, we treat a type variable as a union of its # constraints or bound. See also: # https://docs.python.org/3/library/typing.html#typing.TypeVar # If this type variable is bounded, return the 1-tuple containing only # this wrapped bound. if self._hint.__bound__ is not None: return (TypeHint(self._hint.__bound__),) # Else, this type variable is unbounded. # # If this type variable is constrained, return the n-tuple containing # each of these wrapped constraints. elif self._hint.__constraints__: return tuple(TypeHint(t) for t in self._hint.__constraints__) # Else, this type variable is unconstrained. #FIXME: Consider globalizing this as a private constant for efficiency. # Return the 1-tuple containing only the "typing.Any" catch-all. Why? # Because an unconstrained and unbounded type variable is semantically # equivalent to a type variable bounded by "typing.Any". return (TypeHint(Any),) beartype-0.18.5/beartype/door/_cls/pep/pep484585/000077500000000000000000000000001461113517100211705ustar00rootroot00000000000000beartype-0.18.5/beartype/door/_cls/pep/pep484585/__init__.py000066400000000000000000000000001461113517100232670ustar00rootroot00000000000000beartype-0.18.5/beartype/door/_cls/pep/pep484585/doorpep484585callable.py000066400000000000000000000314451461113517100254030ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **Decidedly Object-Oriented Runtime-checking (DOOR) callable type hint classes** (i.e., :class:`beartype.door.TypeHint` subclasses implementing support for :pep:`484`- and :pep:`585`-compliant ``Callable[...]`` type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.door._cls.doorsub import _TypeHintSubscripted from beartype.door._cls.doorsuper import ( TypeHint, # T, ) from beartype.roar import BeartypeDoorPepUnsupportedException from beartype.typing import ( Any, Tuple, ) from beartype._data.hint.pep.sign.datapepsignset import ( HINT_SIGNS_CALLABLE_PARAMS) # from beartype._data.kind.datakindsequence import TUPLE_EMPTY from beartype._util.cache.utilcachecall import property_cached from beartype._util.hint.pep.proposal.pep484585.utilpep484585callable import ( get_hint_pep484585_callable_params, get_hint_pep484585_callable_return, ) from beartype._util.hint.pep.utilpepget import get_hint_pep_sign_or_none # ....................{ SUBCLASSES }.................... class CallableTypeHint(_TypeHintSubscripted): ''' **Callable type hint wrapper** (i.e., high-level object encapsulating a low-level :pep:`484`- or :pep:`585`-compliant ``Callable[...]`` type hint). ''' # ..................{ INITIALIZERS }.................. def _make_args(self) -> tuple: # print(f'{self}._origin: {self._origin}') # Tuple of all child type hints subscripting this callable type hint, # localized for both readability and negligible efficiency gains. # # Note that this is a flattened tuple of the one or more child type # hints subscripting this callable type hint. Presumably for space # efficiency reasons, both PEP 484- *AND* 585-compliant callable type # hints implicitly flatten the "__args__" dunder tuple from the original # data structure subscripting those hints. CPython produces this # flattened tuple as the concatenation of: # # * Either: # * If the first child type originally subscripting this hint was a # list, all items subscripting the nested list of zero or more # parameter type hints originally subscripting this hint as is: # >>> Callable[[], bool].__args__ # (bool,) # >>> Callable[[int, str], bool].__args__ # (int, str, bool) # # This includes a list containing only the empty tuple signifying a # callable accepting *NO* parameters, in which case that empty tuple # is preserved as is: # >>> Callable[[()], bool].__args__ # ((), bool) # * Else, the first child type originally subscripting this hint as # is. In this case, that child type is required to be either: # * An ellipsis object (i.e., the "Ellipsis" builtin singleton): # >>> Callable[..., bool].__args__ # (Ellipsis, bool) # * A PEP 612-compliant parameter specification (i.e., # "typing.ParamSpec[...]" type hint): # >>> Callable[ParamSpec('P'), bool].__args__ # (~P, bool) # * A PEP 612-compliant parameter concatenation (i.e., # "typing.Concatenate[...]" type hint): # >>> Callable[Concatenate[str, ParamSpec('P')], bool].__args__ # (typing.Concatenate[str, ~P], bool) # * The return type hint originally subscripting this hint. # # Note that both PEP 484- *AND* 585-compliant callable type hints # guarantee this tuple to contain at least one child type hint. Ergo, we # avoid validating that constraint here: # >>> from typing import Callable # >>> Callable[()] # TypeError: Callable must be used as Callable[[arg, ...], result]. # >>> from collections.abc import Callable # >>> Callable[()] # TypeError: Callable must be used as Callable[[arg, ...], result]. # args = self._args args = super()._make_args() # Note that this branch may be literally unreachable, as an # unsubscripted "Callable" should already be implicitly handled by the # "ClassTypeHint" subclass. Nonetheless, this branch exists for safety. if not args: # pragma: no cover args = (..., Any,) else: # Parameters type hint(s) subscripting this callable type hint. # # Note that this: # * May be a special object (e.g., ellipsis) rather than a tuple of # zero or more parameter type hints. # * Has the essential side effect of eliminating harmful edge cases # (e.g., "Callable[[()], Any]", which is semantically but *NOT* # syntactically equivalent to "Callable[[], Any]"). args_params = get_hint_pep484585_callable_params(self._hint) # Return type hint subscripting this callable type hint. args_return = get_hint_pep484585_callable_return(self._hint) # Sign uniquely identifying this parameter list if any *OR* # "None" otherwise. hint_args_sign = get_hint_pep_sign_or_none(args_params) # If this hint was first subscripted by a PEP 612-compliant # parameter type hint, raise an exception. *sigh* if hint_args_sign in HINT_SIGNS_CALLABLE_PARAMS: raise BeartypeDoorPepUnsupportedException( f'PEP 484 or 585 callable type hint {repr(self._hint)} ' f'PEP 612 child type hint {repr(args_params)} ' f'currently unsupported.' ) # Else, this hint was *NOT* first subscripted by a PEP # 612-compliant parameter type hint. # Parameters type hint(s) subscripting this callable type hint, # coerced into a 1-tuple if *NOT* already a tuple. args_params_tuple = ( args_params if isinstance(args_params, tuple) else (args_params,) ) # Recreate the tuple of child type hints subscripting this parent # callable type hint from the tuple of argument type hints # introspected above. Why? Because the latter is saner than the # former in edge cases (e.g., ellipsis, empty argument lists). args = args_params_tuple + (args_return,) # Return these child hints. return args # ..................{ PRIVATE ~ properties }.................. @property # @property_cached def _args_wrapped_tuple(self) -> Tuple[TypeHint, ...]: # Tuple of all child type hints subscripting this callable type hint. args = self._args # Number of child type hints subscripting this callable type hint. args_len = len(args) # Tuple of all child type hint wrappers subscripting this callable type # hint wrapper, initialized to the empty tuple for simplicity. args_wrapped_tuple: Tuple[TypeHint, ...] = () # If this type hint is unsubscripted, return the empty tuple. if not args_len: pass # Else, this type hint is subscripted by one or more child type hints. # # If this type hint is subscripted by exactly one child type hint, then # that child type hint signifies this callable's return type hint, # implying this callable accepts *NO* parameters. In this case... elif args_len == 1: # Return a 2-tuple consisting of... args_wrapped_tuple = ( # Empty parameter list. TypeHint(Tuple[()]), # Return type hint. TypeHint(args[-1]), ) # Else, this type hint is subscripted by two or more child type hints. # # If the first child type hint subscripting this type hint is an # ellipsis (i.e., "..."), this callable accepts *ANY* parameters of # *ANY* arbitrary types. In this case... elif args[0] is ...: # Return a 2-tuple consisting of... args_wrapped_tuple = ( # Variadic parameter list. TypeHint(Any), # Return type hint. TypeHint(args[-1]), ) # Else, the first child type hint subscripting this type hint is *NOT* # an ellipsis. In this case, defer to the superclass approach. else: args_wrapped_tuple = super()._args_wrapped_tuple # Return this tuple. # print(f'Callable: {self._hint}; args: {self._args}; args_wrapped_tuple: {args_wrapped_tuple}') return args_wrapped_tuple # ..................{ PROPERTIES ~ hints }.................. @property # type: ignore @property_cached def param_hints(self) -> Tuple[TypeHint, ...]: ''' Tuple of the one or more parameter type hints subscripting this callable type hint. Notably, if this callable accepts: * *No* parameters (i.e., was originally subscripted by the empty list as ``Callable[[], ???]``), this is the 1-tuple ``(TypeHint(Tuple[()]),)``. * *Any* parameters of *any* arbitrary types (i.e., was originally subscripted by an ellipsis as ``Callable[..., ???]``), this is the 1-tuple ``(TypeHint(Any),)``. ''' return self._args_wrapped_tuple[:-1] @property def return_hint(self) -> TypeHint: ''' Return type hint subscripting this callable type hint. ''' return self._args_wrapped_tuple[-1] # ..................{ PROPERTIES ~ bools }.................. # FIXME: Remove this by instead adding support for ignoring ignorable # callable type hints to our core is_hint_ignorable() tester. Specifically: # * Ignore "Callable[..., {hint_ignorable}]" type hints, where "..." is the # ellipsis singleton and "{hint_ignorable}" is any ignorable type hint. # This has to be handled in a deep manner by: # * Defining a new is_hint_pep484585_ignorable_or_none() tester in the # existing "utilpep484585" submodule, whose initial implementation tests # for *ONLY* ignorable callable type hints. # * Import that tester in the "utilpeptest" submodule. # * Add that tester to the "_IS_HINT_PEP_IGNORABLE_TESTERS" tuple. # * Add example ignorable callable type hints to our test suite's data. @property def is_ignorable(self) -> bool: # Callable[..., Any] (or just `Callable`) return self.is_params_ignorable and self.is_return_ignorable @property def is_params_ignorable(self) -> bool: # Callable[..., ???] return self._args[0] is Ellipsis @property def is_return_ignorable(self) -> bool: # Callable[???, Any] return self.return_hint.is_ignorable # ..................{ PRIVATE ~ testers }.................. #FIXME: Internally comment us up, please. def _is_subhint_branch(self, branch: TypeHint) -> bool: # If that branch is unsubscripted, assume it is subscripted as # "typing.Callable[..., Any]" and just test for compatible origins. if branch._is_args_ignorable: return issubclass(self._origin, branch._origin) elif not isinstance(branch, CallableTypeHint): return False elif not issubclass(self._origin, branch._origin): return False elif not branch.is_params_ignorable and ( ( self.is_params_ignorable or len(self.param_hints) != len(branch.param_hints) or any( self_arg > branch_arg for self_arg, branch_arg in zip( self.param_hints, branch.param_hints) ) ) ): return False # FIXME: Insufficient, sadly. There are *MANY* different type hints that # are ignorable and thus semantically equivalent to "Any". It's likely # we should just reduce this to a one-liner resembling: # return self.return_hint <= branch.return_hint # # Are we missing something? We're probably missing something. *sigh* elif not branch.is_return_ignorable: return ( False if self.is_return_ignorable else self.return_hint <= branch.return_hint ) return True beartype-0.18.5/beartype/door/_cls/pep/pep484585/doorpep484585tuple.py000066400000000000000000000127031461113517100247710ustar00rootroot00000000000000#!/usr/bin/env python3 #--------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Decidedly Object-Oriented Runtime-checking (DOOR) callable type hint classes** (i.e., :class:`beartype.door.TypeHint` subclasses implementing support for :pep:`484`- and :pep:`585`-compliant ``Tuple[...]`` type hints). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.door._cls.doorsub import _TypeHintSubscripted from beartype.door._cls.doorsuper import TypeHint from beartype.typing import Any # ....................{ SUBCLASSES }.................... # FIXME: Document all public and private attributes of this class, please. class _TupleTypeHint(_TypeHintSubscripted): ''' **Tuple type hint wrapper** (i.e., high-level object encapsulating a low-level :pep:`484`- or :pep:`585`-compliant ``Tuple[...]`` type hint). Attributes (Private) -------- ''' # ..................{ INITIALIZERS }.................. def __init__(self, hint: object) -> None: #FIXME: Actually, it might be preferable to define two distinct #subclasses: #* "class _TypeHintTupleVariadic(_TypeHintSequence)", handling variadic # tuple type hints of the form "Tuple[T, ...]". #* "class _TypeHintTupleFixed(_TypeHintSubscripted)", handling # fixed-length tuple type hints. # #Why? Because variadic and fixed-length tuple type hints have *NOTHING* #semantically to do with one another. All they share is the common #prefix "Tuple". Aside from that, everything is dissimilar. Indeed, #most of the logic below (especially _is_subhint_branch(), which is kinda #cray-cray) would strongly benefit from separating this class into two #subclasses. # #Note that implementing this division will probably require generalizing #the TypeHint.__new__() method to support this division. # Initialize all subclass-specific instance variables for safety. self._is_variable_length: bool = False #FIXME: Non-ideal. The superclass __len__() method already returns 0 as #expected for "Tuple[()]" type hints. Excise us up! self._is_empty_tuple: bool = False # Initialize the superclass with all passed parameters. super().__init__(hint) def _make_args(self) -> tuple: # Tuple of the zero or more low-level child type hints subscripting # (indexing) the low-level parent type hint wrapped by this wrapper. args = super()._make_args() # Validate these child hints. Specifically, remove any # PEP-noncompliant child hints from this tuple and set associated flags. # # e.g. `Tuple` without any arguments # This may be unreachable (since a bare Tuple will go to ClassTypeHint), # but it's here for completeness and safety. if len(args) == 0: # pragma: no cover args = (Any,) self._is_variable_length = True #FIXME: This is non-portable and thus basically broken. Ideally, empty #tuples should instead be detected by explicitly calling the #is_hint_pep484585_tuple_empty() tester. elif len(args) == 1 and args[0] == (): args = () self._is_empty_tuple = True elif len(args) == 2 and args[1] is Ellipsis: args = (args[0],) self._is_variable_length = True # Return these child hints. return args # ..................{ PRIVATE ~ properties }.................. @property def _is_args_ignorable(self) -> bool: #FIXME: Actually, pretty sure this only returns true if this hint is #"Tuple[Any, ...]". *shrug* # Return true only if this hint is either "Tuple[Any, ...]" or the # unsubscripted "Tuple" type hint factory. return ( self._is_variable_length and bool(self) and self[0].is_ignorable ) # ..................{ PRIVATE ~ testers }.................. def _is_subhint_branch(self, branch: TypeHint) -> bool: if branch._is_args_ignorable: return issubclass(self._origin, branch._origin) elif not isinstance(branch, _TupleTypeHint): return False elif self._is_args_ignorable: return False elif branch._is_empty_tuple: return self._is_empty_tuple elif branch._is_variable_length: that_hint = branch._args_wrapped_tuple[0] if self._is_variable_length: return that_hint.is_subhint(self._args_wrapped_tuple[0]) return all( this_child.is_subhint(that_hint) for this_child in self._args_wrapped_tuple ) elif self._is_variable_length: return ( branch._is_variable_length and self._args_wrapped_tuple[0].is_subhint( branch._args_wrapped_tuple[0]) ) elif len(self._args) != len(branch._args): return False return all( this_child <= that_child for this_child, that_child in zip( self._args_wrapped_tuple, branch._args_wrapped_tuple ) ) beartype-0.18.5/beartype/door/_doorcheck.py000066400000000000000000000303711461113517100206540ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype Decidedly Object-Oriented Runtime-checking (DOOR) procedural type-checkers** (i.e., high-level functions type-checking arbitrary objects against type hints at *any* time during the lifecycle of the active Python process). ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: This submodule intentionally does *not* import the # @beartype.beartype decorator. Why? Because that decorator conditionally # reduces to a noop under certain contexts (e.g., `python3 -O` optimization), # whereas the API defined by this submodule is expected to unconditionally # operate as expected regardless of the current context. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype.typing import ( Any, Type, overload, ) from beartype._check.checkmake import ( make_func_raiser, make_func_tester, ) from beartype._conf.confcls import ( BEARTYPE_CONF_DEFAULT, BeartypeConf, ) from beartype._data.hint.datahintfactory import TypeGuard from beartype._data.hint.datahinttyping import T # ....................{ VALIDATORS }.................... def die_if_unbearable( # Mandatory flexible parameters. obj: object, hint: object, # Optional keyword-only parameters. *, conf: BeartypeConf = BEARTYPE_CONF_DEFAULT, exception_prefix: str = 'die_if_unbearable() ', ) -> None: ''' Raise an exception if the passed arbitrary object violates the passed type hint under the passed beartype configuration. To configure the type of violation exception raised by this function, set the :attr:`.BeartypeConf.violation_door_type` option of the passed ``conf`` parameter accordingly. Parameters ---------- obj : object Arbitrary object to be type-checked against this hint. hint : object Type hint to type-check this object against. conf : BeartypeConf, optional **Beartype configuration** (i.e., self-caching dataclass encapsulating all settings configuring type-checking for the passed object). Defaults to ``BeartypeConf()``, the default :math:`O(1)` constant-time configuration. exception_prefix : str, optional Human-readable label prefixing the representation of this object in the exception message. Defaults to a reasonably sensible string. Raises ------ ``conf.violation_door_type`` If this object violates this hint. beartype.roar.BeartypeDecorHintNonpepException If this hint is *not* PEP-compliant (i.e., complies with *no* Python Enhancement Proposals (PEPs) currently supported by :mod:`beartype`). beartype.roar.BeartypeDecorHintPepUnsupportedException If this hint is currently unsupported by :mod:`beartype`. Examples -------- .. code-block:: pycon >>> from beartype.door import die_if_unbearable >>> die_if_unbearable(['And', 'what', 'rough', 'beast,'], list[str]) >>> die_if_unbearable(['its', 'hour', 'come', 'round'], list[int]) beartype.roar.BeartypeDoorHintViolation: Object ['its', 'hour', 'come', 'round'] violates type hint list[int], as list index 0 item 'its' not instance of int. ''' # conf._is_debug = True # Memoized low-level type-checking raiser function either raising an # exception or emitting a warning only if the passed object passed violates # the type hint passed to this high-level type-checking raiser function. # # Note that parameters are intentionally passed positionally for efficiency. # Since make_func_raiser() is memoized, passing parameters by keyword would # raise a non-fatal # "_BeartypeUtilCallableCachedKwargsWarning" warning. func_raiser = make_func_raiser(hint, conf, exception_prefix) # Either raise an exception or emit a warning only if the passed object # violates this hint. func_raiser(obj) # pyright: ignore[reportUnboundVariable] # ....................{ TESTERS }.................... def is_subhint(subhint: object, superhint: object) -> bool: ''' :data:`True` only if the first passed hint is a **subhint** of the second passed hint, in which case this second hint is a **superhint** of this first hint. Equivalently, this tester returns :data:`True` only if *all* of the following conditions apply: * These two hints are **semantically related** (i.e., convey broadly similar semantics enabling these two hints to be reasonably compared). For example: * ``callable.abc.Iterable[str]`` and ``callable.abc.Sequence[int]`` are semantically related. These two hints both convey container semantics. Despite their differing child hints, these two hints are broadly similar enough to be reasonably comparable. * ``callable.abc.Iterable[str]`` and ``callable.abc.Callable[[], int]`` are *not* semantically related. Whereas the first hints conveys a container semantic, the second hint conveys a callable semantic. Since these two semantics are unrelated, these two hints are dissimilar enough to *not* be reasonably comparable. * The first hint is **semantically equivalent** to or **narrower** than the second hint. Equivalently: * The first hint matches less than or equal to the total number of all possible objects matched by the second hint. * The size of the countably infinite set of all possible objects matched by the first hint is less than or equal to that of those matched by the second hint. * The first hint is **compatible** with the second hint. Since the first hint is semantically narrower than the second, APIs annotated by the first hint may safely replace that hint with the second hint; doing so preserves backward compatibility. Parameters ---------- subhint : object Type hint or type to be tested as the subhint. superhint : object Type hint or type to be tested as the superhint. Returns ------- bool :data:`True` only if this first hint is a subhint of this second hint. Examples -------- >>> from beartype.door import is_subhint >>> is_subhint(int, int) True >>> is_subhint(Callable[[], list], Callable[..., Sequence[Any]]) True >>> is_subhint(Callable[[], list], Callable[..., Sequence[int]]) False ''' # Avoid circular import dependencies. from beartype.door._cls.doorsuper import TypeHint # The one-liner is mightier than the... many-liner. return TypeHint(subhint).is_subhint(TypeHint(superhint)) # ....................{ TESTERS ~ is_bearable }.................... #FIXME: Document this tester's conditional compliance with PEP 647 (i.e., #"typing.TypeGuard") in our official documentation, please. # Note that this PEP 484- and 647-compliant API is entirely the brain child of # @asford (Alex Ford). If this breaks, redirect all ~~vengeance~~ enquiries to: # https://github.com/asford @overload def is_bearable( obj: object, hint: Type[T], *, conf: BeartypeConf = BEARTYPE_CONF_DEFAULT, ) -> TypeGuard[T]: ''' :pep:`647`-compliant type guard conditionally narrowing the passed object to the passed type hint *only* when this hint is actually a valid **type** (i.e., subclass of the builtin :class:`type` superclass). ''' @overload def is_bearable( obj: T, hint: Any, *, conf: BeartypeConf = BEARTYPE_CONF_DEFAULT, ) -> TypeGuard[T]: ''' :pep:`647`-compliant fallback preserving (rather than narrowing) the type of the passed object when this hint is *not* a valid type (e.g., the :pep:`586`-compliant ``typing.Literal['totally', 'not', 'a', 'type']``, which is clearly *not* a type). ''' # Note that the actual implementation of this overload is intentionally: # * *NOT* decorated by the standard @overload decorator. # * *NOT* annotated by type hints. By PEP 484, only the signatures of # @overload-decorated callables are annotated by type hints. def is_bearable(obj, hint, *, conf = BEARTYPE_CONF_DEFAULT): # pyright: ignore ''' :data:`True` only if the passed arbitrary object satisfies the passed type hint under the passed beartype configuration. Note that this tester is a :pep:`647`-compliant **conditional type guard** (i.e., is annotated by the return type hint ``typing.TypeGuard[bool]``). Specifically: * If the passed type hint is a valid **type** (i.e., subclass of the builtin :class:`type` superclass), this tester is a general-purpose type guard that performs type narrowing on the passed object: e.g., .. code-block:: python from beartype.door import is_bearable def narrow_types_like_a_boss_with_beartype(lst: list[int | str]): # If "lst" is a list of integers, instruct static type-checkers # (e.g., mypy, pyright) that the call to munch_list_of_integers() # function expecting a list of integers is valid. # # Note that this works only because the is_bearable() tester # complies with PEP 647. If this were *NOT* the case, then static # type-checkers would raise a type-checking violation here. if is_bearable(lst, list[int]): munch_list_of_integers(lst) # Else if "lst" is a list of strings, behave similarly as above. elif is_bearable(lst, list[str]): munch_list_of_strings(lst) def munch_list_of_strings(lst: list[str]): ... def munch_list_of_integers(lst: list[int]): .... * If the passed type hint is *not* a valid type (e.g., the :pep:`586`-compliant ``typing.Literal['totally', 'not', 'a', 'type']``, which is clearly *not* a type), this tester is *not* a type guard and thus performs *no* type narrowing on the passed object. This is due to inadequacies in :pep:`647` rather than :mod:`beartype`. Parameters ---------- obj : object Arbitrary object to be tested against this hint. hint : object Type hint to test this object against. conf : BeartypeConf, optional **Beartype configuration** (i.e., self-caching dataclass encapsulating all settings configuring type-checking for the passed object). Defaults to ``BeartypeConf()``, the default constant-time configuration. Returns ------- bool :data:`True` only if this object satisfies this hint. Raises ------ beartype.roar.BeartypeConfException If this configuration is *not* a :class:`BeartypeConf` instance. beartype.roar.BeartypeDecorHintForwardRefException If this hint contains one or more relative forward references, which this tester explicitly prohibits to improve both the efficiency and portability of calls to this tester. beartype.roar.BeartypeDecorHintNonpepException If this hint is *not* PEP-compliant (i.e., complies with *no* Python Enhancement Proposals (PEPs) currently supported by :mod:`beartype`). beartype.roar.BeartypeDecorHintPepUnsupportedException If this hint is currently unsupported by :mod:`beartype`. Examples -------- >>> from beartype.door import is_bearable >>> is_bearable(['Things', 'fall', 'apart;'], list[str]) True >>> is_bearable(['the', 'centre', 'cannot', 'hold;'], list[int]) False ''' # Memoized low-level type-checking tester function returning true only if # the object passed to that tester satisfies the type hint passed to this # high-level type-checking tester function. # # Note that parameters are intentionally passed positionally for efficiency. # Since make_func_tester() is memoized, passing parameters by keyword would # raise a non-fatal # "_BeartypeUtilCallableCachedKwargsWarning" warning. func_tester = make_func_tester(hint, conf) # Return true only if the passed object satisfies this hint. return func_tester(obj) # pyright: ignore beartype-0.18.5/beartype/door/_doordata.py000066400000000000000000000207321461113517100205100ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype Decidedly Object-Oriented Runtime-checking (DOOR) data** (i.e., global constants internally required throughout the :mod:`beartype.door` subpackage). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.door._cls.doorsub import ( _TypeHintOriginIsinstanceableArgs1, _TypeHintOriginIsinstanceableArgs2, _TypeHintOriginIsinstanceableArgs3, _TypeHintSubscripted, ) from beartype.door._cls.doorsuper import TypeHint from beartype.door._cls.pep.pep484.doorpep484class import ClassTypeHint from beartype.door._cls.pep.doorpep484604 import UnionTypeHint from beartype.door._cls.pep.doorpep586 import LiteralTypeHint from beartype.door._cls.pep.doorpep593 import AnnotatedTypeHint from beartype.door._cls.pep.pep484.doorpep484newtype import NewTypeTypeHint from beartype.door._cls.pep.pep484.doorpep484typevar import TypeVarTypeHint from beartype.door._cls.pep.pep484585.doorpep484585callable import ( CallableTypeHint) from beartype.door._cls.pep.pep484585.doorpep484585tuple import _TupleTypeHint from beartype.roar import ( BeartypeDoorNonpepException, # BeartypeDoorPepUnsupportedException, ) from beartype.typing import ( Dict, Type, ) from beartype._data.hint.pep.sign.datapepsigncls import HintSign from beartype._data.hint.pep.sign.datapepsigns import ( HintSignAnnotated, HintSignCallable, HintSignGeneric, HintSignLiteral, HintSignNewType, HintSignTuple, HintSignTypeVar, ) from beartype._util.hint.pep.utilpepget import ( get_hint_pep_args, # get_hint_pep_origin_or_none, get_hint_pep_sign_or_none, ) from beartype._util.hint.pep.utilpeptest import is_hint_pep_typing # ....................{ GETTERS }.................... def get_typehint_subclass(hint: object) -> Type[TypeHint]: ''' Concrete :class:`TypeHint` subclass handling the passed low-level unwrapped PEP-compliant type hint if any *or* raise an exception otherwise. Parameters ---------- hint : object Low-level type hint to be inspected. Returns ---------- Type[TypeHint] Concrete subclass of the abstract :mod:`TypeHint` superclass handling this hint. Raises ---------- beartype.roar.BeartypeDoorNonpepException If this API does *not* currently support the passed hint. beartype.roar.BeartypeDecorHintPepSignException If the passed hint is *not* actually a PEP-compliant type hint. ''' # ..................{ SUBCLASS }.................. # Sign uniquely identifying this hint if any *OR* "None" otherwise (i.e., if # this hint is a PEP-noncompliant class). hint_sign = get_hint_pep_sign_or_none(hint) # Private concrete subclass of this ABC handling this hint if any *OR* # "None" otherwise (i.e., if no such subclass has been authored yet). wrapper_subclass = _HINT_SIGN_TO_TYPEHINT_CLS.get(hint_sign) # type: ignore[arg-type] # If this hint appears to be currently unsupported... if wrapper_subclass is None: #FIXME: This condition is kinda intense. Should we really be conflating #typing attributes that aren't types with objects that are types? Let's #investigate exactly which kinds of type hints require this and #contemplate something considerably more elegant. # If either... if ( # This hint is a PEP-noncompliant isinstanceable class *OR*... isinstance(hint, type) or # An unsupported kind of PEP-compliant type hint (e.g., # "typing.TypedDict" instance)... is_hint_pep_typing(hint) # Return the concrete "TypeHint" subclass handling all such classes. ): wrapper_subclass = ClassTypeHint # Else, raise an exception. else: raise BeartypeDoorNonpepException( f'Type hint {repr(hint)} ' f'currently unsupported by "beartype.door.TypeHint".' ) # Else, this hint is supported. #FIXME: Alternately, it might be preferable to refactor this to resemble: # if ( # not get_hint_pep_args(hint) and # get_hint_pep_origin_type_or_none(hint) is not None # ): # wrapper_subclass = ClassTypeHint # #That's possibly simpler and cleaner, as it seamlessly conveys the exact #condition we're going for -- assuming it works, of course. *sigh* #FIXME: While sensible, the above approach induces non-trivial test #failures. Let's investigate this further at a later time, please. #FIXME: Push the "not" up to the top level of this conditional, please. # If this hint is unsubscripted a subscriptable type has no args, all we care about is the origin. elif ( # Unsubscripted (i.e., indexed by *NO* child type hints) *AND*... not get_hint_pep_args(hint) and #FIXME: No idea, bro. This is pretty weird. For one, #"_HINT_SIGNS_ORIGINLESS" doesn't even contain all the signs it should #(e.g., "HintSignLiteral", "HintSignUnion"). For another we should just #be calling this instead: # get_hint_pep_origin_type_or_none(hint) is not None hint_sign not in _HINT_SIGNS_ORIGINLESS ): wrapper_subclass = ClassTypeHint # In any case, this hint is supported by this concrete subclass. # Return this subclass. return wrapper_subclass # ....................{ PRIVATE ~ globals }.................... # Further initialized below by the _init() function. _HINT_SIGN_TO_TYPEHINT_CLS: Dict[HintSign, Type[TypeHint]] = { HintSignAnnotated: AnnotatedTypeHint, HintSignCallable: CallableTypeHint, HintSignGeneric: _TypeHintSubscripted, HintSignLiteral: LiteralTypeHint, HintSignNewType: NewTypeTypeHint, HintSignTuple: _TupleTypeHint, HintSignTypeVar: TypeVarTypeHint, } ''' Dictionary mapping from each sign uniquely identifying PEP-compliant type hints to the :class:`TypeHint` subclass handling those hints. ''' #FIXME: Consider shifting into "datapepsignset" if still required. _HINT_SIGNS_ORIGINLESS = frozenset(( HintSignNewType, HintSignTypeVar, )) ''' Frozen set of all **origin-less signs.** ''' # ....................{ PRIVATE ~ initializers }.................... def _init() -> None: ''' Initialize this submodule. ''' # Isolate function-specific imports. from beartype._data.hint.pep.sign.datapepsignset import ( HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_1, HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_2, HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_3, HINT_SIGNS_UNION, ) # Fully initialize the "HINT_SIGN_TO_TYPEHINT" dictionary declared above. for sign in HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_1: _HINT_SIGN_TO_TYPEHINT_CLS[sign] = _TypeHintOriginIsinstanceableArgs1 for sign in HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_2: _HINT_SIGN_TO_TYPEHINT_CLS[sign] = _TypeHintOriginIsinstanceableArgs2 for sign in HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_3: _HINT_SIGN_TO_TYPEHINT_CLS[sign] = _TypeHintOriginIsinstanceableArgs3 for sign in HINT_SIGNS_UNION: _HINT_SIGN_TO_TYPEHINT_CLS[sign] = UnionTypeHint # For each concrete "TypeHint" subclass registered with this dictionary # (*AFTER* initializing this dictionary)... for typehint_cls in _HINT_SIGN_TO_TYPEHINT_CLS.values(): # If the unqualified basename of this subclass is prefixed by an # underscore, this subclass is private rather than public. In this case, # silently ignore this private subclass and continue to the next. if typehint_cls.__name__.startswith('_'): continue # Else, this subclass is public. # Sanitize the fully-qualified module name of this public subclass from # the private submodule declaring this subclass (e.g., # "beartype.door._cls.pep.doorpep484604.UnionTypeHint") to the public # "beartype.door" subpackage to both improve the readability of # exceptions and discourage users from violating privacy encapsulation. typehint_cls.__module__ = 'beartype.door' # ....................{ MAIN }.................... # Initialize this submodule. _init() beartype-0.18.5/beartype/door/_doortest.py000066400000000000000000000026371461113517100205620ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype Decidedly Object-Oriented Runtime-checking (DOOR) testers** (i.e., callables testing and validating :class:`beartype.door.TypeHint` instances). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeDoorException # ....................{ VALIDATORS }.................... def die_unless_typehint(obj: object) -> None: ''' Raise an exception unless the passed object is a **type hint wrapper** (i.e., :class:`TypeHint` instance). Parameters ---------- obj : object Arbitrary object to be validated. Raises ---------- beartype.roar.BeartypeDoorException If this object is *not* a type hint wrapper. ''' # Avoid circular import dependencies. from beartype.door._cls.doorsuper import TypeHint # If this object is *NOT* a type hint wrapper, raise an exception. if not isinstance(obj, TypeHint): raise BeartypeDoorException( f'{repr(obj)} not type hint wrapper ' f'(i.e., "beartype.door.TypeHint" instance).' ) # Else, this object is a type hint wrapper. beartype-0.18.5/beartype/meta.py000066400000000000000000000771101461113517100165410ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype metadata.** This submodule exports global constants synopsizing this package -- including versioning and dependencies. Python Version -------------- For uniformity between this codebase and the ``setup.py`` setuptools script importing this module, this module also validates the version of the active Python 3 interpreter. An exception is raised if this version is insufficient. As a tradeoff between backward compatibility, security, and maintainability, this package strongly attempts to preserve compatibility with the first stable release of the oldest version of CPython still under active development. Hence, obsolete and insecure versions of CPython that have reached their official End of Life (EoL) (e.g., Python 3.5) are explicitly unsupported. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: To avoid accidental importation of optional runtime dependencies # (e.g., "typing-extensions") at installation time *BEFORE* the current package # manager has installed those dependencies, this module may *NOT* import from # any submodules of the current package. This includes *ALL* "beartype._util" # submodules, most of which import from "beartype.typing", which conditionally # imports optional runtime dependencies under certain contexts. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: To avoid race conditions during setuptools-based installation, this # module may import *ONLY* from modules guaranteed to exist at the start of # installation. This includes all standard Python and package modules but # *NOT* third-party dependencies, which if currently uninstalled will only be # installed at some later time in the installation. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: To avoid polluting the public module namespace, external attributes # should be locally imported at module scope *ONLY* under alternate private # names (e.g., "from argparse import ArgumentParser as _ArgumentParser" rather # than merely "from argparse import ArgumentParser"). #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! import sys as _sys # from beartype.typing import Tuple as _Tuple # ....................{ METADATA }.................... NAME = 'beartype' ''' Human-readable package name. ''' LICENSE = 'MIT' ''' Human-readable name of the license this package is licensed under. ''' # ....................{ METADATA ~ package }.................... PACKAGE_NAME = NAME.lower() ''' Fully-qualified name of the top-level Python package containing this submodule. ''' PACKAGE_TEST_NAME = f'{PACKAGE_NAME}_test' ''' Fully-qualified name of the top-level Python package exercising this project. ''' # ....................{ PYTHON ~ version }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: Changes to this section *MUST* be synchronized with: # * Signs declared by the private # "beartype._data.hint.pep.datapepsign" submodule, which *MUST* # be synchronized against the "__all__" dunder list global of the "typing" # module bundled with the most recent CPython release. # * Continuous integration test matrices, including: # * The top-level "tox.ini" file. # * The "jobs/tests/strategy/matrix/{tox-env,include/python-version}" # settings of the GitHub Actions-specific # ".github/workflows/python_test.yml" file. # * Front-facing documentation (e.g., "README.rst", "doc/md/INSTALL.md"). # # On bumping the minimum required version of Python, consider also documenting # the justification for doing so in the "Python Version" section of this # submodule's docstring above. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! PYTHON_VERSION_MIN = '3.8.0' ''' Human-readable minimum version of Python required by this package as a ``.``-delimited string. See Also -------- "Python Version" section of this submodule's docstring for a detailed justification of this constant's current value. ''' PYTHON_VERSION_MINOR_MAX = 13 ''' Maximum minor stable version of this major version of Python currently released (e.g., ``5`` if Python 3.5 is the most recent stable version of Python 3.x). ''' def _convert_version_str_to_tuple(version_str: str): # -> _Tuple[int, ...]: ''' Convert the passed human-readable ``.``-delimited version string into a machine-readable version tuple of corresponding integers. ''' assert isinstance(version_str, str), f'"{version_str}" not version string.' return tuple(int(version_part) for version_part in version_str.split('.')) PYTHON_VERSION_MIN_PARTS = _convert_version_str_to_tuple(PYTHON_VERSION_MIN) ''' Machine-readable minimum version of Python required by this package as a tuple of integers. ''' _PYTHON_VERSION_PARTS = _sys.version_info[:3] ''' Machine-readable current version of the active Python interpreter as a tuple of integers. ''' # Validate the version of the active Python interpreter *BEFORE* subsequent # code possibly depending on this version. Since this version should be # validated both at setuptools-based install time and post-install runtime # *AND* since this module is imported sufficiently early by both, stash this # validation here to avoid duplication of this logic and hence the hardcoded # Python version. # # The "sys" module exposes three version-related constants for this purpose: # * "hexversion", an integer intended to be specified in an obscure (albeit # both efficient and dependable) hexadecimal format: e.g., # >>> sys.hexversion # 33883376 # >>> '%x' % sys.hexversion # '20504f0' # * "version", a human-readable string: e.g., # >>> sys.version # 2.5.2 (r252:60911, Jul 31 2008, 17:28:52) # [GCC 4.2.3 (Ubuntu 4.2.3-2ubuntu7)] # * "version_info", a tuple of three or more integers *OR* strings: e.g., # >>> sys.version_info # (2, 5, 2, 'final', 0) # # For sanity, this package will *NEVER* conditionally depend upon the # string-formatted release type of the current Python version exposed via the # fourth element of the "version_info" tuple. Since the first three elements of # that tuple are guaranteed to be integers *AND* since a comparable 3-tuple of # integers is declared above, comparing the former and latter yield the # simplest and most reliable Python version test. # # Note that the nearly decade-old and officially accepted PEP 345 proposed a # new field "requires_python" configured via a key-value pair passed to the # call to setup() in "setup.py" (e.g., "requires_python = ['>=2.2.1'],"), that # field has yet to be integrated into either disutils or setuputils. Hence, # that field is validated manually in the typical way. if _PYTHON_VERSION_PARTS < PYTHON_VERSION_MIN_PARTS: # Human-readable current version of Python. Ideally, "sys.version" would be # leveraged here instead; sadly, that string embeds significantly more than # merely a version and hence is inapplicable for real-world usage: e.g., # # >>> import sys # >>> sys.version # '3.6.5 (default, Oct 28 2018, 19:51:39) \n[GCC 7.3.0]' _PYTHON_VERSION = '.'.join( str(version_part) for version_part in _sys.version_info[:3]) # Die ignominiously. raise RuntimeError( f'{NAME} requires at least Python {PYTHON_VERSION_MIN}, but ' f'the active interpreter only targets Python {_PYTHON_VERSION}. ' f'We feel unbearable sadness for you.' ) # ....................{ METADATA ~ version }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: When modifying the current version of this package below, # consider adhering to the Semantic Versioning schema. Specifically, the # version should consist of three "."-delimited integers # "{major}.{minor}.{patch}", where: # # * "{major}" specifies the major version, incremented only when either: # * Breaking backward compatibility in this package's public API. # * Implementing headline-worthy functionality (e.g., a GUI). Technically, # this condition breaks the Semantic Versioning schema, which stipulates # that *ONLY* changes breaking backward compatibility warrant major bumps. # But this is the real world. In the real world, significant improvements # are rewarded with significant version changes. # In either case, the minor and patch versions both reset to 0. # * "{minor}" specifies the minor version, incremented only when implementing # customary functionality in a manner preserving such compatibility. In this # case, the patch version resets to 0. # * "{patch}" specifies the patch version, incremented only when correcting # outstanding issues in a manner preserving such compatibility. # # When in doubt, increment only the minor version and reset the patch version. # For further details, see http://semver.org. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! VERSION = '0.18.5' ''' Human-readable package version as a ``.``-delimited string. ''' VERSION_PARTS = _convert_version_str_to_tuple(VERSION) ''' Machine-readable package version as a tuple of integers. ''' # ....................{ METADATA ~ synopsis }.................... SYNOPSIS = 'Unbearably fast runtime type checking in pure Python.' ''' Human-readable single-line synopsis of this package. By PyPI design, this string must *not* span multiple lines or paragraphs. ''' # ....................{ METADATA ~ authors }.................... AUTHOR_EMAIL = 'leycec@gmail.com' ''' Email address of the principal corresponding author (i.e., the principal author responding to public correspondence). ''' AUTHORS = 'Cecil Curry, et al.' ''' Human-readable list of all principal authors of this package as a comma-delimited string. For brevity, this string *only* lists authors explicitly assigned copyrights. For the list of all contributors regardless of copyright assignment or attribution, see the `contributors graph`_ for this project. .. _contributors graph: https://github.com/beartype/beartype/graphs/contributors ''' COPYRIGHT = '2014-2024 Beartype authors' ''' Legally binding copyright line excluding the license-specific prefix (e.g., ``"Copyright (c)"``). For brevity, this string *only* lists authors explicitly assigned copyrights. For the list of all contributors regardless of copyright assignment or attribution, see the `contributors graph`_ for this project. .. _contributors graph: https://github.com/beartype/beartype/graphs/contributors ''' # ....................{ METADATA ~ urls }.................... URL_CONDA = f'https://anaconda.org/conda-forge/{PACKAGE_NAME}' ''' URL of this project's entry on **Anaconda** (i.e., alternate third-party Python package repository utilized by the Anaconda Python distribution). ''' URL_LIBRARIES = f'https://libraries.io/pypi/{PACKAGE_NAME}' ''' URL of this project's entry on **Libraries.io** (i.e., third-party open-source package registrar associated with the Tidelift open-source funding agency). ''' URL_PYPI = f'https://pypi.org/project/{PACKAGE_NAME}' ''' URL of this project's entry on **PyPI** (i.e., official Python package repository, also colloquially known as the "cheeseshop"). ''' URL_RTD = f'https://readthedocs.org/projects/{PACKAGE_NAME}' ''' URL of this project's entry on **ReadTheDocs (RTD)** (i.e., popular Python documentation host, shockingly hosting this project's documentation). ''' # ....................{ METADATA ~ urls : docs }.................... URL_HOMEPAGE = f'https://{PACKAGE_NAME}.readthedocs.io' ''' URL of this project's homepage. ''' URL_PEP585_DEPRECATIONS = ( f'{URL_HOMEPAGE}/en/latest/api_roar/#pep-585-deprecations') ''' URL documenting :pep:`585` deprecations of :pep:`484` type hints. ''' # ....................{ METADATA ~ urls : repo }.................... URL_REPO_ORG_NAME = PACKAGE_NAME ''' Name of the **organization** (i.e., parent group or user principally responsible for maintaining this project, indicated as the second-to-last trailing subdirectory component) of the URL of this project's git repository. ''' URL_REPO_BASENAME = PACKAGE_NAME ''' **Basename** (i.e., trailing subdirectory component) of the URL of this project's git repository. ''' URL_REPO = f'https://github.com/{URL_REPO_ORG_NAME}/{URL_REPO_BASENAME}' ''' URL of this project's git repository. ''' URL_DOWNLOAD = f'{URL_REPO}/archive/{VERSION}.tar.gz' ''' URL of the source tarball for the current version of this project. This URL assumes a tag whose name is ``v{VERSION}`` where ``{VERSION}`` is the human-readable current version of this project (e.g., ``v0.4.0``) to exist. Typically, no such tag exists for live versions of this project -- which have yet to be stabilized and hence tagged. Hence, this URL is typically valid *only* for previously released (rather than live) versions of this project. ''' URL_FORUMS = f'{URL_REPO}/discussions' ''' URL of this project's user forums. ''' URL_ISSUES = f'{URL_REPO}/issues' ''' URL of this project's issue tracker. ''' URL_RELEASES = f'{URL_REPO}/releases' ''' URL of this project's release list. ''' # ....................{ METADATA ~ libs : runtime }.................... _LIB_RUNTIME_OPTIONAL_VERSION_MINIMUM_NUMPY = '1.21.0' ''' Minimum optional version of NumPy recommended for use with this project. NumPy >= 1.21.0 first introduced the third-party PEP-noncompliant :attr:`numpy.typing.NDArray` type hint supported by the :func:`beartype.beartype` decorator. ''' _LIB_RUNTIME_OPTIONAL_VERSION_MINIMUM_TYPING_EXTENSIONS = '3.10.0.0' ''' Minimum optional version of the third-party :mod:`typing_extensions` package recommended for use with this project. :mod:`typing_extensions` >= 3.10.0.0 backports all :mod:`typing` attributes unavailable under older Python interpreters supported by the :func:`beartype.beartype` decorator. ''' # Note that we intentionally omit NumPy here, because: # * If you want it, you're already using it. # * If you do *NOT* want it, you're *NOT* already using it. LIBS_RUNTIME_OPTIONAL = ( ( f'typing-extensions >=' f'{_LIB_RUNTIME_OPTIONAL_VERSION_MINIMUM_TYPING_EXTENSIONS}' ), ) ''' Optional runtime package dependencies as a tuple of :mod:`setuptools`-specific requirements strings of the format ``{project_name} {comparison1}{version1},...,{comparisonN}{versionN}``, where: * ``{project_name}`` is a :mod:`setuptools`-specific project name (e.g., ``"numpy"``, ``"scipy"``). * ``{comparison1}`` and ``{comparisonN}`` are :mod:`setuptools`-specific version comparison operators. As well as standard mathematical comparison operators (e.g., ``==``, ``>=``, ``<``), :mod:`setuptools` also supports the PEP 440-compliant "compatible release" operator ``~=`` more commonly denoted by ``^`` in modern package managers (e.g., poetry, npm); this operator enables forward compatibility with all future versions of this dependency known *not* to break backward compatibility, but should only be applied to dependencies strictly following the semantic versioning contract. * ``{version1}`` and ``{version1}`` are arbitrary version strings (e.g., ``2020.2.16``, ``0.75a2``). ''' # ....................{ METADATA ~ libs : test : optional }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Avoid constraining optional test-time dependencies to version # ranges, which commonly fail for edge-case test environments -- including: # * The oldest Python version still supported by @beartype, which typically is # *NOT* supported by newer versions of these dependencies. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! LIBS_TESTTIME_OPTIONAL = ( # Required by optional Equinox-specific integration tests. 'equinox', # Require a reasonably recent version of mypy known to behave well. Less # recent versions are significantly deficient with respect to error # reporting and *MUST* thus be blacklisted. # # Note that PyPy currently fails to support mypy. See also this official # documentation discussing this regrettable incompatibility: # https://mypy.readthedocs.io/en/stable/faq.html#does-it-run-on-pypy 'mypy >=0.800; platform_python_implementation != "PyPy"', #FIXME: Let's avoid attempting to remotely compile with nuitka under GitHub #Actions-hosted continuous integration (CI) for the moment. Doing so is #non-trivial enough under local testing workflows. *sigh* # Require a reasonably recent version of nuitka if the current platform is a # Linux distribution *AND* the active Python interpreter targets Python >= # 3.8. For questionable reasons best ignored, nuitka fails to compile # beartype under Python <= 3.7. # 'nuitka >=1.2.6; sys_platform == "linux" and python_version >= "3.8.0"', #FIXME: Consider dropping the 'and platform_python_implementation != "PyPy"' #clause now that "tox.ini" installs NumPy wheels from a third-party vendor #explicitly supporting PyPy. # Require NumPy. NumPy has become *EXTREMELY* non-trivial to install under # macOS with "pip", due to the conjunction of multiple issues. These # include: # * NumPy > 1.18.0, whose initial importation now implicitly detects # whether the BLAS implementation NumPy was linked against is sane and # raises a "RuntimeError" exception if that implementation is insane: # RuntimeError: Polyfit sanity test emitted a warning, most # likely due to using a buggy Accelerate backend. If you # compiled yourself, more information is available at # https://numpy.org/doc/stable/user/building.html#accelerated-blas-lapack-libraries # Otherwise report this to the vendor that provided NumPy. # RankWarning: Polyfit may be poorly conditioned # * Apple's blatantly broken multithreaded implementation of their # "Accelerate" BLAS replacement, which neither NumPy nor "pip" have *ANY* # semblance of control over. # * "pip" under PyPy, which for unknown reasons fails to properly install # NumPy even when the "--force-reinstall" option is explicitly passed to # "pip". Oddly, passing that option to "pip" under CPython resolves this # issue -- which is why we only selectively disable NumPy installation # under macOS + PyPy. # # See also this upstream NumPy issue: # https://github.com/numpy/numpy/issues/15947 ( 'numpy; ' 'sys_platform != "darwin" and ' 'platform_python_implementation != "PyPy"' ), # Required by optional Pandera-specific integration tests. 'pandera', # Required by optional Sphinx-specific integration tests. # # Note that Sphinx currently provokes unrelated test failures under Python # 3.7 with obscure deprecation warnings. Since *ALL* of this only applies # to Python 3.7, we crudely circumvent this nonsense by simply avoiding # installing Sphinx under Python 3.7. The exception resembles: # FAILED # ../../../beartype_test/a00_unit/a20_util/test_utilobject.py::test_is_object_hashable # - beartype.roar.BeartypeModuleUnimportableWarning: Ignoring module # "pkg_resources.__init__" importation exception DeprecationWarning: # Deprecated call to `pkg_resources.declare_namespace('sphinxcontrib')`. 'sphinx; python_version >= "3.8.0"', #FIXME: Temporarily disabled for sanity. # Required by optional PyTorch-specific integration tests. # # Note that PyTorch has yet to release a Python >= 3.12-compatible version. # 'torch; python_version < "3.12.0"', # Required to exercise third-party backports of type hint factories # published by the standard "typing" module under newer versions of Python. ( f'typing-extensions >=' f'{_LIB_RUNTIME_OPTIONAL_VERSION_MINIMUM_TYPING_EXTENSIONS}' ), ) ''' **Optional developer test-time package dependencies** (i.e., dependencies recommended to test this package with :mod:`tox` as a developer at the command line) as a tuple of :mod:`setuptools`-specific requirements strings of the format ``{project_name} {comparison1}{version1},...,{comparisonN}{versionN}``. See Also ---------- :data:`LIBS_RUNTIME_OPTIONAL` Further details. ''' # ....................{ METADATA ~ libs : test : mandatory }.................... LIBS_TESTTIME_MANDATORY_COVERAGE = ( 'coverage >=5.5', ) ''' **Mandatory test-time coverage package dependencies** (i.e., dependencies required to measure test coverage for this package) as a tuple of :mod:`setuptools`-specific requirements strings of the format ``{project_name} {comparison1}{version1},...,{comparisonN}{versionN}``. See Also ---------- :data:`LIBS_RUNTIME_OPTIONAL` Further details. ''' # For completeness, install *ALL* optional test-time dependencies into *ALL* # isolated virtual environments managed by "tox". Failure to list *ALL* # optional test-time dependencies here commonly results in errors from mypy, # which raises false positives on parsing "import" statements for currently # uninstalled third-party packages (e.g., "import numpy as np"). LIBS_TESTTIME_MANDATORY_TOX = LIBS_TESTTIME_OPTIONAL + ( 'pytest >=4.0.0', ) ''' **Mandatory tox test-time package dependencies** (i.e., dependencies required to test this package under :mod:`tox`) as a tuple of :mod:`setuptools`-specific requirements strings of the format ``{project_name} {comparison1}{version1},...,{comparisonN}{versionN}``. See Also ---------- :data:`LIBS_RUNTIME_OPTIONAL` Further details. ''' LIBS_TESTTIME_MANDATORY = ( LIBS_TESTTIME_MANDATORY_COVERAGE + LIBS_TESTTIME_MANDATORY_TOX + ( # A relatively modern version of tox is required. 'tox >=3.20.1', ) ) ''' **Mandatory developer test-time package dependencies** (i.e., dependencies required to test this package with :mod:`tox` as a developer at the command line) as a tuple of :mod:`setuptools`-specific requirements strings of the format ``{project_name} {comparison1}{version1},...,{comparisonN}{versionN}``. See Also ---------- :data:`LIBS_RUNTIME_OPTIONAL` Further details. ''' # ....................{ METADATA ~ libs : doc : sphinx }.................... _SPHINX_VERSION_MINIMUM = '4.2.0' ''' Machine-readable minimum (inclusive) version as a ``.``-delimited string of :mod:`sphinx` required to build package documentation. Specifically, this project requires: * :mod:sphinx` >= 4.2.0, which resolved a `severe compatibility issue`_ with Python >= 3.10. .. _severe compatibility issue: https://github.com/sphinx-doc/sphinx/issues/9816 ''' #FIXME: Once "pydata-sphinx-theme" 0.13.0 is released: #* Relax this restriction (e.g., by simply commenting this global out both here # and below). #* Bump "_SPHINX_THEME_VERSION_MAXIMUM >= '0.13.0'" below. _SPHINX_VERSION_MAXIMUM_EXCLUSIVE = '6.0.0' ''' Machine-readable maximum (exclusive) version as a ``.``-delimited string of :mod:`sphinx` required to build package documentation. Specifically, this project requires: * :mod:sphinx` < 6.0.0, as more recent versions `currently conflict with our Sphinx theme `__. .. _theme conflict: https://github.com/sphinx-doc/sphinx/issues/9816 ''' # ....................{ METADATA ~ libs : doc : theme }.................... #FIXME: Switch! So, "pydata-sphinx-theme" is ostensibly *MOSTLY* great. However, #there are numerous obvious eccentricities in "pydata-sphinx-theme" that we #strongly disagree with -- especially that theme's oddball division in TOC #heading levels between the top and left sidebars. # #Enter "sphinx-book-theme", stage left. "sphinx-book-theme" is based on #"pydata-sphinx-theme", but entirely dispenses with all of the obvious #eccentricities that hamper usage of "pydata-sphinx-theme". We no longer have #adequate time to maintain custom documentation CSS against the moving target #that is "pydata-sphinx-theme". Ergo, we should instead let "sphinx-book-theme" #do all of that heavy lifting for us. Doing so will enable us to: #* Lift the horrifying constraint above on a maximum Sphinx version. *gulp* #* Substantially simplify our Sphinx configuration. Notably, the entire fragile # "doc/src/_templates/" subdirectory should be *ENTIRELY* excised away. # #Please transition to "sphinx-book-theme" as time permits. SPHINX_THEME_NAME = 'pydata-sphinx-theme' ''' Name of the third-party Sphinx extension providing the custom HTML theme preferred by this documentation. Note that we selected this theme according to mostly objective (albeit ultimately subjective) heuristic criteria. In descending order of importance, we selected the theme with: #. The most frequent git commit history. #. The open issues and pull requests (PRs). #. The most GitHub stars as a crude proxy for aggregate rating. #. **IS NOT STRONGLY OPINIONATED** (i.e., is configurable with standard Sphinx settings and directives). Furo ---------- Furo_ handily bested all other themes across the first three criteria. Furo is very well-maintained, frequently closes out open issues and merges open PRs, and sports the highest quantity of GitHub stars by an overwhelming margin. Sadly, Furo handily loses against literally unmaintained themes across the final criteria. Furo is absurdly strongly opinionated to an authoritarian degree we rarely find in open-source software. Why? Because it's principal maintainer is. Like maintainer, like software. Furo routinely ignores standard Sphinx settings and directives due to subjective opinions held by its maintainer, including: * Most user-defined ``:toctree:`` settings used to configure both global and local tables of contents (TOCs) and thus the leftmost navigation sidebar, effectively preventing users from using that sidebar to navigate to anything. We are *not* kidding. ``:toctree:`` settings ignored by Furo include: * ``:maxdepth:``. Internally, Furo forces the ``:titlesonly:`` setting by passing ``titles_only=True`` to Sphinx's ``toctree()`` function at runtime. Doing so effectively coerces ``:maxdepth: 1``, thus intentionally hiding *all* document structure from the navigation sidebar -- where (usually) *all* document structure is displayed. Users thus have no means of directly jumping from the root landing page to child leaf documents, significantly obstructing user experience (UX) and usability. See also this `feature request `__ to relax these constraints, to which the Furo maintainer caustically replies: No, there isn't any (supported) way to do this. Separating the page content hierarchy and site structure was an explicit design goal. We fundamentally disagree with those goals and have thus permanently switched away from Furo. Unjustified opinions are the little death of sanity. PyData ====== Furo and PyData are neck-and-neck with respect to git commit history; both are extremely well-maintained. Furo leaps ahead with respect to both issue and PR resolution, however; PyData has an extreme number of open issues and PRs, where Furo enjoys none. Moreover, Furo also enjoys dramatically more GitHub stars. Nonetheless, PyData is *not* strongly opinionated; Furo is. PyData does *not* silently ignore standard Sphinx settings and directives for largely indefensible reasons. Consequently, PyData wins by default. In fact, *any* other theme (including even unmaintained dead themes) wins by default; *no* other theme (to my limited knowledge) forcefully ignores standard Sphinx settings and directives to the extent that Furo does. PyData wins by *literally* doing nothing. Laziness prevails. All hail La-Z-Boy. .. _Furo: https://github.com/pradyunsg/furo .. _Furo discussion: https://github.com/pradyunsg/furo/discussions/146 ''' _SPHINX_THEME_VERSION_MAXIMUM = '0.7.2' # _SPHINX_THEME_VERSION_MAXIMUM = '0.12.0' ''' Machine-readable maximum (inclusive) version as a ``.``-delimited string of the above Sphinx theme optionally leveraged when building package documentation. This theme is a rapidly moving target that frequently breaks backward compatibility. Although understandable, the fragility of this theme leaves us little alternatives but to pin to a **maximum** rather than **minimum** version of this theme. Specifically, this project requires: * :mod:pydata_sphinx_theme` <= 0.7.2, as our circumvention of both pydata/pydata-sphinx-theme#90 and pydata/pydata-sphinx-theme#221 assumes a reasonably older version of this theme. See also this currently `open issue`_. .. _open issue: https://github.com/pydata/pydata-sphinx-theme/issues/1181 ''' # ....................{ METADATA ~ libs : doc }.................... LIBS_DOCTIME_MANDATORY = ( # Sphinx itself. ( f'sphinx ' f'>={_SPHINX_VERSION_MINIMUM}, ' f'<{_SPHINX_VERSION_MAXIMUM_EXCLUSIVE}' ), # Third-party Sphinx theme. f'{SPHINX_THEME_NAME} <={_SPHINX_THEME_VERSION_MAXIMUM}', # Third-party Sphinx extensions. 'autoapi >=0.9.0', 'sphinxext-opengraph >= 0.7.5', ) ''' **Mandatory developer documentation build-time package dependencies** (i.e., dependencies required to manually build documentation for this package as a developer at the command line) as a tuple of :mod:`setuptools`-specific requirements strings of the format ``{project_name} {comparison1}{version1},...,{comparisonN}{versionN}``. For flexibility, these dependencies are loosely relaxed to enable developers to build with *any* versions satisfying at least the bare minimum. For the same reason, optional documentation build-time package dependencies are omitted. Since our documentation build system emits a non-fatal warning for each missing optional dependency, omitting these optional dependencies here imposes no undue hardships while improving usability. See Also ---------- :data:`LIBS_RUNTIME_OPTIONAL` Further details. ''' #FIXME: For future use, we still preserve an RTD-specific list of requirements. #It's unclear whether we actually require this, however. Consider excising. The #prior approach of pinning exact Sphinx versions failed painfully by #accidentally constraining us to obsolete Sphinx versions known to be broken. LIBS_DOCTIME_MANDATORY_RTD = LIBS_DOCTIME_MANDATORY # LIBS_DOCTIME_MANDATORY_RTD = ( # f'sphinx =={_SPHINX_VERSION_MINIMUM}', # f'{SPHINX_THEME_NAME} =={_SPHINX_THEME_VERSION_MAXIMUM}', # ) ''' **Mandatory Read The Docs (RTD) documentation build-time package dependencies** (i.e., dependencies required to automatically build documentation for this package from the third-party RTD hosting service) as a tuple of :mod:`setuptools`-specific requirements strings of the format ``{project_name} {comparison1}{version1},...,{comparisonN}{versionN}``. For consistency, these dependencies are strictly constrained to force RTD to build against a single well-tested configuration known to work reliably. See Also ---------- :data:`LIBS_RUNTIME_OPTIONAL` Further details. ''' # ....................{ METADATA ~ libs : dev }.................... LIBS_DEVELOPER_MANDATORY = LIBS_TESTTIME_MANDATORY + LIBS_DOCTIME_MANDATORY ''' **Mandatory developer package dependencies** (i.e., dependencies required to develop and meaningfully contribute pull requests for this package) as a tuple of :mod:`setuptools`-specific requirements strings of the format ``{project_name} {comparison1}{version1},...,{comparisonN}{versionN}``. This tuple includes all mandatory test- and documentation build-time package dependencies and is thus a convenient shorthand for those lower-level tuples. See Also ---------- :data:`LIBS_RUNTIME_OPTIONAL` Further details. ''' beartype-0.18.5/beartype/peps/000077500000000000000000000000001461113517100162025ustar00rootroot00000000000000beartype-0.18.5/beartype/peps/__init__.py000066400000000000000000000024431461113517100203160ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype Python Enhancement Proposal (PEP) API.** This subpackage provides a medley of miscellaneous low-level utility functions implementing unofficial (albeit well-tested) runtime support for PEPs lacking official runtime support in CPython's standard library. This subpackage is intended to be used both by downstream third-party packages and the :mod:`beartype` codebase itself. Supported PEPs include: * :pep:`563` (i.e., "Postponed Evaluation of Annotations") via the :func:`resolve_pep563` function. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To avoid polluting the public module namespace, external attributes # should be locally imported at module scope *ONLY* under alternate private # names (e.g., "from argparse import ArgumentParser as _ArgumentParser" rather # than merely "from argparse import ArgumentParser"). #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype.peps._pep563 import ( resolve_pep563 as resolve_pep563, ) beartype-0.18.5/beartype/peps/_pep563.py000066400000000000000000000520311461113517100177360ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype :pep:`563` **resolvers** (i.e., public high-level callables resolving stringified :pep:`563`-compliant type hints implicitly postponed by the active Python interpreter via a ``from __future__ import annotations`` statement at the head of the external user-defined module currently being introspected). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... #FIXME: [DOCOS] Officially document both this and the public "beartype.peps" #submodule, please. #FIXME: Conditionally emit a non-fatal PEP 563-specific warning when the active #Python interpreter targets Python >= 3.10 *AND* the passed callable is nested. # ....................{ IMPORTS }.................... from beartype.roar import BeartypePep563Exception from beartype._check.checkcall import make_beartype_call from beartype._check.forward.fwdmain import resolve_hint from beartype._conf.confcls import ( BEARTYPE_CONF_DEFAULT, BeartypeConf, ) from beartype._data.hint.datahinttyping import TypeStack from beartype._util.cache.pool.utilcachepoolobjecttyped import ( release_object_typed) from beartype._util.func.utilfuncget import get_func_annotations from collections.abc import Callable # ....................{ RESOLVERS }.................... def resolve_pep563( # Mandatory parameters. func: Callable, # Optional parameters. conf: BeartypeConf = BEARTYPE_CONF_DEFAULT, cls_stack: TypeStack = None, ) -> None: ''' Resolve all :pep:`563`-based **postponed annotations** (i.e., strings that when dynamically evaluated as Python expressions yield actual annotations) on the passed callable to their **referents** (i.e., the actual annotations to which those postponed annotations evaluate) if `PEP 563`_ is active for that callable *or* silently reduce to a noop otherwise (i.e., if :pep:`563` is *not* active for that callable). :pep:`563` is active for that callable if the module declaring that callable explicitly enabled :pep:`563` support with a leading dunder importation of the form ``from __future__ import annotations``. If :pep:`563` is active for that callable, then for each type-hint annotating that callable: * If that hint is a string and thus postponed, this function: #. Dynamically evaluates that string within that callable's globals context (i.e., set of all global variables defined by the module declaring that callable). #. Replaces that hint's string value with the expression produced by this dynamic evaluation. * Else, this function preserves that hint as is (e.g., due to that hint that was previously postponed having already been evaluated by a prior decorator). Parameters ---------- func : Callable Callable to resolve postponed annotations on. conf : BeartypeConf, optional Beartype configuration configuring :func:`beartype.beartype` uniquely specific to this callable. Defaults to :data`.BEARTYPE_CONF_DEFAULT`, the default beartype configuration. cls_stack : TypeStack Either: * If that callable is a method of a class, the **type stack** (i.e., tuple of one or more lexically nested classes in descending order of top- to bottom-most lexically nested) such that: * The first item of this tuple is expected to be the **root class** (i.e., top-most class whose lexical scope encloses that callable, typically declared at module scope and thus global). * The last item of this tuple is expected to be the **current class** (i.e., possibly nested class directly containing that method). * Else, that callable is *not* a method of a class. In this case, :data:`None`. Defaults to :data:`None`. Note that this function requires *both* the root and current class to correctly resolve edge cases under :pep:`563`: e.g., .. code-block:: python from __future__ import annotations from beartype import beartype @beartype class Outer(object): class Inner(object): # At this time, the "Outer" class has been fully defined but # is *NOT* yet accessible as a module-scoped attribute. Ergo, # the *ONLY* means of exposing the "Outer" class to the # recursive decoration of this get_outer() method is to # explicitly pass the "Outer" class as the "cls_root" # parameter to all decoration calls. def get_outer(self) -> Outer: return Outer() Note also that nested classes have *no* implicit access to either their parent classes *or* to class variables declared by those parent classes. Nested classes *only* have explicit access to module-scoped classes -- exactly like any other arbitrary objects: e.g., .. code-block:: python class Outer(object): my_str = str class Inner(object): # This induces a fatal compile-time exception resembling: # NameError: name 'my_str' is not defined def get_str(self) -> my_str: return 'Oh, Gods.' Nonetheless, this tuple *must* contain all of those nested classes lexically containing the passed method. Why? Because this function resolves local attributes defined in the body of the callable on the current call stack lexically containing those nested classes (if any) by treating the length of this tuple as the total number of classes lexically nesting the current class. In short, just pass everything. Raises ---------- BeartypePep563Exception If either: * That callable is *not* a pure-Python callable (e.g., is C-based). * Evaluating a postponed annotation on that callable raises an exception (e.g., due to that annotation referring to local state inaccessible in this deferred context). ''' # ..................{ NOOP }.................. # Dictionary to be returned, mapping from the name of each annotated # parameter and return of the passed callable to the non-string type hint # resolved from the string type hint annotating that parameter or return -- # raising an exception if that callable is *NOT* a pure-Python callable. # # Note that the "func.__annotations__" dictionary *CANNOT* be safely # directly assigned to below, as the loop performing that assignment below # necessarily iterates over that dictionary. As with most languages, Python # containers cannot be safely mutated while being iterated. arg_name_to_hint = get_func_annotations( func=func, exception_cls=BeartypePep563Exception, exception_prefix='Callable ', ) # If that callable is unannotated, silently reduce to a noop. if not arg_name_to_hint: return # Else, that callable is annotated by one or more type hints. # If that callable was *NOT* subject to PEP 563-compliant postponement of # type hints under the standard "from __future__ import annotations" import, # silently reduce to a noop. # # Note that there exist numerous means of detecting PEP 563. This approach: # * Is the least efficient, requiring O(n) iteration for n the number of # type hints annotating that callable. # * Is the most reliable, detecting PEP 563 regardless of whether: # * That callable was dynamically synthesized in-memory *OR* physically # defined in an on-disk source module. In the latter case, this # detection heuristic could statically analyze the on-disk source code # underlying that callable for an import of the form: # from __future__ import annotations # In the former case, however, that analysis is infeasible. # * The "__future__.annotations" singleton object is a global attribute of # the module defining that callable. This simplistic test fails under # numerous edge cases, including if: # * That callable was dynamically synthesized in-memory, in which case # that callable may have *NO* such module. # * That callable was physically defined in an on-disk source module # that enabled PEP 563 but which then maliciously deleted the # "__future__.annotations" singleton object from module scope: e.g., # from __future__ import annotations # del annotations # Yes, that is valid Python. Yes, Python continues to enable PEP 563 # for that module despite that module deleting the "annotations" # attribute from module scope. Yes, we're facepalming ourselves. # # Since reliability is *FAR* more important than efficiency, this function # adopts the detection heuristic that is the most inefficient and reliable. # # For the name of each annotated parameter and return of the passed callable # and the type hint annotating that parameter or return... for arg_name, hint in arg_name_to_hint.items(): # If this hint is *NOT* stringified, this hint was either: # * Never postponed under PEP 563 (i.e., the module defining that # callable did *NOT* import "from __future__ import annotations"). # * Previously postponed under PEP 563 (i.e., the module defining that # callable imported "from __future__ import annotations") but has # since been resolved into a non-string type hint by a competing # runtime type-introspector, possibly including @beartype itself. # # In either case, PEP 563 is now disabled for this hint. But PEP 563 is # a module-scoped effect that universally applies to *ALL* type hints # annotating *ALL* callables of a module. If PEP 563 is disabled for one # hint of a callable, then PEP 563 must necessarily be disabled for all # hints of that same callable. In this case, reduce to a noop. if not isinstance(hint, str): return # Else, this hint is stringified. In this case, this hint was either: # * Postponed under PEP 563 (i.e., the module defining that callable # imported "from __future__ import annotations"). # * Never postponed under PEP 563 (i.e., the module defining that # callable did *NOT* import "from __future__ import annotations") but # was instead simply a PEP 484-compliant forward reference (e.g., # "def muh_func(muh_arg: 'MuhClass'): ..."). # # Differentiating these two cases is infeasible! Python's standard # library failed to ship solutions to this or any other outstanding # runtime issues with PEP 563. Instead, we pretend everything will be # okay by silently ignoring the latter case. Doing so largely suffices # but can technically yield a false positive. This is why PEP 563 should # have failed Python's peer review process. Of course, it passed # instead. In short: "Trust me, bro." # All type hints annotating the passed callable are now guaranteed to have # been stringified. For simplicity, we assume these hints were stringified # automatically by PEP 563 rather than manually by user typing. # ..................{ LOCALS }.................. # Beartype call metadata describing the passed callable. bear_call = make_beartype_call( func=func, conf=conf, cls_stack=cls_stack, ) # Make a shallow copy of the dictionary to be returned. Why? Because the # "func.__annotations__" dictionary *CANNOT* be safely directly assigned to # below, as the loop performing that assignment below necessarily iterates # over that dictionary. As with most languages, Python containers cannot be # safely mutated while being iterated. arg_name_to_hint = arg_name_to_hint.copy() # ..................{ RESOLUTION }.................. # For the name of each annotated parameter and return of the passed callable # and the stringified type hint annotating that parameter or return... # # Note that refactoring this iteration into a dictionary comprehension would # be both: # * Largely infeasible (e.g., due to the need to raise human-readable # exceptions on evaluating invalid type hints). # * largely pointless (e.g., due to dictionary comprehensions being either # no faster or even slower than explicit iteration for small dictionary # sizes, as "func.__annotations__" usually is). for arg_name, hint in arg_name_to_hint.items(): # If this hint is stringified, resolve this stringified type hint to the # non-string type hint to which this string refers. # # Note that this test could technically yield a false positive in the # unlikely edge case that this hint was previously postponed but has # since been replaced in-place by its referent that is itself a PEP # 484-compliant forward reference matching the PEP 563 format without # actually being a PEP 563-postponed type hint. Since PEP 563 failed to # provide solutions to this or any other outstanding runtime issues with # PEP 563, there is *NOTHING* we can differentiate these two edge cases. # Instead, we pretend everything will be okay. "Trust me, bro!" if isinstance(hint, str): arg_name_to_hint[arg_name] = resolve_hint( hint=hint, bear_call=bear_call, exception_cls=BeartypePep563Exception, ) # Else, this hint is *NOT* stringified. In this case, preserve this hint # as is. # ..................{ RETURN }.................. # Release this beartype call metadata back to its object pool. release_object_typed(bear_call) # Attempt to... try: # Atomically (i.e., all-at-once) replace that callable's postponed # annotations with these resolved annotations for safety and efficiency. # # While the @beartype decorator goes to great lengths to preserve the # originating "__annotations__" dictionary as is, PEP 563 is # sufficiently expensive, non-trivial, and general-purpose to implement # that generally resolving postponed annotations for downstream # third-party callers is justified. Everyone benefits from replacing # useless postponed annotations with useful real annotations; so, do so. func.__annotations__ = arg_name_to_hint # If doing so fails with an exception resembling the following, then that # callable is *NOT* a pure-Python callable but rather a C-based decorator # object of some sort (e.g., class, property, or static method descriptor): # AttributeError: 'method' object has no attribute '__annotations__' # # C-based decorator objects define a read-only "__annotations__" dunder # attribute that proxies an original writeable "__annotations__" dunder # attribute of the pure-Python callables they originally decorated. Ergo, # detecting this edge case is non-trivial and most most easily deferred to # this late time. While non-ideal, simplicity >>>> idealism in this case. except AttributeError: # For the name of each annotated parameter and return of that callable # and the destringified type hint annotating this parameter or return, # overwrite the stringified type hint originally annotating this # parameter or return with this destringified type hint. # # Note that: # * The above assignment is an efficient O(1) operation and thus # intentionally performed first. # * This iteration-based assignment is an inefficient O(n) operation # (where "n" is the number of annotated parameters and returns of that # callable) and thus intentionally performed last here. for arg_name, arg_hint in arg_name_to_hint.items(): func.__annotations__[arg_name] = arg_hint # print( # f'{func.__name__}() PEP 563-postponed annotations resolved:' # f'\n\t------[ POSTPONED ]------\n\t{func_hints_postponed}' # f'\n\t------[ RESOLVED ]------\n\t{func_hints_resolved}' # ) # ....................{ PRIVATE ~ resolvers }.................... #FIXME: We currently no longer require this. See above for further commentary. # from beartype.roar import BeartypeDecorHintPepException # from beartype._util.cache.pool.utilcachepoollistfixed import FIXED_LIST_SIZE_MEDIUM # # def _die_if_hint_repr_exceeds_child_limit( # hint_repr: str, pith_label: str) -> None: # ''' # Raise an exception if the passed machine-readable representation of an # arbitrary annotation internally exceeds the **child limit** (i.e., maximum # number of nested child type hints listed as subscripted arguments of # PEP-compliant type hints) permitted by the :func:`beartype.beartype` # decorator. # # The :mod:`beartype` decorator internally traverses over these nested child # types of the parent PEP-compliant type hint produced by evaluating this # string representation to its referent with a breadth-first search (BFS). # For efficiency, this search is iteratively implemented with a cached # **fixed list** (i.e., # :class:`beartype._util.cache.pool.utilcachepoollistfixed.FixedList` # instance) rather than recursively implemented with traditional recursion. # Since the size of this list is sufficiently large to handle all uncommon # *and* uncommon edge cases, this list suffices for *all* PEP-compliant type # hints of real-world interest. # # Nonetheless, safety demands that we guarantee this by explicitly raising an # exception when the internal structure of this string suggests that the # resulting PEP-compliant type hint will subsequently violate this limit. # This has the convenient side effect of optimizing that BFS, which may now # unconditionally insert child hints into arbitrary indices of that cached # fixed list without having to explicitly test whether each index exceeds the # fixed length of that list. # # Caveats # ---------- # **This function is currently irrelevant.** Why? Because all existing # implementations of the :mod:`typing` module are sufficiently # space-consumptive that they already implicitly prohibit deep nesting of # PEP-compliant type hints. See commentary in the # :mod:`beartype_test.a00_unit.data.pep.pep563.data_pep563_poem` submodule for appalling details. # Ergo, this validator could technically be disabled. Indeed, if this # validator actually incurred any measurable costs, it *would* be disabled. # Since it doesn't, this validator has preserved purely for forward # compatibility with some future revision of the :mod:`typing` module that # hopefully improves that module's horrid space consumption. # # Parameters # ---------- # hint_repr : str # Machine-readable representation of this annotation, typically but *not* # necessarily as a :pep:`563`-formatted postponed string. # pith_label : str # Human-readable label describing the callable parameter or return value # annotated by this string. # # Raises # ---------- # BeartypeDecorHintPepException # If this representation internally exceeds this limit. # ''' # assert isinstance(hint_repr, str), f'{repr(hint_repr)} not string.' # # # Total number of hints transitively encapsulated in this hint (i.e., the # # total number of all child hints of this hint as well as this hint # # itself), defined as the summation of... # hints_num = ( # # Number of parent PEP-compliant type hints nested in this hint, # # including this hint itself *AND*... # hint_repr.count('[') + # # Number of child type hints (both PEP-compliant type hints and # # non-"typing" types) nested in this hint, excluding the last child # # hint subscripting each parent PEP-compliant type hint *AND*... # hint_repr.count(',') + # # Number of last child hints subscripting all parent PEP-compliant type # # hints. # hint_repr.count(']') # ) # # # If this number exceeds the fixed length of the cached fixed list with # # which the @beartype decorator traverses this hint, raise an exception. # if hints_num >= FIXED_LIST_SIZE_MEDIUM: # raise BeartypeDecorHintPepException( # f'{pith_label} hint representation "{hint_repr}" ' # f'contains {hints_num} subscripted arguments ' # f'exceeding maximum limit {FIXED_LIST_SIZE_MEDIUM-1}.' # ) beartype-0.18.5/beartype/plug/000077500000000000000000000000001461113517100162025ustar00rootroot00000000000000beartype-0.18.5/beartype/plug/__init__.py000066400000000000000000000017051461113517100203160ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype plugin API.** This submodule publishes a medley of attributes enabling users to extend :mod:`beartype` with user-defined runtime behaviours. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To avoid polluting the public module namespace, external attributes # should be locally imported at module scope *ONLY* under alternate private # names (e.g., "from argparse import ArgumentParser as _ArgumentParser" rather # than merely "from argparse import ArgumentParser"). #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype.plug._plughintable import ( BeartypeHintable as BeartypeHintable, ) beartype-0.18.5/beartype/plug/_plughintable.py000066400000000000000000000332411461113517100213740ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype plugin mixin hierarchy** (i.e., public classes intended to be subclassed as mixins by users extending :mod:`beartype` with third-party runtime behaviours). Most of the public attributes defined by this private submodule are explicitly exported to external users in our top-level :mod:`beartype.plug.__init__` submodule. This private submodule is *not* intended for direct importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import ( Optional, Tuple, ) from beartype._util.cache.utilcachecall import callable_cached # ....................{ MIXINS }.................... class BeartypeHintable(object): ''' **Beartype hintable mixin** (i.e., class intended to be subclassed as a mixin by user-defined classes extending :mod:`beartype` with class-specific runtime type-checking via the :mod:`beartype`-specific :meth:`__beartype_hint__` method). Usage ---------- **You are encouraged but not required to subclass this mixin.** Doing so publicly declares your intention to implement this abstract method and then raises a :exc:`NotImplementedError` exception when you fail to do so, improving the quality of your codebase with a simple contractual guarantee. This mixin does *not* require a custom metaclass and is thus safely subclassable by everything -- including your own classes. **Beartype internally ignores this mixin.** This mixin exists *only* to improve the quality of your codebase. Instead, beartype detects type hints defining :meth:`__beartype_hint__` methods via the :func:`getattr` builtin and then replaces those hints with the new type hints returned by those methods. In pseudo-code, this logic crudely resembles: e.g., .. code-block:: python # "__beartype_hint__" attribute of this type hint if any *OR* "None". beartype_hinter = getattr(hint, '__beartype_hint__') # If this hint defines this method, replace this hint with the new type # hint created and returned by this method. if callable(beartype_hinter): hint = beartype_hinter() You care about this, because this means that: * You can trivially monkey-patch third-party classes *not* under your direct control with :meth:`__beartype_hint__` methods, constraining those classes with runtime type-checking implemented by you! * :mod:`beartype` accepts *any* arbitrary objects defining :meth:`__beartype_hint__` methods as valid type hints -- including objects that are neither classes nor PEP-compliant! Of course, doing so would render your code incompatible with static type-checkers and thus IDEs. That's a bad thing. Nonetheless, this API enables you to do bad things. With great plugin power comes great user responsibility. ''' # ....................{ METHODS }.................... @classmethod def __beartype_hint__(cls) -> object: ''' **Beartype type hint transform** (i.e., :mod:`beartype`-specific dunder class method returning a new type hint, which typically constrains this class with additional runtime type-checking). ''' raise NotImplementedError( # pragma: no cover 'Abstract base class method ' 'BeartypeHintable.__beartype_hint__() undefined.' ) # ....................{ TESTERS }.................... #FIXME: Document us up, please. #FIXME: Unit test us up, please. #FIXME: Call us elsewhere, please. @callable_cached def is_hint_beartypehintable(hint: object) -> bool: # Return true only if this hint defines the "__beartype_hint__" attribute. return hasattr(hint, '__beartype_hint__') # ....................{ TRANSFORMERS ~ more than meets the }.................... # ....................{ eye }.................... #FIXME: Document us up, please. #FIXME: Unit test us up, please. #FIXME: Significant complications exist suggesting that we should immediately #release beartype 0.12.0 and contemplate implementing this later: #* The "beartype._check.error" subpackage will need to implement a comparable # mechanism as the "beartype._check.code" subpackage for detecting and avoiding # recursion in this reduction. Curiously, "beartype._check.error" only ever # calls the sanify_hint_child() sanifier in a single place. That simplifies # things a bit. Still, we'll need to add a similar "set" somewhere in that # subpackage tracking which "BeartypeHintable" objects have already been # reduced. #* Even ignoring that, detecting and avoiding recursion in # "beartype._check.code" alone will be non-trivial. We can't pass the original # presanified type hint to the make_check_expr() factory, because that hint has # *NOT* been coerced into a memoizable singleton (e.g., think PEP 585). That # means the caller needs to pass either: # * A boolean "is_hint_beartypehintable" parameter that is true only if the # presanified root type hint was a "BeartypeHintable". # * A "beartypehintables: Optional[set]" parameter that is a non-empty set # containing the presanified root type hint if that hint was a # "BeartypeHintable" *OR* "None" otherwise. # The caller can trivially detect "BeartypeHintable" hints by calling the # is_hint_beartypehintable() tester defined above. That's *NOT* the issue, # thankfully. The issue is that we call sanify_*_root() functions in exactly # three different places. We'll now need to duplicate this detection of # "BeartypeHintable" hints across those three different places. Is this # something we *REALLY* want to do? Is there truly no better way? # #Examining the code calling sanify_*_root() functions, it superficially looks #like we might want to consider *NO LONGER DEFINING OR CALLING* sanify_*_root() #functions. Like, seriously. The logic performed by those functions has become #trivial. They're practically one-liners. That said, sanify_hint_child() is #still extremely useful and should be preserved exactly as is. Consider: #* High-level functions calling sanify_*_root() functions should instead just # call either coerce_func_hint_root() or coerce_hint_root() based on context. # Those functions should *NOT* call reduce_hint() anymore. #* Excise up all sanify_*_root() functions. #* The make_check_expr() function should now call: # * On the passed root type hint: # if is_hint_beartypehintable(hint_root): # hint_parent_beartypehintables = {hint_root,} # hint_root = transform_hint_beartypehintable(hint_root) # # hint_root = reduce_hint(hint_root) # * On each child type hint: # # This exact logic is likely to be duplicated into # # "beartype._check.error". That's not particularly a problem -- just # # something worth noting. One approach to preserving DRY here would be # # to shift this "if" statement into sanify_hint_child(). Of course, # # everything then becomes non-trivial, because we would then need to # # both pass *AND* return "hint_parent_beartypehintables" sets to and # # from the sanify_hint_child() function. *sigh* # if ( # is_hint_beartypehintable(hint_child) and # hint_child not in hint_parent_beartypehintables # ): # if hint_parent_beartypehintables is None: # hint_parent_beartypehintables = {hint_child,} # else: # hint_parent_beartypehintables |= {hint_child,} # # hint_child = transform_hint_beartypehintable(hint_child) # # hint_child = sanify_hint_child(hint_root) #FIXME: Wow. What a fascinatingly non-trivial issue. The above doesn't work, #either. Why? Two reasons: #* sanify_*_root() functions *MUST* continue to perform reduction -- including # calling both reduce_hint() and transform_hint_beartypehintable(). Why? Because # reduction *MUST* be performed before deciding "is_hint_ignorable", which # *MUST* be decided before generating code. This is non-optional. #* transform_hint_beartypehintable() *CANNOT* be performed in either: # * reduce_hint(), because reduce_hint() is memoized but # transform_hint_beartypehintable() is non-memoizable by definition. # * coerce_*_hint(), because coerce_*_hint() is permanently applied to # "__annotations__" but transform_hint_beartypehintable() should *NEVER* be. # #Altogether, this suggests that: #* All sanify_*() functions *MUST* call transform_hint_beartypehintable() # directly, outside of calls to either reduce_hint() and coerce_*_hint(). #* Frozensets should be used. Doing so enables memoization, if we wanted. #* Call transform_hint_beartypehintable() from sanify_hint_child(), whose # signature *MUST* be augmented accordingly (i.e., to both accept and return # "hints_parent_beartypehintable: Optional[frozenset]"). #* Call transform_hint_beartypehintable() from sanify_*hint_root(), whose # signatures *MUST* be augmented accordingly (i.e., to additionally return # "Optional[frozenset]"). #* Augment make_check_expr() to: # * Accept an additional # "hints_parent_beartypehintable: Optional[frozenset]," parameter. # * Add yet another new entry to each "hint_meta" FixedList as follows: # * Define a new "HINT_META_INDEX_HINTS_PARENT_BEARTYPEHINTABLE" constant. # * For the root "hint_meta", initialize the value of: # hint_root_meta[HINT_META_INDEX_HINTS_PARENT_BEARTYPEHINTABLE] = ( # hints_parent_beartypehintable) #* Restore unit testing in "_data_nonpepbeartype", please. # #That should more or less do it, folks. Phew! It's still sufficiently #non-trivial that we want to defer this until *AFTER* beartype 0.12.0, though. #FIXME: Unit test us up, please. #FIXME: Document us up, please. @callable_cached def transform_hint_beartypehintable( hint: object, hints_parent_beartypehintable: Optional[frozenset], ) -> Tuple[object, Optional[frozenset]]: # ..................{ PLUGIN }.................. # Beartype plugin API. Respect external user-defined classes satisfying the # beartype plugin API *BEFORE* handling these classes in any way. # If this hint has already been transformed by a prior call to this # function, preserve this hint as is. Doing so avoids infinite recursion and # is, indeed, the entire point of the "hints_parent_beartypehintable" set. if ( hints_parent_beartypehintable and hint in hints_parent_beartypehintable ): return (hint, hints_parent_beartypehintable) # Else, this hint has *NOT* yet been transformed by such a call. # Beartype-specific "__beartype_hint__" attribute defined by this hint if # any *OR* "None" otherwise. # # Note that usage of the low-level getattr() builtin is intentional. *ALL* # alternative higher-level approaches suffer various deficits, including: # * Obstructing monkey-patching. The current approach trivializes # monkey-patching by third parties, enabling users to readily add # __beartype_hint__() support to third-party packages *NOT* under their # direct control. Alternative higher-level approaches obstruct that by # complicating (or just outright prohibiting) monkey-patching. # * Abstract base classes (ABCs) assume that hints that are classes are # issubclassable (i.e., safely passable as the first arguments of the # issubclass() builtin). Sadly, various real-world hints that are classes # are *NOT* issubclassable. This includes the core # "typing.NDArray[{dtype}]" type hints, astonishingly. Of course, even # this edge case could be surmounted by explicitly testing for # issubclassability (e.g., by calling our existing # is_type_issubclassable() tester); since that tester internally leverages # the inefficient Easier to Ask for Forgiveness than Permission (EAFP) # paradigm, doing so would impose a measurable performance penalty. This # only compounds the monkey-patching complications that an ABC imposes. # * PEP 544-compliant protocols assume that the active Python interpreter # supports PEP 544, which Python 3.7 does not. While Python 3.7 has # probably hit its End of Life (EOL) by the time you are reading this, # additional issue exist. On the one hand, protocols impose even *MORE* of # a performance burden than ABCs. On the other hand, protocols ease the # user-oriented burden of monkey-patching. # # In short, this low-level approach effectively imposes *NO* burdens at all. # There exists *NO* reason to prefer higher-level alternatives. __beartype_hint__ = getattr(hint, '__beartype_hint__', None) # If this hint does *NOT* define the "__beartype_hint__" attribute, preserve # this hint as is. if __beartype_hint__ is None: return (hint, hints_parent_beartypehintable) # Else, this hint defines the "__beartype_hint__" attribute. #FIXME: Define a new private exception type, please. # # If this attribute is *NOT* callable, raise an exception. # if not callable(beartypehintable_reducer): # raise SomeExceptiot(...) # # Else, this attribute is callable. # Replace this hint with the new type hint returned by this callable. hint = __beartype_hint__() if hints_parent_beartypehintable is None: hints_parent_beartypehintable = frozenset((hint,)) else: #FIXME: Unsure if this works for frozensets. Probably not. *sigh* hints_parent_beartypehintable |= {hint,} # Return this transformed hint. return (hint, hints_parent_beartypehintable) beartype-0.18.5/beartype/py.typed000066400000000000000000000000001461113517100167200ustar00rootroot00000000000000beartype-0.18.5/beartype/roar/000077500000000000000000000000001461113517100161765ustar00rootroot00000000000000beartype-0.18.5/beartype/roar/__init__.py000066400000000000000000000244671461113517100203240ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype exception and warning hierarchies.** This submodule publishes a hierarchy of: * :mod:`beartype`-specific exceptions raised both by: * The :func:`beartype.beartype` decorator at decoration and call time. * Other public submodules and subpackages at usage time, including user-defined data validators imported from the :mod:`beartype.vale` subpackage. * :mod:`beartype`-specific warnings emitted at similar times. Hear :mod:`beartype` roar as it efficiently checks types, validates data, and raids native beehives for organic honey. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To prevent "mypy --no-implicit-reexport" from raising literally # hundreds of errors at static analysis time, *ALL* public attributes *MUST* be # explicitly reimported under the same names with "{exception_name} as # {exception_name}" syntax rather than merely "{exception_name}". Yes, this is # ludicrous. Yes, this is mypy. For posterity, these failures resemble: # beartype/_cave/_cavefast.py:47: error: Module "beartype.roar" does not # explicitly export attribute "BeartypeCallUnavailableTypeException"; # implicit reexport disabled [attr-defined] #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To avoid polluting the public module namespace, external attributes # should be locally imported at module scope *ONLY* under alternate private # names (e.g., "from argparse import ArgumentParser as _ArgumentParser" rather # than merely "from argparse import ArgumentParser"). #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Public exception hierarchy. from beartype.roar._roarexc import ( # Exceptions. BeartypeException as BeartypeException, BeartypeCaveException as BeartypeCaveException, BeartypeCaveNoneTypeOrException as BeartypeCaveNoneTypeOrException, BeartypeCaveNoneTypeOrKeyException as BeartypeCaveNoneTypeOrKeyException, BeartypeCaveNoneTypeOrMutabilityException as BeartypeCaveNoneTypeOrMutabilityException, BeartypeClawException as BeartypeClawException, BeartypeClawHookException as BeartypeClawHookException, BeartypeClawHookUnpackagedException as BeartypeClawHookUnpackagedException, BeartypeClawImportException as BeartypeClawImportException, BeartypeClawImportAstException as BeartypeClawImportAstException, BeartypeClawImportConfException as BeartypeClawImportConfException, BeartypeConfException as BeartypeConfException, BeartypeConfParamException as BeartypeConfParamException, BeartypeConfShellVarException as BeartypeConfShellVarException, BeartypeDoorException as BeartypeDoorException, BeartypeDoorNonpepException as BeartypeDoorNonpepException, BeartypeDoorPepException as BeartypeDoorPepException, BeartypeDoorPepUnsupportedException as BeartypeDoorPepUnsupportedException, BeartypeDecorException as BeartypeDecorException, BeartypeDecorWrappeeException as BeartypeDecorWrappeeException, BeartypeDecorWrapperException as BeartypeDecorWrapperException, BeartypeDecorHintException as BeartypeDecorHintException, BeartypeDecorHintForwardRefException as BeartypeDecorHintForwardRefException, BeartypeDecorHintNonpepException as BeartypeDecorHintNonpepException, BeartypeDecorHintNonpepNumpyException as BeartypeDecorHintNonpepNumpyException, BeartypeDecorHintNonpepPanderaException as BeartypeDecorHintNonpepPanderaException, BeartypeDecorHintPepException as BeartypeDecorHintPepException, BeartypeDecorHintPepSignException as BeartypeDecorHintPepSignException, BeartypeDecorHintPepUnsupportedException as BeartypeDecorHintPepUnsupportedException, BeartypeDecorHintPep484Exception as BeartypeDecorHintPep484Exception, BeartypeDecorHintPep484585Exception as BeartypeDecorHintPep484585Exception, BeartypeDecorHintPep544Exception as BeartypeDecorHintPep544Exception, BeartypeDecorHintPep557Exception as BeartypeDecorHintPep557Exception, BeartypeDecorHintPep585Exception as BeartypeDecorHintPep585Exception, BeartypeDecorHintPep586Exception as BeartypeDecorHintPep586Exception, BeartypeDecorHintPep591Exception as BeartypeDecorHintPep591Exception, BeartypeDecorHintPep593Exception as BeartypeDecorHintPep593Exception, BeartypeDecorHintPep604Exception as BeartypeDecorHintPep604Exception, BeartypeDecorHintPep647Exception as BeartypeDecorHintPep647Exception, BeartypeDecorHintPep673Exception as BeartypeDecorHintPep673Exception, BeartypeDecorHintPep695Exception as BeartypeDecorHintPep695Exception, BeartypeDecorHintPep3119Exception as BeartypeDecorHintPep3119Exception, BeartypeDecorHintTypeException as BeartypeDecorHintTypeException, BeartypeDecorParamException as BeartypeDecorParamException, BeartypeDecorParamNameException as BeartypeDecorParamNameException, BeartypeCallException as BeartypeCallException, BeartypeCallUnavailableTypeException as BeartypeCallUnavailableTypeException, BeartypeCallHintException as BeartypeCallHintException, BeartypeCallHintForwardRefException as BeartypeCallHintForwardRefException, BeartypeKindException as BeartypeKindException, BeartypeKindFrozenDictException as BeartypeKindFrozenDictException, BeartypeHintOverridesException as BeartypeHintOverridesException, BeartypePepException as BeartypePepException, BeartypePep563Exception as BeartypePep563Exception, BeartypePlugException as BeartypePlugException, BeartypePlugInstancecheckStrException as BeartypePlugInstancecheckStrException, BeartypeValeException as BeartypeValeException, BeartypeValeSubscriptionException as BeartypeValeSubscriptionException, BeartypeValeValidationException as BeartypeValeValidationException, # Violations (i.e., exceptions raised during runtime type-checking). BeartypeCallHintViolation as BeartypeCallHintViolation, BeartypeCallHintParamViolation as BeartypeCallHintParamViolation, BeartypeCallHintReturnViolation as BeartypeCallHintReturnViolation, BeartypeDecorHintParamDefaultViolation as BeartypeDecorHintParamDefaultViolation, BeartypeDoorHintViolation as BeartypeDoorHintViolation, ) # Public warning hierarchy. from beartype.roar._roarwarn import ( BeartypeWarning as BeartypeWarning, BeartypeClawWarning as BeartypeClawWarning, BeartypeClawDecorWarning as BeartypeClawDecorWarning, BeartypeConfWarning as BeartypeConfWarning, BeartypeConfShellVarWarning as BeartypeConfShellVarWarning, BeartypeDecorHintWarning as BeartypeDecorHintWarning, BeartypeDecorHintParamDefaultForwardRefWarning as BeartypeDecorHintParamDefaultForwardRefWarning, BeartypeDecorHintPepWarning as BeartypeDecorHintPepWarning, BeartypeDecorHintPepDeprecationWarning as BeartypeDecorHintPepDeprecationWarning, BeartypeDecorHintPep585DeprecationWarning as BeartypeDecorHintPep585DeprecationWarning, BeartypeDecorHintPep613DeprecationWarning as BeartypeDecorHintPep613DeprecationWarning, BeartypeDecorHintNonpepWarning as BeartypeDecorHintNonpepWarning, BeartypeDecorHintNonpepNumpyWarning as BeartypeDecorHintNonpepNumpyWarning, BeartypeModuleWarning as BeartypeModuleWarning, BeartypeModuleNotFoundWarning as BeartypeModuleNotFoundWarning, BeartypeModuleAttributeNotFoundWarning as BeartypeModuleAttributeNotFoundWarning, BeartypeModuleUnimportableWarning as BeartypeModuleUnimportableWarning, BeartypeValeWarning as BeartypeValeWarning, BeartypeValeLambdaWarning as BeartypeValeLambdaWarning, ) # ....................{ DEPRECATIONS }.................... def __getattr__(attr_deprecated_name: str) -> object: ''' Dynamically retrieve a deprecated attribute with the passed unqualified name from this submodule and emit a non-fatal deprecation warning on each such retrieval if this submodule defines this attribute *or* raise an exception otherwise. The Python interpreter implicitly calls this :pep:`562`-compliant module dunder function under Python >= 3.7 *after* failing to directly retrieve an explicit attribute with this name from this submodule. Since this dunder function is only called in the event of an error, neither space nor time efficiency are a concern here. Parameters ---------- attr_deprecated_name : str Unqualified name of the deprecated attribute to be retrieved. Returns ---------- object Value of this deprecated attribute. Warns ---------- :class:`DeprecationWarning` If this attribute is deprecated. Raises ---------- :exc:`AttributeError` If this attribute is unrecognized and thus erroneous. ''' # Isolate imports to avoid polluting the module namespace. from beartype._util.module.utilmoddeprecate import deprecate_module_attr # Return the value of this deprecated attribute and emit a warning. return deprecate_module_attr( attr_deprecated_name=attr_deprecated_name, attr_deprecated_name_to_nondeprecated_name={ 'BeartypeAbbyException': ( 'BeartypeDoorException'), 'BeartypeAbbyHintViolation': ( 'BeartypeDoorHintViolation'), 'BeartypeAbbyTesterException': ( 'BeartypeDoorException'), 'BeartypeCallHintPepException': ( 'BeartypeCallHintViolation'), 'BeartypeCallHintPepParamException': ( 'BeartypeCallHintParamViolation'), 'BeartypeCallHintPepReturnException': ( 'BeartypeCallHintReturnViolation'), 'BeartypeDecorHintNonPepException': ( 'BeartypeDecorHintNonpepException'), 'BeartypeDecorHintNonPepNumPyException': ( 'BeartypeDecorHintNonpepNumpyException'), 'BeartypeDecorHintPep563Exception': ( 'BeartypePep563Exception'), 'BeartypeDecorHintPepDeprecatedWarning': ( 'BeartypeDecorHintPepDeprecationWarning'), 'BeartypeDecorPepException': ( 'BeartypePepException'), }, attr_nondeprecated_name_to_value=globals(), ) beartype-0.18.5/beartype/roar/_roarexc.py000066400000000000000000001610311461113517100203540ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **exception hierarchy** (i.e., public and private exception subclasses raised at decoration, call, and usage time by :mod:`beartype`). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To avoid polluting the public module namespace, external attributes # should be locally imported at module scope *ONLY* under alternate private # names (e.g., "from argparse import ArgumentParser as _ArgumentParser" rather # than merely "from argparse import ArgumentParser"). #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from abc import ABCMeta as _ABCMeta # ....................{ PRIVATE ~ mixins }.................... class _BeartypeHintForwardRefExceptionMixin(Exception, metaclass=_ABCMeta): ''' Mixin of all **beartype forward reference exceptions** (i.e., exceptions concerning parent type hints containing one or more forward references to child type hints that have yet to be declared). This mixin enables internal logic throughout the :mod:`beartype` codebase to conveniently, efficiently, and transparently handle *all* forward reference exceptions -- including: * :exc:`.BeartypeCallHintForwardRefException`. * :exc:`.BeartypeDecorHintForwardRefException`. ''' pass # ....................{ SUPERCLASS }.................... class BeartypeException(Exception, metaclass=_ABCMeta): ''' Abstract base class of all **beartype exceptions.** Instances of subclasses of this exception are raised either: * At decoration time from the :func:`beartype.beartype` decorator. * At call time from the new callable generated by the :func:`beartype.beartype` decorator to wrap the original callable. ''' # ..................{ INITIALIZERS }.................. def __init__(self, message: str) -> None: ''' Initialize this exception. This constructor (in order): #. Passes all passed arguments as is to the superclass constructor. #. Sanitizes the fully-qualified module name of this exception from the private ``"beartype.roar._roarexc"`` submodule to the public ``"beartype.roar"`` subpackage to both improve the readability of exception messages and discourage end users from accessing this private submodule. By default, Python emits less readable and dangerous exception messages resembling: beartype.roar._roarexc.BeartypeCallHintParamViolation: @beartyped quote_wiggum_safer() parameter lines=[] violates type hint typing.Annotated[list[str], Is[lambda lst: bool(lst)]], as value [] violates validator Is[lambda lst: bool(lst)]. ''' assert isinstance(message, str), ( f'{repr(message)} not exception message.') # If... # # Note that this logic unavoidably duplicates the body of the existing # beartype._util.text.utiltextmunge.uppercase_str_char_first() function # for safety. Attempting to call *ANY* beartype-specific callable # (including that function) from within an exception initializer would # invite a shocking calamity that would surely shatter the whole world. if ( # This message contains at least two characters *AND*... len(message) >= 2 and # The first character of this message is lowercase... message[0].islower() ): # Then uppercase only this character for readability. message = f'{message[0].upper()}{message[1:]}' # Defer to the superclass constructor. super().__init__(message) # Sanitize the fully-qualified module name of the class of this # exception. See the docstring for justification. self.__class__.__module__ = 'beartype.roar' # print(f'{self.__class__.__name__}: {message}') # ....................{ DECORATOR }.................... class BeartypeDecorException(BeartypeException): ''' Abstract base class of all **beartype decorator exceptions.** Instances of subclasses of this exception are raised at decoration time from the :func:`beartype.beartype` decorator. ''' pass # ....................{ DECORATOR ~ wrapp[ee|er] }.................... class BeartypeDecorWrappeeException(BeartypeDecorException): ''' **Beartype decorator wrappee exception.** This exception is raised at decoration time from the :func:`beartype.beartype` decorator when passed a **wrappee** (i.e., object to be decorated by this decorator) of invalid type. ''' pass class BeartypeDecorWrapperException(BeartypeDecorException): ''' **Beartype decorator parse exception.** This exception is raised at decoration time from the :func:`beartype.beartype` decorator on accidentally generating an **invalid wrapper** (i.e., syntactically invalid new callable to wrap the original callable). ''' pass # ....................{ DECORATOR ~ hint }.................... class BeartypeDecorHintException(BeartypeDecorException): ''' Abstract base class of all **beartype decorator type hint exceptions.** Instances of subclasses of this exception are raised at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated by one or more **invalid type hints** (i.e., annotations that are neither PEP-compliant nor PEP-compliant type hints supported by this decorator). ''' pass class BeartypeDecorHintForwardRefException( BeartypeDecorHintException, _BeartypeHintForwardRefExceptionMixin): ''' **Beartype decorator forward reference type hint exception.** This exception is raised at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated by an **invalid forward reference type hint** (i.e., string whose value is the name of a user-defined class that has yet to be declared). ''' pass class BeartypeDecorHintTypeException(BeartypeDecorHintException): ''' **Beartype decorator class type hint exception.** This exception is raised at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated by an **invalid class type hint** (i.e., class invalid for use as a type hint, typically due to failing to support runtime :func:`isinstance` calls). ''' pass # ....................{ DECORATOR ~ hint : non-pep }.................... class BeartypeDecorHintNonpepException(BeartypeDecorHintException): ''' **Beartype decorator PEP-noncompliant type hint exception.** This exception is raised at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated by an **invalid PEP-noncompliant type hint** (i.e., type hint failing to comply with :mod:`beartype`-specific semantics, including tuple unions and fully-qualified forward references). Tuple unions, for example, are required to contain *only* PEP-noncompliant annotations. This exception is thus raised for callables type-hinted with tuples containing one or more PEP-compliant items (e.g., instances or classes declared by the stdlib :mod:`typing` module) *or* arbitrary objects (e.g., dictionaries, lists, numbers, sets). ''' pass class BeartypeDecorHintNonpepNumpyException(BeartypeDecorHintNonpepException): ''' **Beartype decorator PEP-noncompliant NumPy type hint exception.** This exception is raised at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated by an **invalid NumPy type hint** (e.g., ``numpy.typing.NDArray[...]`` type hint subscripted by an invalid number of arguments). ''' pass class BeartypeDecorHintNonpepPanderaException(BeartypeDecorHintNonpepException): ''' **Beartype decorator PEP-noncompliant Pandera type hint exception.** This exception is raised at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated by an **invalid Pandera type hint** (e.g., ``pandera.typing.DataFrame[...]`` type hint annotating a parameter or return of a callable *not* decorated by the PEP-noncompliant :func:`pandera.check_types` decorator). ''' pass # ....................{ DECORATOR ~ hint : pep }.................... class BeartypeDecorHintPepException(BeartypeDecorHintException): ''' Abstract base class of all **beartype decorator PEP-compliant type hint value exceptions.** Instances of subclasses of this exception are raised at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated with one or more PEP-compliant type hints either violating an annotation-centric PEP (e.g., :pep:`484`) *or* this decorator's implementation of such a PEP. ''' pass class BeartypeDecorHintPepSignException(BeartypeDecorHintPepException): ''' **Beartype decorator PEP-compliant type hint sign exception.** Instances of subclasses of this exception are raised at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated with one or more PEP-compliant type hints *not* uniquely identifiable by a **sign** (i.e., object uniquely identifying a category of PEP-compliant type hints). ''' pass class BeartypeDecorHintPepUnsupportedException(BeartypeDecorHintPepException): ''' **Beartype decorator unsupported PEP-compliant type hint exception.** This exception is raised at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated with one or more PEP-compliant type hints (e.g., instances or classes declared by the stdlib :mod:`typing` module) currently unsupported by this decorator. ''' pass # ....................{ DECORATOR ~ hint : pep : proposal }.................... class BeartypeDecorHintPep3119Exception(BeartypeDecorHintPepException): ''' **Beartype decorator** :pep:`3119`-compliant **type hint exception.** This exception is raised at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated with one or more PEP-compliant type hints either violating :pep:`3119` *or* this decorator's implementation of :pep:`3119`, including: * Hints that are **non-isinstanceable classes** (i.e., classes that prohibit being passed as the second parameter to the :func:`isinstance` builtin by leveraging metaclasses overriding the ``__instancecheck__()`` dunder method to raise exceptions). Notably, this includes most public classes declared by the standard :mod:`typing` module. ''' pass class BeartypeDecorHintPep484Exception(BeartypeDecorHintPepException): ''' **Beartype decorator** :pep:`484`-compliant **type hint exception.** This exception is raised at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated with one or more PEP-compliant type hints either violating :pep:`484` *or* this decorator's implementation of :pep:`484`, including: * Hints subscripted by the :attr:`typing.NoReturn` type hint (e.g., ``typing.List[typing.NoReturn]``). ''' pass class BeartypeDecorHintPep484585Exception(BeartypeDecorHintPepException): ''' **Beartype decorator** :pep:`484`- or :pep:`585`-compliant **dual type hint exception.** This exception is raised at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated with one or more PEP-compliant type hints violating :pep:`484`, :pep:`585`, *or* this decorator's implementation of :pep:`484` or :pep:`585`. ''' pass class BeartypeDecorHintPep544Exception(BeartypeDecorHintPepException): ''' **Beartype decorator** :pep:`544`-compliant **type hint exception.** This exception is raised at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated with one or more PEP-compliant type hints either violating :pep:`544` *or* this decorator's implementation of :pep:`544`. ''' pass class BeartypeDecorHintPep557Exception(BeartypeDecorHintPepException): ''' **Beartype decorator** :pep:`557`-compliant **type hint exception.** This exception is raised at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated with one or more PEP-compliant type hints either violating :pep:`557` *or* this decorator's implementation of :pep:`557`. ''' pass class BeartypeDecorHintPep585Exception(BeartypeDecorHintPepException): ''' **Beartype decorator** :pep:`585`-compliant **type hint exception.** This exception is raised at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated with one or more PEP-compliant type hints either violating :pep:`585` *or* this decorator's implementation of :pep:`585`. ''' pass class BeartypeDecorHintPep586Exception(BeartypeDecorHintPepException): ''' **Beartype decorator** :pep:`586`-compliant **type hint exception.** This exception is raised at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated with one or more PEP-compliant type hints either violating :pep:`586` *or* this decorator's implementation of :pep:`586`. ''' pass class BeartypeDecorHintPep591Exception(BeartypeDecorHintPepException): ''' **Beartype decorator** :pep:`591`-compliant **type hint exception.** This exception is raised at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated with one or more PEP-compliant type hints either violating :pep:`591` *or* this decorator's implementation of :pep:`591`. ''' pass class BeartypeDecorHintPep593Exception(BeartypeDecorHintPepException): ''' **Beartype decorator** :pep:`593`-compliant **type hint exception.** This exception is raised at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated with one or more PEP-compliant type hints either violating :pep:`593` *or* this decorator's implementation of :pep:`593`. ''' pass class BeartypeDecorHintPep604Exception(BeartypeDecorHintPepException): ''' **Beartype decorator** :pep:`604`-compliant **type hint exception.** This exception is raised at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated with one or more PEP-compliant type hints either violating :pep:`604` *or* this decorator's implementation of :pep:`604`. ''' pass class BeartypeDecorHintPep647Exception(BeartypeDecorHintPepException): ''' **Beartype decorator** :pep:`647`-compliant **type hint exception.** This exception is raised at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated with one or more PEP-compliant type hints either violating :pep:`647` *or* this decorator's implementation of :pep:`647`. ''' pass class BeartypeDecorHintPep673Exception(BeartypeDecorHintPepException): ''' **Beartype decorator** :pep:`673`-compliant **type hint exception.** This exception is raised at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated with one or more PEP-compliant type hints either violating :pep:`673` *or* this decorator's implementation of :pep:`673`. ''' pass class BeartypeDecorHintPep695Exception(BeartypeDecorHintPepException): ''' **Beartype decorator** :pep:`695`-compliant **type hint exception.** This exception is raised at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated with one or more PEP-compliant type hints either violating :pep:`695` *or* this decorator's implementation of :pep:`695`. ''' pass # ....................{ DECORATOR ~ param }.................... class BeartypeDecorParamException(BeartypeDecorException): ''' Abstract base class of all **beartype decorator parameter exceptions.** Instances of subclasses of this exception are raised at decoration time from the :func:`beartype.beartype` decorator on receiving a callable declaring invalid parameters. ''' pass class BeartypeDecorParamNameException(BeartypeDecorParamException): ''' **Beartype decorator parameter name exception.** This exception is raised at decoration time from the :func:`beartype.beartype` decorator on receiving a callable declaring parameters with **invalid names** (i.e., prefixed by the :mod:`beartype`-reserved substring ``"__bear"``). ''' pass # ....................{ CALL }.................... class BeartypeCallException(BeartypeException): ''' Abstract base class of all **beartyped callable exceptions.** Instances of subclasses of this exception are raised from wrapper functions generated by the :func:`beartype.beartype` decorator, typically when failing a runtime type-check at call time. ''' pass class BeartypeCallUnavailableTypeException(BeartypeCallException): ''' **Beartyped callable unavailable type exceptions.** This exception is raised from the :class:`beartype.cave.UnavailableType` class when passed to either the :func:`isinstance` or :func:`issubclass` builtin functions, typically due to a type defined by the :class:`beartype.cave` submodule being conditionally unavailable under the active Python interpreter. ''' pass # ....................{ CALL ~ hint }.................... class BeartypeCallHintException(BeartypeCallException): ''' Abstract base class of all **beartype type-checking exceptions.** Instances of subclasses of this exception are raised from wrapper functions generated by the :func:`beartype.beartype` decorator when failing a runtime type-check at callable call time, typically due to either being passed a parameter or returning a value violating a type hint annotating that parameter or return. ''' pass class BeartypeCallHintForwardRefException( BeartypeCallHintException, _BeartypeHintForwardRefExceptionMixin): ''' **Beartype type-checking forward reference exception.** This exception is raised from wrapper functions generated by the :func:`beartype.beartype` decorator when a **forward reference type hint** (i.e., string whose value is the name of a user-defined class that has yet to be defined) erroneously references a module attribute whose value is *not* actually a class. ''' pass # ....................{ CALL ~ hint : violation }.................... class BeartypeCallHintViolation(BeartypeCallHintException): ''' Abstract base class of all **beartype type-checking violations.** Instances of subclasses of this exception are raised by :mod:`beartype` when an object to be type-checked violates the type hint annotating that object. This includes wrapper functions generated by the :func:`beartype.beartype` decorator when either passed a parameter or returning an object violating the type hint annotating that parameter or return. Attributes ---------- _culprits_weakref_and_repr : Tuple[(object, str), ...] Tuple of 2-tuples (``culprit_weakref``, ``culprit_repr``) weakly referring to all of the culprits previously passed to the :meth:`__init__` method, where: * ``culprits_repr`` is the machine-readable string representation of the culprit weakly referred to by the ``culprit_weakref`` reference. * ``culprits_weakref`` is a weak reference to that culprit, defined as either: * If that culprit is not ``None`` *and* that culprit can be weakly referenced, a **weak reference** (i.e., :class:`weakref.ref` instance) to that culprit. * If that culprit is ``None``, a singleton non-``None`` placeholder. Since the :class:`weakref.ref` class ambiguously returns ``None`` when that culprit has already been garbage-collected, this attribute intentionally substitutes ``None`` for this placeholder. * If that culprit *cannot* be weakly referenced (e.g., due to being an instance of a builtin variable-sized C-based type), ``None``. ''' # ..................{ INITIALIZERS }.................. def __init__(self, message: str, culprits: tuple) -> None: ''' Initialize this type-checking exception. Parameters ---------- message : str Human-readable message describing this exception. culprits : Tuple[object, ...] Tuple of one or more **culprits** (i.e., user-defined objects directly responsible for this exception, typically due to violating a type hint annotating a parameter passed to *or* object returned from the wrapper function generated by the :func:`beartype.beartype` decorator raising this exception). This exception internally preserves a weak reference to these culprits, which callers may then safely retrieve at any time via the :meth:`culprits` property. Raises ------ _BeartypeUtilExceptionException If the culprits are either: * *Not* a tuple. * The empty tuple. ''' # Avoid circular import dependencies. from beartype._util.py.utilpyweakref import make_obj_weakref_and_repr # Initialize the superclass with the passed message. super().__init__(message) #FIXME: Unit test us up, please. # If the culprits are *NOT* a tuple, raise an exception. if not isinstance(culprits, tuple): raise _BeartypeUtilExceptionException( f'Culprits {repr(culprits)} not tuple.') # Else, the culprits are a tuple. # # If the culprits are the empty tuple, raise an exception. elif not culprits: raise _BeartypeUtilExceptionException('Culprits tuple empty.') # Else, the culprits are a non-empty tuple. # Tuple of 2-tuples ("culprit_weakref", "culprit_repr") weakly referring # to all of the passed culprits. self._culprits_weakref_and_repr = tuple( make_obj_weakref_and_repr(culprit) for culprit in culprits ) # ..................{ PROPERTIES }.................. # Read-only properties intentionally providing no corresponding setters. @property def culprits(self) -> tuple: ''' Tuple of one or more **culprits** (i.e., user-defined objects directly responsible for this exception, typically due to violating a type hint annotating a parameter passed to *or* object returned from the wrapper function generated by the :func:`beartype.beartype` decorator raising this exception). Specifically, this property returns either: * If a container (e.g., dictionary, list, set, tuple) is responsible for this exception, the 2-tuple ``(culprit_root, culprit_leaf)`` where: * ``culprit_root`` is the outermost such container. Typically, this is the passed parameter or returned value indirectly violating this hint. * ``culprit_leaf`` is the innermost item transitively contained in ``culprit_root`` directly violating this hint. * If a non-container (e.g., scalar, class instance) is responsible for this exception, the 1-tuple ``(culprit,)`` where ``culprit`` is that non-container. Caveats ------- **This property is safely accessible from any context.** However, this property is most usefully accessed *only* from the ``except ...:`` block directly catching this exception. To avoid memory leaks, this property only weakly rather than strongly refers to these culprits and is thus best accessed only where these culprits are accessible. Notably, this property is guaranteed to refer to these culprits *only* for the duration of the ``except ...:`` block directly catching this exception. Since these culprits may be garbage-collected at any time thereafter, this property *cannot* be guaranteed to refer to these culprits outside that block. If this property is accessed from *any* other context and ore or more of these culprits have already been garbage-collected, the corresponding item(s) of this property are only the machine-readable representations of those culprits rather than those actual culprits. **This property returns the machine-readable representation of instances of builtin variable-sized C-based types** (e.g., :class:`dict`, :class:`int`, :class:`list`, :class:`tuple`) **rather than those instances themselves.** Why? Because CPython limitations prevent those instances from being weakly referred to. Blame Guido and the BDFL! ''' # Avoid circular import dependencies. from beartype._util.py.utilpyweakref import get_weakref_obj_or_repr # Tuple of one or more strong references to the culprits previously # passed to the __init__() method for those culprits that are alive # *OR* their representations otherwise. culprits = tuple( get_weakref_obj_or_repr( obj_weakref=culprit_weakref, obj_repr=culprit_repr) for culprit_weakref, culprit_repr in self._culprits_weakref_and_repr ) # print(f'culprits_weakref_and_repr: {self._culprits_weakref_and_repr}') # Return these culprits. return culprits class BeartypeCallHintParamViolation(BeartypeCallHintViolation): ''' **Beartyped callable parameter type-checking exception.** This exception is raised from a call to a wrapper function generated by the :func:`beartype.beartype` decorator type-checking a decorated callable when the caller passes that call a parameter violating the type hint annotating that parameter of that decorated callable. ''' pass class BeartypeCallHintReturnViolation(BeartypeCallHintViolation): ''' **Beartyped callable return type-checking exception.** This exception is raised from a call to a wrapper function generated by the :func:`beartype.beartype` decorator type-checking a decorated callable when that call returns an object violating the type hint annotating the return of that decorated callable. ''' pass class BeartypeDecorHintParamDefaultViolation(BeartypeCallHintViolation): ''' **Beartyped decorator optional parameter default value type-checking exception.** This exception is raised at decoration time by the :func:`beartype.beartype` decorator when type-checking a decorated callable accepting an optional parameter whose default value violates the type hint annotating that parameter. ''' # ....................{ PEP }.................... class BeartypePepException(BeartypeDecorException): ''' Abstract base class of all **beartype Python Enhancement Proposal (PEP) exceptions.** Instances of subclasses of this exception are raised at both call time and decoration time on receiving a callable or class violating a specific PEP. ''' pass class BeartypePep563Exception(BeartypePepException): ''' **Beartype** :pep:`563` **exception.** This exception is raised at both call time of the :func:`beartype.peps.resolve_pep563` function and decoration time of the :func:`beartype.beartype` decorator on failing to dynamically evaluate a postponed annotation of a callable for which :pep:`563` is active. ''' pass # ....................{ API ~ cave }.................... class BeartypeCaveException(BeartypeException): ''' Abstract base class of all **beartype cave exceptions.** Instances of subclasses of this exception are raised at usage time from various types published by the :func:`beartype.cave` submodule. ''' pass # ....................{ API ~ cave : nonetypeor }.................... class BeartypeCaveNoneTypeOrException(BeartypeCaveException): ''' Abstract base class of all **beartype cave** ``None`` **tuple factory exceptions.** Instances of subclasses of this exception are raised at usage time from the :func:`beartype.cave.NoneTypeOr` tuple factory. ''' pass class BeartypeCaveNoneTypeOrKeyException(BeartypeCaveNoneTypeOrException): ''' **Beartype cave** ``None`` **tuple factory key exception.** Instances of this exception are raised when indexing the :func: `beartype.cave.NoneTypeOr` dictionary with an invalid key, including: * The empty tuple. * Arbitrary objects that are neither: * **Types** (i.e., :class:`beartype.cave.ClassType` instances). * **Tuples of types** (i.e., tuples whose items are all :class:`beartype.cave.ClassType` instances). ''' pass class BeartypeCaveNoneTypeOrMutabilityException( BeartypeCaveNoneTypeOrException): ''' **Beartype cave** ``None`` **tuple factory mutability exception.** Instances of this exception are raised when attempting to explicitly set a key on the :func:`beartype.cave.NoneTypeOr` dictionary. ''' pass # ....................{ API ~ claw }.................... class BeartypeClawException(BeartypeException): ''' Abstract base class of all **beartype import hook exceptions.** Instances of subclasses of this exception are raised at call time from the callables and classes published by the :mod:`beartype.claw` subpackage. ''' pass # ....................{ API ~ claw : hook }.................... class BeartypeClawHookException(BeartypeClawException): ''' **Beartype import hook-time exception.** This exception is raised at **beartype import hook-time** (i.e., the early time encompassing the call to a public beartype import hook published by the :mod:`beartype.claw` subpackage by a downstream third-party codebase) on various fatal errors (e.g., when that codebase calls that hook with invalid parameters). ''' pass class BeartypeClawHookUnpackagedException(BeartypeClawHookException): ''' **Beartype import hook-time unpackaged exception.** This exception is raised at **beartype import hook-time** (i.e., the early time encompassing the call to a public beartype import hook published by the :mod:`beartype.claw` subpackage by a downstream third-party codebase) when the :func:`beartype.claw.beartype_this_package` function is called from outside any package structure (e.g., top-level module or executable script). ''' pass # ....................{ API ~ claw : import }.................... class BeartypeClawImportException(BeartypeClawException): ''' **Beartype import hook import exception.** This exception is raised at import time when importing a module erroneously transformed by a beartype import hook previously installed by a prior call to a public function published by the :mod:`beartype.claw` subpackage. ''' pass class BeartypeClawImportAstException(BeartypeClawImportException): ''' **Beartype import hook abstract syntax tree (AST) exception.** This exception is raised at import time when a **beartype import hook** (i.e., previously installed by a prior call to a public function published by the :mod:`beartype.claw` subpackage) erroneously transforms a module from its original syntactically valid AST into a new syntactically invalid AST. ''' pass class BeartypeClawImportConfException(BeartypeClawImportException): ''' **Beartype import hook configuration exception.** This exception is raised at import time when a **beartype import hook** (i.e., previously installed by a prior call to a public function published by the :mod:`beartype.claw` subpackage) erroneously attempts to access a non-existent beartype configuration. ''' pass # ....................{ API ~ conf }.................... class BeartypeConfException(BeartypeException): ''' Abstract base class of all **beartype configuration exceptions.** Instances of subclasses of this exception are raised by the :class:`beartype.BeartypeConf` class to inform the user of various fatal edge cases concerning beartype configuration. ''' pass class BeartypeConfParamException(BeartypeConfException): ''' **Beartype configuration parameter exception.** Instances of this exception are raised at instantiation time of the :class:`beartype.BeartypeConf` class when the caller attempts to erroneously instantiate that class with an invalid parameter. ''' pass class BeartypeConfShellVarException(BeartypeConfException): ''' **Beartype configuration shell environment variable exception.** Instances of this exception are raised at instantiation time of the :class:`beartype.BeartypeConf` class when the caller erroneously sets a shell environment variable recognized by that class (e.g., ``${BEARTYPE_IS_COLOR}``) to an invalid value. ''' pass # ....................{ API ~ door }.................... class BeartypeDoorException(BeartypeException): ''' Abstract base class of all **Decidedly Object-Oriented Runtime-checking (DOOR) exceptions.** Instances of subclasses of this exception are raised at call time from callables and classes published by the :func:`beartype.door` subpackage. ''' pass class BeartypeDoorHintViolation(BeartypeCallHintViolation): ''' **Beartype object-oriented type-checking exception.** This exception is raised at call time by both: * The :func:`beartype.door.die_if_unbearable` function when passed an object violating the passed type hint. * The :meth:`beartype.door.TypeHint.die_if_unbearable` method when passed an object violating the current type hint. ''' pass # ....................{ API ~ door : pep }.................... class BeartypeDoorNonpepException(BeartypeDoorException): ''' **Decidedly Object-Oriented Runtime-checking (DOOR) PEP-noncompliant type hint exception.** This exception is raised at call time from :func:`beartype.door` callables and classes on receiving an **invalid PEP-noncompliant type hint** (i.e., type hint failing to comply with PEP standards currently supported by the :mod:`beartype.door` API). ''' pass class BeartypeDoorPepException(BeartypeDoorException): ''' **Decidedly Object-Oriented Runtime-checking (DOOR) PEP-compliant type hint exception.** This exception is raised at call time from :func:`beartype.door` callables and classes on receiving an **invalid PEP-compliant type hint** (i.e., type hint complying with PEP standards currently supported by the :mod:`beartype.door` API but otherwise invalid for various reasons). ''' pass class BeartypeDoorPepUnsupportedException(BeartypeDoorPepException): ''' **Decidedly Object-Oriented Runtime-checking (DOOR) unsupported PEP-compliant type hint exception.** This exception is raised at call time from :func:`beartype.door` callables and classes on receiving an **unsupported PEP-compliant type hint** (i.e., type hint complying with PEP standards *not* currently supported by the :mod:`beartype.door` API). ''' pass # ....................{ API ~ kind }.................... class BeartypeKindException(BeartypeException): ''' Abstract base class of all **beartype container exceptions.** Instances of subclasses of this exception are raised at usage (e.g., instantiation, callable call) time from various class hierarchies implementing **beartype containers** (i.e., pure-Python data structures defined by :mod:`beartype`). ''' pass # ....................{ API ~ kind : dict }.................... class BeartypeKindFrozenDictException(BeartypeException): ''' **Beartype frozen dictionary exception.** This exception is raised from various methods of the private :class:`beartype._util.kind.map.utilmapfrozen.FrozenDict` class publicly exposed as the :class:`beartype.BeartypeHintOverrides` subclass, typically due to external callers erroneously attempting to modify key-value pairs of instances of this class. ''' pass class BeartypeHintOverridesException(BeartypeKindFrozenDictException): ''' **Beartype hint overrides exception.** This exception is raised from various methods of the public :class:`beartype.BeartypeHintOverrides` class, typically due to external callers erroneously attempting to instantiate instances of this class with **recursive hint overrides** (e.g., ``BeartypeHintOverrides{str: list[str]})``). ''' pass # ....................{ API ~ plug }.................... class BeartypePlugException(BeartypeException): ''' Abstract base class of all **beartype plugin exceptions.** Instances of subclasses of this exception are raised at various times from functionality utilizing :mod:`beartype`-specific plugin APIs standardized by the :mod:`beartype.plug` subpackage, typically on detecting invalid usage of a :mod:`beartype`-specific plugin API within a third-party Python package or module. ''' pass class BeartypePlugInstancecheckStrException(BeartypePlugException): ''' **Beartype** ``__instancecheck_str__()`` **exception.** This exception is raised at various times from functionality utilizing the :mod:`beartype`-specific ``__instancecheck_str__()`` plugin API (i.e., a :mod:`beartype`-specific dunder method defined on metaclasses of classes to return human-readable substrings describing the failure of arbitrary objects to satisfy those classes). ''' pass # ....................{ API ~ vale }.................... class BeartypeValeException(BeartypeException): ''' Abstract base class of all **beartype validator exceptions.** Instances of subclasses of this exception are raised at usage (e.g., instantiation, callable call) time from the class hierarchy published by the :mod:`beartype.vale` subpackage. ''' pass class BeartypeValeSubscriptionException(BeartypeValeException): ''' **Beartype validator subscription exception.** This exception is raised at instantiation time when subscripting (indexing) factories published by the :mod:`beartype.vale` subpackage, including attempts to: * Instantiate *any* of these factories. Like standard type hints, these factories are *only* intended to be subscripted (indexed). * Apply the ``&`` or ``|`` operators to *any* subscriptions of these factories and *any* other objects (e.g., ``beartype.vale.Is[lambda obj: True]] & 'If it seems bad, it is.'``). * Subscript the :attr:`beartype.vale.Is` factory by anything other than a **validator** (i.e., tester function satisfying the type hint ``collections.abc.Callable[[typing.Any,], bool]``). ''' pass class BeartypeValeValidationException(BeartypeValeException): ''' **Beartype validator validation exception.** This exception is raised at validation time (e.g.,, at call time of a :func:`beartype.beartype`-decorated callable annotated by a beartype validator) when a beartype validator fails to properly validate an object, including attempts to: * Subscript the :attr:`beartype.vale.Is` factory by a **non-bool-like validator** (i.e., tester function returning an object that is neither a :class:`bool` *nor* implicitly convertible into a :class:`bool`). ''' pass # ....................{ PRIVATE ~ door }.................. class _BeartypeDoorTextException(BeartypeDoorException): ''' **Decidedly Object-Oriented Runtime-checking (DOOR) text exception.** This exception is raised at call time from :func:`beartype.door` callables and classes on detecting invalid strings (e.g., on raising an exception whose message is *not* prefixed by the expected substring). ''' pass # ....................{ PRIVATE ~ vale }.................... class _BeartypeValeUtilException(BeartypeValeException): ''' **Beartype validator utility exception.** This exception is raised from various submodules of the private :func:`beartype.vale._util` subpackage. ''' pass # ....................{ PRIVATE ~ util }.................... class _BeartypeUtilException(BeartypeException): ''' Abstract base class of all **beartype private utility exceptions.** Instances of subclasses of this exception are raised by *most* (but *not* all) private submodules of the private :mod:`beartype._util` subpackage. These exceptions denote critical internal issues and should thus *never* be raised, let alone allowed to percolate up the call stack to end users. ''' pass class _BeartypeUtilExceptionException(_BeartypeUtilException): ''' **Beartype exception utility exception.** This exception is raised by various functions of the private :mod:`beartype.roar._roarexc` subpackage. This exception denotes a critical internal issue and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass # ....................{ PRIVATE ~ util : ast }.................. class _BeartypeUtilAstException(_BeartypeUtilException): ''' **Beartype abstract syntax tree (AST) utility exception.** This exception is raised by various functions of the private :mod:`beartype._util.ast` subpackage. This exception denotes a critical internal issue and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass # ....................{ PRIVATE ~ util : cache }.................. class _BeartypeUtilCachedException(_BeartypeUtilException): ''' Abstract base class of all **beartype caching utility exceptions.** Instances of subclasses of this exception are raised by private submodules of the private :mod:`beartype._util.cache` subpackage. These exceptions denote critical internal issues and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass class _BeartypeUtilCallableCachedException(_BeartypeUtilCachedException): ''' **Beartype memoization exception.** This exception is raised by the :func:`beartype._util.cache.utilcache.utilcachecall.callable_cached` decorator on various fatal errors (e.g., when the signature of the decorated callable is unsupported). This exception denotes a critical internal issue and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass class _BeartypeUtilCacheLruException(_BeartypeUtilCachedException): ''' **Beartype Least Recently Used (LRU) cache exception.** This exception is raised by the :func:`beartype._util.cache.utilcache.utilcachelru.CacheLruStrong` class on various fatal errors (e.g., when the cache capacity is *not* a positive integer). This exception denotes a critical internal issue and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass # ....................{ PRIVATE ~ util : cache : pool }.................. class _BeartypeUtilCachedKeyPoolException(_BeartypeUtilException): ''' **Beartype key pool exception.** This exception is raised by private functions of the private :mod:`beartype._util.cache.pool.utilcachepool` subpackage on various fatal edge cases. This exception denotes a critical internal issue and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass class _BeartypeUtilCachedFixedListException(_BeartypeUtilCachedException): ''' **Beartype decorator fixed list exception.** This exception is raised at decoration time from the :func:`beartype.beartype` decorator when an internal callable erroneously mutates a **fixed list** (i.e., list constrained to a fixed length defined at instantiation time), usually by attempting to modify the length of that list. This exception denotes a critical internal issue and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass class _BeartypeUtilCachedObjectTypedException(_BeartypeUtilCachedException): ''' **Beartype decorator typed object exception.** This exception is raised at decoration time from the :func:`beartype.beartype` decorator when an internal callable erroneously acquires a **pooled typed object** (i.e., object internally cached to a pool of all objects of that type). This exception denotes a critical internal issue and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass # ....................{ PRIVATE ~ util : call }.................. class _BeartypeCallHintRaiseException(_BeartypeUtilException): ''' Abstract base class of all **beartype human-readable exception raiser exceptions.** Instances of subclasses of this exception are raised by private utility **exception raiser functions** (i.e., functions raising human-readable exceptions from wrapper functions when either passed a parameter or returning a value annotated by a type hint fails the runtime type-check required by that hint) when an unexpected failure occurs. This exception denotes a critical internal issue and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass class _BeartypeCallHintPepRaiseException(_BeartypeCallHintRaiseException): ''' **Beartype PEP-compliant human-readable exception raiser exception.** This exception is raised by the :func:`beartype._check.error.errorget.get_func_pith_violation` exception raiser function when an unexpected failure occurs. This exception denotes a critical internal issue and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass class _BeartypeCallHintPepRaiseDesynchronizationException( _BeartypeCallHintPepRaiseException): ''' **Beartype human-readable exception raiser desynchronization exception.** This exception is raised by the :func:`beartype._check.error.errorget.get_func_pith_violation` function (which raises human-readable exceptions from wrapper functions when either passed a parameter or returning a value, referred to as the "pith" for brevity, annotated by a PEP-compliant type hint fails the type-check required by that hint) when this pith appears to satisfy this type-check, a runtime paradox implying either: * The parent wrapper function generated by the :mod:`beartype.beartype` decorator type-checking this pith triggered a false negative by erroneously misdetecting this pith as failing this type check. * The :func:`beartype._check.error.errorget.get_func_pith_violation` function re-type-checking this pith triggered a false positive by erroneously misdetecting this pith as satisfying this type check when in fact this pith fails to do so. This exception denotes a critical internal issue and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass # ....................{ PRIVATE ~ util : kind }.................. class _BeartypeUtilCallFrameException(_BeartypeUtilException): ''' **Beartype call stack frame utility exception.** This exception is raised by various functions of the private :mod:`beartype._util.utilfunc` subpackage. This exception denotes a critical internal issue and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass class _BeartypeUtilMappingException(_BeartypeUtilException): ''' **Beartype mapping utility exception.** This exception is raised by various functions of the private :mod:`beartype._util.kind.map` subpackage. This exception denotes a critical internal issue and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass class _BeartypeUtilModuleException(_BeartypeUtilException): ''' **Beartype module utility exception.** This exception is raised by various functions of the private :mod:`beartype._util.module.utilmodget` subpackage. Notably, this includes: * When dynamically importing an unimportable external user-defined module, typically due to a **PEP-compliant forward reference type hint** (i.e., string whose value is the name of a user-defined class that has yet to be defined) erroneously referencing a non-existent module or module attribute. This exception denotes a critical internal issue and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass class _BeartypeUtilPathException(_BeartypeUtilException): ''' **Beartype path utility exception.** This exception is raised by various functions of the private :mod:`beartype._util.path` subpackage on various fatal edge cases. This exception denotes a critical internal issue and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass class _BeartypeUtilTypeException(_BeartypeUtilException): ''' **Beartype class utility exception.** This exception is raised by various functions of the private :mod:`beartype._util.cls` subpackage. This exception denotes a critical internal issue and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass # ....................{ PRIVATE ~ util : kind : callable }.................. class _BeartypeUtilCallableException(_BeartypeUtilException): ''' **Beartype callable utility exception.** This exception is raised by various functions of the private :mod:`beartype._util.func` subpackage. This exception denotes a critical internal issue and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass class _BeartypeUtilCallableScopeException(_BeartypeUtilCallableException): ''' **Beartype callable scope utility exception.** This exception is raised by various functions of the private :mod:`beartype._util.func.utilfuncscope` submodule. This exception denotes a critical internal issue and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass class _BeartypeUtilCallableScopeNotFoundException( _BeartypeUtilCallableException): ''' **Beartype callable missing scope utility exception.** This exception is raised by the private :mod:`beartype._util.func.utilfuncscope.get_func_locals` getter on failing to find the lexical scope of the parent callable or class declaring the passed nested callable, enabling callers of that getter to identify this common edge case. This exception denotes a critical internal issue and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass class _BeartypeUtilCallableWrapperException(_BeartypeUtilCallableException): ''' **Beartype callable wrapper utility exception.** This exception is raised by various functions of the private :mod:`beartype._util.func.utilfuncwrap` subpackage. This exception denotes a critical internal issue and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass # ....................{ PRIVATE ~ util : object }.................. class _BeartypeUtilObjectException(_BeartypeUtilException): ''' Abstract base class of all **beartype object utility exceptions.** Instances of subclasses of this exception are raised by private functions defined by the private :mod:`beartype._util.utilobject` submodule. These exceptions denote critical internal issues and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass class _BeartypeUtilObjectNameException(_BeartypeUtilObjectException): ''' **Beartype object name exception.** This exception is raised by the :func:`beartype._util.utilobject.get_object_basename_scoped` getter when the passed object is **unnamed** (i.e., fails to declare either the ``__name__`` or ``__qualname__`` dunder attributes). This exception denotes a critical internal issue and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass # ....................{ PRIVATE ~ util : python }.................. class _BeartypeUtilPythonException(_BeartypeUtilException): ''' Abstract base class of all beartype **Python utility exceptions.** Instances of subclasses of this exception are raised by private submodules of the private :mod:`beartype._util.py` subpackage. These exceptions denote critical internal issues and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass class _BeartypeUtilPythonInterpreterException(_BeartypeUtilPythonException): ''' Beartype **Python interpreter utility exception.** This exception is raised by private functions of the private :mod:`beartype._util.py.utilpyinterpreter` submodule on fatal edge cases. This exception denotes a critical internal issue and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass class _BeartypeUtilPythonWeakrefException(_BeartypeUtilPythonException): ''' Beartype **Python weak reference utility exception.** This exception is raised by private functions of the private :mod:`beartype._util.py.utilpyweakref` submodule on fatal edge cases. This exception denotes a critical internal issue and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass # ....................{ PRIVATE ~ util : text }.................. class _BeartypeUtilTextException(_BeartypeUtilException): ''' Beartype **text utility exception.** This exception is raised by various functions of the private :mod:`beartype._util.text` subpackage. This exception denotes a critical internal issue and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass class _BeartypeUtilTextIdentifierException(_BeartypeUtilTextException): ''' Beartype **Python identifier utility exception.** This exception is raised by private functions of the private :mod:`beartype._util.text.utiltextidentifier` submodule on fatal edge cases. This exception denotes a critical internal issue and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass class _BeartypeUtilTextVersionException(_BeartypeUtilTextException): ''' Beartype **Python version utility exception.** This exception is raised by private functions of the private :mod:`beartype._util.text.utiltextversion` submodule on fatal edge cases. This exception denotes a critical internal issue and should thus *never* be raised -- let alone allowed to percolate up the call stack to end users. ''' pass beartype-0.18.5/beartype/roar/_roarwarn.py000066400000000000000000000413251461113517100205470ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **warning hierarchy** (i.e., public and private warning subclasses emitted at decoration, call, and usage time by :mod:`beartype`). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To avoid polluting the public module namespace, external attributes # should be locally imported at module scope *ONLY* under alternate private # names (e.g., "from argparse import ArgumentParser as _ArgumentParser" rather # than merely "from argparse import ArgumentParser"). #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from abc import ABCMeta as _ABCMeta # ....................{ SUPERCLASS }.................... class BeartypeWarning(UserWarning, metaclass=_ABCMeta): ''' Abstract base class of all **beartype warnings.** Instances of subclasses of this warning are emitted either: * At decoration time from the :func:`beartype.beartype` decorator. * At call time from the new callable generated by the :func:`beartype.beartype` decorator to wrap the original callable. * At Sphinx-based documentation building time from Python code invoked by the ``doc/Makefile`` file. ''' # ..................{ INITIALIZERS }.................. def __init__(self, message: str) -> None: ''' Initialize this exception. This constructor (in order): #. Passes all passed arguments as is to the superclass constructor. #. Sanitizes the fully-qualified module name of this exception from the private ``"beartype.roar._roarwarn"`` submodule to the public ``"beartype.roar"`` subpackage to both improve the readability of exception messages and discourage end users from accessing this private submodule. ''' # Defer to the superclass constructor. super().__init__(message) # Sanitize the fully-qualified module name of the class of this # warning. See the docstring for justification. self.__class__.__module__ = 'beartype.roar' # ....................{ CLAW }.................... class BeartypeClawWarning(BeartypeWarning): ''' Abstract base class of all **beartype import hook warnings.** Instances of subclasses of this warning are emitted at module importation time from the import hooks registered by the :func:`beartype.claw` subpackage, typically due to the :func:`beartype.beartype` decorator failing to decorate callables or classes in modules imported by those hooks. ''' pass class BeartypeClawDecorWarning(BeartypeClawWarning): ''' **Beartype import hook decoration warning.** This warning is emitted at module importation time from the import hooks registered by the :func:`beartype.claw` subpackage when the :func:`beartype.beartype` decorator fails to decorate a callable or class declared in a module imported by those hooks. ''' pass # ....................{ CONF }.................... class BeartypeConfWarning(BeartypeWarning): ''' Abstract base class of all **beartype configuration warnings.** Instances of subclasses of this warning are emitted by the :class:`beartype.BeartypeConf` class to inform the user of various non-fatal edge cases concerning beartype configuration. ''' pass class BeartypeConfShellVarWarning(BeartypeConfWarning): ''' **Beartype configuration shell environment variable warning.** Instances of this warning are emitted at instantiation time of the :class:`beartype.BeartypeConf` class when the caller erroneously sets a shell environment variable recognized by that class (e.g., ``${BEARTYPE_IS_COLOR}``) to an valid value conflicting with that of a corresponding parameter also passed to that class (e.g., ``is_color``). ''' pass # ....................{ DECORATOR ~ hint }.................... class BeartypeDecorHintWarning(BeartypeWarning): ''' Abstract base class of all **beartype decorator type hint warnings.** Instances of subclasses of this warning are emitted at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated by a suspicious (but *not* necessarily erroneous) type hint warranting non-fatal warnings *without* raising fatal exceptions. ''' pass class BeartypeDecorHintParamDefaultForwardRefWarning(BeartypeDecorHintWarning): ''' **Beartyped decorator optional parameter default value type-checking forward reference warning.** This exception is raised at decoration time by the :func:`beartype.beartype` decorator when the default value of an optional parameter of a decorated callable is *not* type-checkable against the type hint annotating that parameter, due to that type hint containing a forward reference to a user-defined object that is undefined at that decoration time. ''' pass # ....................{ DECORATOR ~ hint : pep }.................... class BeartypeDecorHintPepWarning(BeartypeDecorHintWarning): ''' Abstract base class of all **beartype decorator PEP-compliant type hint warnings.** Instances of subclasses of this warning are emitted at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated by a suspicious (but *not* necessarily erroneous) PEP-compliant type hint warranting non-fatal warnings *without* raising fatal exceptions. ''' pass #FIXME: Consider removal. # class BeartypeDecorHintPepIgnorableDeepWarning(BeartypeDecorHintPepWarning): # ''' # **Beartype decorator deeply ignorable PEP-compliant type hint warning.** # # This warning is emitted at decoration time from the # :func:`beartype.beartype` decorator on receiving a callable annotated by # one or more **deeply ignorable PEP-compliant type hints** (i.e., instances or classes declared # by the stdlib :mod:`typing` module) currently unsupported by this # decorator. # ''' # # pass #FIXME: Consider removal. # class BeartypeDecorHintPepUnsupportedWarning(BeartypeWarning): # ''' # **Beartype decorator unsupported PEP-compliant type hint warning.** # # This warning is emitted at decoration time from the # :func:`beartype.beartype` decorator on receiving a callable annotated with # one or more PEP-compliant type hints (e.g., instances or classes declared # by the stdlib :mod:`typing` module) currently unsupported by this # decorator. # ''' # # pass # ....................{ DECORATOR ~ hint : pep : deprecate }.................... class BeartypeDecorHintPepDeprecationWarning( BeartypeDecorHintPepWarning, DeprecationWarning): ''' **Beartype decorator PEP-compliant type hint deprecation warning.** This warning is emitted at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated by one or more **deprecated PEP-compliant type hints** (i.e., type hints compliant with outdated PEPs that have since been obsoleted by recent PEPs), including: * If the active Python interpreter targets at least Python >= 3.9 and thus supports :pep:`585`, outdated :pep:`484`-compliant type hints (e.g., ``typing.List[int]``) that have since been obsoleted by the equivalent :pep:`585`-compliant type hints (e.g., ``list[int]``). ''' pass #FIXME: This should *REALLY* have been called #"BeartypeDecorHintPep484DeprecationWarning". Oh well. Let's preserve backward #compatibility by just accepting this as is. This goes away in 2026, anyway. class BeartypeDecorHintPep585DeprecationWarning( BeartypeDecorHintPepDeprecationWarning): ''' **Beartype decorator** :pep:`585`-mandated **deprecation of** :pep:`484`-compliant **type hint warning.** This warning is emitted at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated by one or more outdated :pep:`484`-compliant type hints (e.g., ``typing.List[int]``) that have since been obsoleted by the equivalent :pep:`585`-compliant type hints (e.g., ``list[int]``) if the active Python interpreter targets at least Python >= 3.9 and thus supports :pep:`585`. See Also -------- https://github.com/beartype/beartype#pep-585-deprecations Further discussion ''' pass class BeartypeDecorHintPep613DeprecationWarning( BeartypeDecorHintPepDeprecationWarning): ''' **Beartype decorator** :pep:`613`-compliant **type hint warning.** This warning is emitted at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated by one or more outdated :pep:`613`-compliant **type aliases** (i.e., :obj:`typing.TypeAlias` type hint singletons) that have since been obsoleted by the equivalent :pep:`695`-compliant type aliases (e.g., ``type alias = list[int]``) if the active Python interpreter targets at least Python >= 3.10 and thus supports :pep:`613`. ''' pass # ....................{ DECORATOR ~ hint : non-pep }.................... class BeartypeDecorHintNonpepWarning(BeartypeWarning): ''' Abstract base class of all **beartype decorator PEP-noncompliant type hint warnings.** Instances of subclasses of this warning are emitted at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated by a suspicious (but *not* necessarily erroneous) PEP-noncompliant type hint warranting non-fatal warnings *without* raising fatal exceptions. ''' pass class BeartypeDecorHintNonpepNumpyWarning(BeartypeDecorHintNonpepWarning): ''' **Beartype decorator PEP-noncompliant NumPy type hint warning.** This exception is raised at decoration time from the :func:`beartype.beartype` decorator on receiving a callable annotated by an suspicious NumPy type hint, including: * **Typed NumPy arrays** (i.e., ``numpy.typed.NDArray[...]`` type hints) under Python < 3.8, which this decorator currently reduces to **untyped NumPy arrays** (i.e., :class:`numpy.ndarray`). ''' pass # ....................{ MODULE }.................... class BeartypeModuleWarning(BeartypeWarning): ''' Abstract base class of all **beartype module warnings.** Instances of subclasses of this warning are emitted at various times (including at decoration time from the :func:`beartype.beartype` decorator) on failing to import optional third-party modules, packages, or C extensions warranting non-fatal warnings *without* raising fatal exceptions. ''' pass class BeartypeModuleNotFoundWarning(BeartypeModuleWarning): ''' **Beartype missing optional dependency warning.** This warning is emitted at various times to inform the user of a **missing recommended optional dependency** (i.e., third-party Python package *not* installed under the active Python interpreter whose installation is technically optional but recommended). ''' pass class BeartypeModuleAttributeNotFoundWarning(BeartypeModuleWarning): ''' **Beartype missing optional dependency attribute warning.** This warning is emitted at various times to inform the user of a **missing recommended optional dependency attribute** (i.e., attribute *not* defined by a third-party Python package installed under the active Python interpreter whose installation is technically optional but recommended, typically due to the currently installed version of that package being unexpectedly old and thus failing to define an attribute defined by modern versions of that package). ''' pass class BeartypeModuleUnimportableWarning(BeartypeModuleWarning): ''' **Beartype unimportable optional dependency warning.** This warning is emitted at various times to inform the user of an **unimportable optional dependency** (i.e., third-party Python package installed under the active Python interpreter but which raises unexpected exceptions from module scope when imported). ''' pass # ....................{ SPHINX }.................... #FIXME: Consider removal. # class BeartypeSphinxWarning(BeartypeWarning, metaclass=_ABCMeta): # ''' # Abstract base class of all **beartype Sphinx warnings.** # # Instances of subclasses of this warning are emitted at Sphinx-based # documentation building time from the ``doc/Makefile`` file in various edge # cases warranting non-fatal warnings *without* raising fatal exceptions. # ''' # # pass # ....................{ VALE }.................... class BeartypeValeWarning(BeartypeWarning): ''' Abstract base class of all **beartype data validation warnings.** Instances of subclasses of this warning are emitted at usage (e.g., instantiation, method call) time from the class hierarchy published by the :func:`beartype.vale` subpackage by suspicious (but *not* necessarily erroneous) PEP-compliant type hints warranting non-fatal warnings *without* raising fatal exceptions. ''' pass class BeartypeValeLambdaWarning(BeartypeValeWarning): ''' **Beartype data validation lambda function warning.** This warning is emitted on passing the :func:`repr` builtin an instance of the :class:`beartype.vale.Is` class subscripted by a lambda function whose definition is *not* parsable from the script or module file defining that lambda. ''' pass # ....................{ PRIVATE ~ conf }.................... class _BeartypeConfReduceDecoratorExceptionToWarningDefault( BeartypeConfWarning): ''' Beartype :attr:`beartype.BeartypeConf.warning_cls_on_decorator_exception` **fake warning default.** This warning is *not* actually emitted at all anywhere but instead constitutes intentional design abuse of this submodule. Specifically, this warning is used as the default value for the public :attr:`beartype.BeartypeConf.warning_cls_on_decorator_exception` configuration parameter, enabling private functionality elsewhere to distinguish between the following two common cases: * A user does explicitly sets that parameter to :data:`None`, instructing the :func:`beartype.beartype` decorator to raise exceptions rather than emit warnings on decoration-time errors. * A user does *not* explicitly set that parameter, which then defaults to this fake warning category. Private functionality elsewhere then detects this default and conditionally sets that parameter to a meaningful default depending on the current context. As example, when *not* explicitly set by the user: * The :mod:`beartype.claw` API defaults that parameter to the public :class:`.BeartypeClawDecorWarning` warning category. * The :func:`beartype.beartype` decorator defaults that parameter to :data:`None`. This warning is doing the wrong things, but for the right reasons. Again, this warning is a placeholder that should *never* be emitted to end users. ''' pass # ....................{ PRIVATE ~ util }.................... class _BeartypeUtilWarning(BeartypeWarning): ''' Abstract base class of all **beartype private utility warnings.** Instances of subclasses of this warning are emitted by *most* (but *not* all) private submodules of the private :mod:`beartype._util` subpackage. These warnings denote non-critical internal issues and should thus *never* be emitted, let alone allowed to percolate up the call stack to end users. ''' pass # ....................{ PRIVATE ~ util : call }.................... class _BeartypeUtilCallableWarning(_BeartypeUtilWarning): ''' Beartype **decorator memoization decorator keyword argument** warning. This warning is emitted from callables memoized by the :func:`beartype._util.cache.utilcachecall.callable_cached` decorator on calls receiving one or more keyword arguments. Memoizing keyword arguments is substantially more space- and time-intensive than memoizing the equivalent positional arguments, partially defeating the purpose of memoization in the first place. This warning denotes a critical internal issue and should thus *never* be emitted to end users. ''' pass beartype-0.18.5/beartype/typing/000077500000000000000000000000001461113517100165455ustar00rootroot00000000000000beartype-0.18.5/beartype/typing/__init__.py000066400000000000000000000415321461113517100206630ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype** :mod:`typing` **compatibility layer.** This submodule declares the exact same set of **public typing attributes** (i.e., module-scoped attributes listed by the :attr:`typing.__all__` global) as declared by the :mod:`typing` module for your current Python version. Although the attributes declared by this submodule *mostly* share the same values as the attributes declared by :mod:`typing`, notable differences include: * :pep:`585`-deprecated typing attributes. :pep:`585` deprecated **38 public typing attributes** to "...be removed from the typing module in the first Python version released 5 years after the release of Python 3.9.0." This submodule preserves those attributes under their original names for the Python 3.8-specific version of the :mod:`typing` module, thus preserving forward compatibility with future Python versions. These include: * :attr:`typing.AbstractSet`. * :attr:`typing.AsyncContextManager`. * :attr:`typing.AsyncGenerator`. * :attr:`typing.AsyncIterable`. * :attr:`typing.AsyncIterator`. * :attr:`typing.Awaitable`. * :attr:`typing.ByteString`. * :attr:`typing.Callable`. * :attr:`typing.ChainMap`. * :attr:`typing.Collection`. * :attr:`typing.Container`. * :attr:`typing.ContextManager`. * :attr:`typing.Coroutine`. * :attr:`typing.Counter`. * :attr:`typing.DefaultDict`. * :attr:`typing.Deque`. * :attr:`typing.Dict`. * :attr:`typing.FrozenSet`. * :attr:`typing.Generator`. * :attr:`typing.ItemsView`. * :attr:`typing.Iterable`. * :attr:`typing.Iterator`. * :attr:`typing.KeysView`. * :attr:`typing.List`. * :attr:`typing.Mapping`. * :attr:`typing.MappingView`. * :attr:`typing.Match`. * :attr:`typing.MutableMapping`. * :attr:`typing.MutableSequence`. * :attr:`typing.MutableSet`. * :attr:`typing.OrderedDict`. * :attr:`typing.Pattern`. * :attr:`typing.Reversible`. * :attr:`typing.Set`. * :attr:`typing.Tuple`. * :attr:`typing.Type`. * :attr:`typing.Sequence`. * :attr:`typing.ValuesView`. Usage ---------- :mod:`beartype` users are strongly encouraged to import typing attributes from this submodule rather than from :mod:`typing` directly: e.g., .. code-block:: python # Instead of this... from typing import Tuple, List, Dict, Set, FrozenSet, Type # ...always do this. from beartype.typing import Tuple, List, Dict, Set, FrozenSet, Type ''' # ....................{ TODO }.................... #FIXME: Fundamentally generalize this submodule to optionally backport #attributes from "typing_extensions" where available, resolving issue #237 at: # https://github.com/beartype/beartype/issues/237 # #To do so, we'll basically want to discard the entire current implementation of #this submodule in favour of a fundamentally superior approach resembling: # # In "beartype.typing.__init__": the future of typing backports begins today. # from typing import TYPE_CHECKING # # # If @beartype is currently being statically type-checked (e.g., # # by mypy or pyright), just defer to the third-party # # "typing_extensions" package. # # # # Note that this does *NOT* mean that @beartype now unconditionally # # requires "typing_extensions" at either runtime or static # # type-checking time. Any code in an "if TYPE_CHECKING:" is (basically) # # just a convincing semantic lie that everything syntactically ignores. # if TYPE_CHECKING: # from typing_extensions import * # <-- heh # # Else, @beartype is currently being imported from at runtime. This is # # the common case. This is also the non-trivial case, because @beartype # # does *NOT* require "typing_extensions" as a mandatory runtime # # dependency, because @beartype requires *NOTHING* as a runtime # # dependency. This is the only rule in @beartype's Rule of Law. # else: # #FIXME: Unfortunately, to avoid circular import dependencies, these # #imports will need to be copy-and-pasted into equivalent condensed # #submodules of a new "beartype.typing._util" subpackage. # # Import the requisite machinery that will make the magic happen. # from beartype._util.hint.utilhintfactory import TypeHintTypeFactory # from beartype._util.api.utilapityping import ( # import_typing_attr_or_fallback as _import_typing_attr_or_fallback) # # # Dynamically define the "Self" type hint as follows: # # * If the active Python interpreter targets Python >= 3.11, just # # defer to the canonical "typing.Self" type hint. # # * Else if "typing_extensions" is importable *AND* of a sufficiently # # recent version to define the backported "typing_extensions.Self" # # type hint, fallback to that hint. # # * Else, synthesize a placeholder type hint that @beartype internally # # recognizes as semantically equivalent to "typing.Self". # Self = _import_typing_attr_or_fallback('Self', object) # LiteralString = _import_typing_attr_or_fallback('Self', str) # TypeGuard = _import_typing_attr_or_fallback('Self', bool) # Annotated = _import_typing_attr_or_fallback('Annotated', bool) # # #FIXME: Repeat the above logic for *ALL* existing "typing" attributes. # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To avoid polluting the public module namespace, external attributes # *NOT* intended for public importation should be locally imported at module # scope *ONLY* under alternate private names (e.g., "import re as _re" rather # than merely "from re"). # WARNING: To preserve PEP 561 compliance with static type checkers (e.g., # mypy), external attributes *MUST* be explicitly imported with standard static # import machinery rather than non-standard dynamic import shenanigans (e.g., # "from typing import Annotated" rather than # "import_typing_attr_or_none('Annotated')"). #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype._util.py.utilpyversion import ( IS_PYTHON_AT_LEAST_3_12 as _IS_PYTHON_AT_LEAST_3_12, IS_PYTHON_AT_LEAST_3_11 as _IS_PYTHON_AT_LEAST_3_11, IS_PYTHON_AT_LEAST_3_10 as _IS_PYTHON_AT_LEAST_3_10, IS_PYTHON_AT_LEAST_3_9 as _IS_PYTHON_AT_LEAST_3_9, ) # ....................{ IMPORTS ~ all }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To prevent "mypy --no-implicit-reexport" from raising literally # hundreds of errors at static analysis time, *ALL* public attributes *MUST* be # explicitly reimported under the same names with "{exception_name} as # {exception_name}" syntax rather than merely "{exception_name}". Yes, this is # ludicrous. Yes, this is mypy. For posterity, these failures resemble: # beartype/_cave/_cavefast.py:47: error: Module "beartype.roar" does not # explicitly export attribute "BeartypeCallUnavailableTypeException"; # implicit reexport disabled [attr-defined] #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Import all public attributes of the "typing" module both available under all # supported Python versions and *NOT* deprecated by a subsequent Python version # under their original names. from typing import ( TYPE_CHECKING as TYPE_CHECKING, Any as Any, AnyStr as AnyStr, BinaryIO as BinaryIO, ClassVar as ClassVar, Final as Final, # pyright: ignore ForwardRef as ForwardRef, Generic as Generic, Hashable as Hashable, IO as IO, Literal as Literal, # pyright: ignore NewType as NewType, NamedTuple as NamedTuple, NoReturn as NoReturn, Optional as Optional, Reversible as Reversible, # pyright: ignore Sized as Sized, SupportsIndex as SupportsIndex, # pyright: ignore TypedDict as TypedDict, # pyright: ignore Text as Text, TextIO as TextIO, TypeVar as TypeVar, Union as Union, cast as cast, final as final, # pyright: ignore get_args as get_args, # pyright: ignore get_origin as get_origin, # pyright: ignore get_type_hints as get_type_hints, no_type_check as no_type_check, no_type_check_decorator as no_type_check_decorator, overload as overload, ) # ....................{ IMPORTS ~ version }.................... # Import all public attributes of the "typing" module both available under a # subset of supported Python versions and *NOT* deprecated by a subsequent # Python version under their original names. #FIXME: mypy is now emitting non-fatal warnings about our failing to import from #"typing_extensions", which is both an overly strongly opinionated position for #mypy to stake out *AND* a bad opinion at that, because "typing_extensions" #fails to comply with the runtime API of the "typing" module and is thus mostly #unusable at runtime. These warnings resemble: # beartype/typing/__init__.py:145: note: Use `from typing_extensions import Final` instead # beartype/typing/__init__.py:145: note: See https://mypy.readthedocs.io/en/stable/runtime_troubles.html#using-new-additions-to-the-typing-module # beartype/typing/__init__.py:145: note: Use `from typing_extensions import Literal` instead # #That's not the worst, however. mypy is erroneously ignoring our intentional #"# type: ignore[attr-defined]" pragmas here. It's likely that the ultimate #culprit is our use of beartype-specific "IS_PYTHON_AT_LEAST_*" boolean globals. #Instead, mypy appears to only support hard-coded tests against the #"sys.version_info" tuple: e.g., # if sys.version_info >= (3, 8): # #To resolve this, we should consider: #* Abandoning our usage of beartype-specific "IS_PYTHON_AT_LEAST_*" boolean # globals for hard-coded tests against the "sys.version_info" tuple (above). #* Submitting an upstream issue requesting that mypy respect the # "# type: ignore[attr-defined]" pragma rather than emitting warnings here. # If the active Python interpreter targets Python >= 3.10... if _IS_PYTHON_AT_LEAST_3_10: from typing import ( # type: ignore[attr-defined] Concatenate as Concatenate, # pyright: ignore ParamSpec as ParamSpec, # pyright: ignore ParamSpecArgs as ParamSpecArgs, # pyright: ignore ParamSpecKwargs as ParamSpecKwargs, # pyright: ignore TypeAlias as TypeAlias, # pyright: ignore TypeGuard as TypeGuard, # pyright: ignore is_typeddict as is_typeddict, # pyright: ignore ) # If the active Python interpreter targets Python >= 3.11... if _IS_PYTHON_AT_LEAST_3_11: from typing import ( # type: ignore[attr-defined] LiteralString as LiteralString, # pyright: ignore Never as Never, # pyright: ignore NotRequired as NotRequired, # pyright: ignore Required as Required, # pyright: ignore Self as Self, # pyright: ignore TypeVarTuple as TypeVarTuple, # pyright: ignore Unpack as Unpack, # pyright: ignore assert_never as assert_never, # pyright: ignore assert_type as assert_type, # pyright: ignore clear_overloads as clear_overloads, # pyright: ignore dataclass_transform as dataclass_transform, # pyright: ignore reveal_type as reveal_type, # pyright: ignore get_overloads as get_overloads, # pyright: ignore reveal_type as reveal_type, # pyright: ignore ) # If the active Python interpreter targets Python >= 3.12... if _IS_PYTHON_AT_LEAST_3_12: from typing import ( # type: ignore[attr-defined] TypeAliasType as TypeAliasType, # pyright: ignore override as override, # pyright: ignore ) # ....................{ PEP ~ 544 }.................... # If this interpreter is performing static type-checking (e.g., via mypy), defer # to the standard library versions of the family of "Supports*" protocols # available under Python < 3.8. if TYPE_CHECKING: from typing import ( # type: ignore[attr-defined] Protocol as Protocol, # pyright: ignore SupportsAbs as SupportsAbs, SupportsBytes as SupportsBytes, SupportsComplex as SupportsComplex, SupportsFloat as SupportsFloat, SupportsIndex as SupportsIndex, # pyright: ignore SupportsInt as SupportsInt, SupportsRound as SupportsRound, runtime_checkable as runtime_checkable, # pyright: ignore ) # Else, this interpreter is *NOT* performing static type-checking. In this # case, prefer our optimized PEP 544 attributes. else: from beartype.typing._typingpep544 import ( Protocol as Protocol, SupportsAbs as SupportsAbs, SupportsBytes as SupportsBytes, SupportsComplex as SupportsComplex, SupportsFloat as SupportsFloat, SupportsIndex as SupportsIndex, SupportsInt as SupportsInt, SupportsRound as SupportsRound, runtime_checkable as runtime_checkable, ) # ....................{ PEP ~ 585 }.................... # If this interpreter is either performing static type-checking (e.g., via mypy) # *OR* targets Python < 3.9 and thus fails to support PEP 585, import *ALL* # public attributes of the "typing" module deprecated by PEP 585 as their # original values. # # This is intentionally performed *BEFORE* the corresponding "else:" branch # below handling the Python >= 3.9 case. Why? Because mypy. If the order of # these two branches is reversed, mypy emits errors under Python < 3.9 when # attempting to subscript any of the builtin types (e.g., "Tuple"): e.g., # error: "tuple" is not subscriptable [misc] if TYPE_CHECKING or not _IS_PYTHON_AT_LEAST_3_9: from typing import ( AbstractSet as AbstractSet, AsyncContextManager as AsyncContextManager, AsyncGenerator as AsyncGenerator, AsyncIterable as AsyncIterable, AsyncIterator as AsyncIterator, Awaitable as Awaitable, ByteString as ByteString, Callable as Callable, ChainMap as ChainMap, Collection as Collection, Container as Container, ContextManager as ContextManager, Coroutine as Coroutine, Counter as Counter, DefaultDict as DefaultDict, Deque as Deque, Dict as Dict, FrozenSet as FrozenSet, Generator as Generator, ItemsView as ItemsView, Iterable as Iterable, Iterator as Iterator, KeysView as KeysView, List as List, Mapping as Mapping, Match as Match, MappingView as MappingView, MutableMapping as MutableMapping, MutableSequence as MutableSequence, MutableSet as MutableSet, OrderedDict as OrderedDict, Pattern as Pattern, Reversible as Reversible, Set as Set, Tuple as Tuple, Type as Type, Sequence as Sequence, ValuesView as ValuesView, ) # If the active Python interpreter targets Python >= 3.9 and thus supports PEP # 585, alias *ALL* public attributes of the "typing" module deprecated by PEP # 585 to their equivalent values elsewhere in the standard library. else: from collections import ( ChainMap as ChainMap, Counter as Counter, OrderedDict as OrderedDict, defaultdict as DefaultDict, deque as Deque, ) from collections.abc import ( AsyncIterable as AsyncIterable, AsyncIterator as AsyncIterator, AsyncGenerator as AsyncGenerator, Awaitable as Awaitable, ByteString as ByteString, Callable as Callable, Collection as Collection, Container as Container, Coroutine as Coroutine, Generator as Generator, ItemsView as ItemsView, Iterable as Iterable, Iterator as Iterator, KeysView as KeysView, Mapping as Mapping, MappingView as MappingView, MutableMapping as MutableMapping, MutableSequence as MutableSequence, MutableSet as MutableSet, Reversible as Reversible, Sequence as Sequence, ValuesView as ValuesView, Set as AbstractSet, ) from contextlib import ( AbstractContextManager as ContextManager, AbstractAsyncContextManager as AsyncContextManager, ) from re import ( Match as Match, Pattern as Pattern, ) from typing import ( # type: ignore[attr-defined] Annotated, ) Dict = dict # type: ignore[misc] FrozenSet = frozenset # type: ignore[misc] List = list # type: ignore[misc] Set = set # type: ignore[misc] Tuple = tuple # type: ignore[assignment] Type = type # type: ignore[assignment] beartype-0.18.5/beartype/typing/_typingcache.py000066400000000000000000000165561461113517100215710ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype typing callable caching** (i.e., general-purpose memoization of function and method calls intended to be called *only* from submodules of this subpackage) utilities. This private submodule implements only a minimal subset of the caching functionality implemented by the general-purpose :mod:`beartype._util.cache.utilcachecall` submodule, from which this submodule was originally derived. Since the latter transitively imports from the :mod:`beartype.typing` subpackage at module scope, submodules of the :mod:`beartype.typing` subpackage *cannot* safely import from the :mod:`beartype._util.cache.utilcachecall` submodule at module scope. Ergo, the existence of this submodule. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_9 from functools import wraps from typing import TYPE_CHECKING # If either a pure-static type-checker is currently statically type-checking # @beartype *OR* the active Python interpreter targets Python >= 3.9, PEP 585 is # supported. In this case, embrace non-deprecated PEP 585-compliant type hints. if TYPE_CHECKING or IS_PYTHON_AT_LEAST_3_9: from collections.abc import Callable Dict = dict # Else, the active Python interpreter targets Python < 3.9 and thus fails to # support PEP 585. Note that we intentionally avoid importing these type hint # factories from "beartype.typing", as that would induce a circular import # dependency. Instead, we manually import the relevant type hint factories # conditionally depending on the version of the active Python interpreter. else: from typing import Callable, Dict # ....................{ CONSTANTS }.................... _SENTINEL = object() ''' Sentinel object of arbitrary value. ''' # ....................{ DECORATORS }.................... def callable_cached_minimal(func: Callable) -> Callable: ''' **Memoize** (i.e., efficiently cache and return all previously returned values of the passed callable as well as all previously raised exceptions of that callable previously rather than inefficiently recalling that callable) the passed callable. Parameters ---------- func : Callable Callable to be memoized. Returns ---------- Callable Closure wrapping this callable with memoization. See Also ---------- :func:`beartype._util.cache.utilcachecall.callable_cached` Further details. ''' assert callable(func), f'{repr(func)} not callable.' # Dictionary mapping a tuple of all flattened parameters passed to each # prior call of the decorated callable with the value returned by that # call if any (i.e., if that call did *NOT* raise an exception). params_flat_to_return_value: Dict[tuple, object] = {} # get() method of this dictionary, localized for efficiency. params_flat_to_return_value_get = params_flat_to_return_value.get # Dictionary mapping a tuple of all flattened parameters passed to each # prior call of the decorated callable with the exception raised by that # call if any (i.e., if that call raised an exception). params_flat_to_exception: Dict[tuple, Exception] = {} # get() method of this dictionary, localized for efficiency. params_flat_to_exception_get = params_flat_to_exception.get @wraps(func) def _callable_cached(*args): f''' Memoized variant of the {func.__name__}() callable. See Also ---------- :func:`callable_cached` Further details. ''' # If passed only one positional argument, minimize space consumption by # flattening this tuple of only that argument into that argument. Since # tuple items are necessarily hashable, this argument is necessarily # hashable as well and thus permissible as a dictionary key below. if len(args) == 1: params_flat = args[0] # Else, one or more positional arguments are passed. In this case, # reuse this tuple as is. else: params_flat = args # Attempt to... try: #FIXME: Optimize the params_flat_to_exception_get() case, please. #Since "None" is *NOT* a valid exception, we shouldn't need a #sentinel for safety here. Instead, this should suffice: # exception = params_flat_to_exception_get(params_flat) # # If this callable previously raised an exception when called with # # these parameters, re-raise the same exception. # if exception: # raise exception # Exception raised by a prior call to the decorated callable when # passed these parameters *OR* the sentinel placeholder otherwise # (i.e., if this callable either has yet to be called with these # parameters *OR* has but failed to raise an exception). # # Note that this call raises a "TypeError" exception if any item of # this flattened tuple is unhashable. exception = params_flat_to_exception_get(params_flat, _SENTINEL) # If this callable previously raised an exception when called with # these parameters, re-raise the same exception. if exception is not _SENTINEL: raise exception # pyright: ignore[reportGeneralTypeIssues] # Else, this callable either has yet to be called with these # parameters *OR* has but failed to raise an exception. # Value returned by a prior call to the decorated callable when # passed these parameters *OR* a sentinel placeholder otherwise # (i.e., if this callable has yet to be passed these parameters). return_value = params_flat_to_return_value_get( params_flat, _SENTINEL) # If this callable has already been called with these parameters, # return the value returned by that prior call. if return_value is not _SENTINEL: return return_value # Else, this callable has yet to be called with these parameters. # Attempt to... try: # Call this parameter with these parameters and cache the value # returned by this call to these parameters. return_value = params_flat_to_return_value[params_flat] = func( *args) # If this call raises an exception... except Exception as exception: # Cache this exception to these parameters. params_flat_to_exception[params_flat] = exception # Re-raise this exception. raise exception # If one or more objects either passed to *OR* returned from this call # are unhashable, perform this call as is *WITHOUT* memoization. While # non-ideal, stability is better than raising a fatal exception. except TypeError: return func(*args) # Return this value. return return_value # Return this wrapper. return _callable_cached beartype-0.18.5/beartype/typing/_typingpep544.py000066400000000000000000000656511461113517100215470ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype** :pep:`544` **optimization layer.** This private submodule implements a :func:`beartype.beartype``-compatible (i.e., decorated by the :func:`typing.runtime_checkable` decorator) drop-in replacement for :class:`typing.Protocol` that can lead to significant performance improvements. ''' # ....................{ TODO }.................... #FIXME: *YIKES.* Our "beartype.typing.Protocol" implementation is broken yet #again -- but this time for @classmethod-decorated callables. Consider this: # from beartype.typing import Protocol # class BrokenProtocol(Protocol): # @classmethod # def broken_classmethod(cls) -> object: # pass # #Now define an arbitrary class violating that protocol: # class BrokenClass(object): pass # #Now attempt to demonstrate that this class violates that protocol: # >>> isinstance(BrokenClass, BrokenProtocol) # True # <----- WAAAAAAAAAT # #This issue is almost certainly related to classmethods. We clearly never tested #that. Classmethods clearly require explicit handling and caching. *sigh* # ....................{ IMPORTS }.................... from beartype.typing._typingcache import callable_cached_minimal from beartype._util.py.utilpyversion import ( IS_PYTHON_AT_LEAST_3_12, IS_PYTHON_AT_LEAST_3_9, ) from typing import ( # type: ignore[attr-defined] EXCLUDED_ATTRIBUTES, # pyright: ignore TYPE_CHECKING, Any, Generic, Protocol as _ProtocolSlow, SupportsAbs as _SupportsAbsSlow, SupportsBytes as _SupportsBytesSlow, SupportsComplex as _SupportsComplexSlow, SupportsFloat as _SupportsFloatSlow, SupportsIndex as _SupportsIndexSlow, # pyright: ignore SupportsInt as _SupportsIntSlow, SupportsRound as _SupportsRoundSlow, TypeVar, runtime_checkable, ) # If either *NO* pure-static type-checker is currently statically type-checking # @beartype *OR* the active Python interpreter targets Python < 3.9, the active # Python interpreter targets Python < 3.9 and thus fails to support PEP 585. In # this case, embrace non-deprecated PEP 585-compliant type hints. if not (TYPE_CHECKING or IS_PYTHON_AT_LEAST_3_9): from typing import Dict, Tuple, Type # Else, the active Python interpreter targets Python < 3.9 and thus fails to # support PEP 585. # # Note that we intentionally: # * Avoid importing these type hint factories from "beartype.typing", as that # would induce a circular import dependency. Instead, we manually import the # relevant type hint factories conditionally depending on the version of the # active Python interpreter. *sigh* # * Test the negation of this condition first. Why? Because mypy quietly # defecates all over itself if the order of these two branches is reversed. # Yeah. It's as bad as it sounds. else: Dict = dict Tuple = tuple Type = type # If the active Python interpreter was invoked by a static type checker (e.g., # mypy), violate privacy encapsulation. Doing so invites breakage under newer # Python releases. Confining any potential breakage to this technically optional # static type-checking phase minimizes the fallout by ensuring that this API # continues to behave as expected at runtime. # # See also this deep typing voodoo: # https://github.com/python/mypy/issues/11614 if TYPE_CHECKING: from abc import ABCMeta as _ProtocolMeta # Else, this interpreter was *NOT* invoked by a static type checker and is thus # subject to looser runtime constraints. In this case, access the same metaclass # *WITHOUT* violating privacy encapsulation. else: _ProtocolMeta = type(_ProtocolSlow) # ....................{ PRIVATE ~ constants }.................... _PROTOCOL_ATTR_NAMES_IGNORABLE = frozenset(EXCLUDED_ATTRIBUTES) ''' Frozen set of the names all **ignorable non-protocol attributes** (i.e., attributes *not* considered part of the protocol of a :class:`beartype.typing.Protocol` subclass when passing that protocol to the :func:`isinstance` builtin in structural subtyping checks). ''' _T_co = TypeVar("_T_co", covariant=True) ''' Arbitrary covariant type variable. ''' _TT = TypeVar("_TT", bound="_CachingProtocolMeta") ''' Arbitrary type variable bound (i.e., confined) to classes. ''' # ....................{ PRIVATE ~ metaclasses }.................... class _CachingProtocolMeta(_ProtocolMeta): ''' **Caching protocol metaclass** (i.e., drop-in replacement for the private metaclass of the public :class:`typing.Protocol` superclass that additionally caches :meth:`class.__instancecheck__` results). This metaclass amortizes the `non-trivial time complexity of protocol validation `__ to a trivial constant-time lookup. .. _protocol cost: https://github.com/python/mypy/issues/3186#issuecomment-885718629 Caveats ---------- **This metaclass will yield unpredictable results for any object with one or more methods not declared by the class of that object,** including objects whose methods are dynamically assembled at runtime. This metaclass is ill-suited for such "types." Motivation ---------- By default, :class:`typing.Protocol` subclasses are constrained to only be checkable by static type checkers (e.g., :mod:`mypy`). Checking a protocol with a runtime type checker (e.g., :mod:`beartype`) requires explicitly decorating that protocol with the :func:`typing.runtime_checkable` decorator. Why? We have no idea. For unknown (but probably indefensible) reasons, :pep:`544` authors enforced this constraint with a trivial private :class:`typing.Protocol` boolean instance variable imposing *no* space or time burden set only by the optional :func:`typing.runtime_checkable` decorator. Since that's demonstrably insane, we pretend :pep:`544` authors chose wisely by unconditionally decorating *all* :class:`beartype.typing.Protocol` subclasses by that decorator. Technically, any non-caching :class:`typing.Protocol` subclass can be effectively coerced into a caching :class:`beartype.typing.Protocol` protocol through inheritance: e.g., .. code-block:: python >>> from abc import abstractmethod >>> from typing import Protocol >>> from beartype.typing import _CachingProtocolMeta, runtime_checkable >>> @runtime_checkable ... class _MyProtocol(Protocol): # plain vanilla protocol ... @abstractmethod ... def myfunc(self, arg: int) -> str: ... pass >>> @runtime_checkable # redundant, but useful for documentation ... class MyProtocol( ... _MyProtocol, ... Protocol, ... metaclass=_CachingProtocolMeta, # caching version ... ): ... pass >>> class MyImplementation: ... def myfunc(self, arg: int) -> str: ... return str(arg * -2 + 5) >>> my_thing: MyProtocol = MyImplementation() >>> isinstance(my_thing, MyProtocol) True Pragmatically, :class:`beartype.typing.Protocol` trivially eliminates *all* of the above fragile boilerplate: e.g., .. code-block:: python >>> from beartype.typing import Protocol >>> class MyBearProtocol(Protocol): ... @abstractmethod ... def myfunc(self, arg: int) -> str: ... pass >>> my_thing: MyBearProtocol = MyImplementation() >>> isinstance(my_thing, MyBearProtocol) True ''' # ................{ CLASS VARIABLES }................ _abc_inst_check_cache: Dict[type, bool] # pyright: ignore ''' :func:`isinstance` **cache** (i.e., dictionary mapping from each type of any object previously passed as the first parameter to the :func:`isinstance` builtin whose second parameter was this protocol onto each boolean returned by that call to that builtin). ''' # ................{ DUNDERS }................ def __new__( mcls: Type[_TT], # pyright: ignore name: str, bases: Tuple[type, ...], # pyright: ignore namespace: Dict[str, Any], # pyright: ignore **kw: Any, ) -> _TT: # See cls = super().__new__(mcls, name, bases, namespace, **kw) # pyright: ignore # If this class is *NOT* the abstract "beartype.typing.Protocol" # superclass defined below... if name != 'Protocol': #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Synchronize this "if" conditional against the standard # "typing" module, which defines the exact same logic in the # Protocol.__init_subclass__() class method. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # If it is unknown whether this class is an abstract protocol # directly subclassing the "Protocol" superclass *OR* a concrete # subclass of an abstract protocol, decide which applies now. Why? # Because upstream performs the same logic. Since this logic tests # the non-transitive dunder tuple "__bases__" of all *DIRECT* # superclasses of this class rather than the transitive dunder tuple # "__mro__" of all direct and indirect superclasses of this class, # upstream logic erroneously detects abstract fast @beartype # protocols as concrete by unconditionally reducing to: # cls._is_protocol = False # # Why? Because "beartype.typing.Protocol" subclasses # "typing.Protocol", subclasses of "beartype.typing.Protocol" list # "beartype.typing.Protocol" rather than "typing.Protocol" in their # "__bases__" dunder tuple. Disaster, thy name is "typing"! if not cls.__dict__.get('_is_protocol'): # print(f'Protocol {cls} bases: {cls.__bases__}') cls._is_protocol = any(b is Protocol for b in cls.__bases__) # type: ignore[attr-defined] # If this protocol is concrete rather than abstract, monkey-patch # this concrete protocol to be implicitly type-checkable at runtime. # By default, protocols are *NOT* type-checkable at runtime unless # explicitly decorated by this nonsensical decorator. # # Note that the abstract "beartype.typing.Protocol" superclass # *MUST* be explicitly excluded from consideration. Why? For unknown # reasons, monkey-patching that superclass as implicitly # type-checkable at runtime has extreme consequences throughout the # typing ecosystem. In particular, doing so causes *ALL* # non-protocol classes to be subsequently erroneously detected as # being PEP 544-compliant protocols: e.g., # # If we monkey-patched the "Protocol" superclass as well, then # # the following snippet would insanely hold true... wat!?!?!?! # >>> from typing import Protocol # >>> class OhBoy(object): pass # >>> issubclass(OhBoy, Protocol) # True # <-- we have now destroyed the world, folks. if cls._is_protocol: # type: ignore[attr-defined] # print(f'Protocol {cls} mro: {cls.__mro__}') runtime_checkable(cls) # pyright: ignore # Else, this class is the abstract "beartype.typing.Protocol" # superclass defined below. In this case, avoid dangerously # monkey-patching this superclass. # Prefixing this class member with "_abc_" is necessary to prevent # it from being considered part of the Protocol. See also: # https://github.com/python/cpython/blob/main/Lib/typing.py cls._abc_inst_check_cache = {} # Return this caching protocol. return cls def __instancecheck__(cls, inst: Any) -> bool: ''' :data:`True` only if the passed object is a **structural subtype** (i.e., satisfies the protocol defined by) the passed protocol. Parameters ---------- cls : type :pep:`544`-compliant protocol to check this object against. inst : Any Arbitrary object to check against this protocol. Returns ---------- bool :data:`True` only if this object satisfies this protocol. ''' # Attempt to... try: #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: This *MUST* remain *SUPER* tight!! Even adding a # mere assertion here can add ~50% to our best-case runtime. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Return a pre-cached boolean indicating whether an object of # the same arbitrary type as the object passed to this call # satisfied the same protocol in a prior call of this method. return cls._abc_inst_check_cache[type(inst)] # If this method has yet to be passed the same protocol *AND* an # object of the same type as the object passed to this call... except KeyError: # If you're going to do *anything*, do it here. Try not to # expand the rest of this method if you can avoid it. inst_t = type(inst) bases_pass_muster = True for base in cls.__bases__: #FIXME: This branch probably erroneously matches unrelated #user-defined types whose names just happen to be "Generic" #or "Protocol". Ideally, we should tighten that up to only #match the actual "{beartype,}.typing.{Generic,Protocol}" #superclasses. Of course, note that #"beartype.typing.Protocol" is *NOT* "typing.Protocol', so #we'll want to explicitly test against both. if base is cls or base.__name__ in ( 'Protocol', 'Generic', 'object', ): continue if not isinstance(inst, base): bases_pass_muster = False break cls._abc_inst_check_cache[inst_t] = bases_pass_muster and ( _check_only_my_attrs(cls, inst)) return cls._abc_inst_check_cache[inst_t] # ....................{ PRIVATE ~ functions }.................... #FIXME: Docstring us up, please. #FIXME: Comment us up, please. def _check_only_my_attrs(cls, inst: Any, _EMPTY_DICT = {}) -> bool: cls_attr_name_to_value = cls.__dict__ cls_attr_name_to_hint = cls_attr_name_to_value.get( '__annotations__', _EMPTY_DICT) cls_attr_names = ( cls_attr_name_to_value | cls_attr_name_to_hint if IS_PYTHON_AT_LEAST_3_9 else dict(cls_attr_name_to_value, **cls_attr_name_to_hint) ) # For the name of each attribute declared by this protocol class... for cls_attr_name in cls_attr_names: # If... if ( # This name implies this attribute to be unignorable *AND*... # # Specifically, if this name is neither... not ( # A private attribute defined by dark machinery in the # "ABCMeta" metaclass for abstract base classes *OR*... cls_attr_name.startswith('_abc_') or # That of an ignorable non-protocol attribute... cls_attr_name in _PROTOCOL_ATTR_NAMES_IGNORABLE # This attribute is either... ) and ( # Undefined by the passed object *OR*... not hasattr(inst, cls_attr_name) or # Defined by the passed object as a "blocked" (i.e., omitted # from being type-checked as part of this protocol) method. # For unknown and indefensible reasons, PEP 544 explicitly # supports this fragile, unreadable, and error-prone idiom # enabling objects to leave methods "undefined." What this!? ( #FIXME: Unit test this up, please. # A callable *AND*... callable(getattr(cls, cls_attr_name, None)) and # The passed object nullified this method. *facepalm* getattr(inst, cls_attr_name) is None ) ) ): # Then the passed object violates this protocol. In this case, # return false. return False # Else, the passed object satisfies this protocol. In this case, return # true. return True # ....................{ CLASSES }.................... # @runtime_checkable class Protocol( _ProtocolSlow, # Force protocols to be generics. Although the standard # "typing.Protocol" superclass already implicitly subclasses from the # "typing.Generic" superclass, the non-standard # "typing_extensions.Protocol" superclass does *NOT*. Ergo, we force # this to be the case. Generic, # pyright: ignore metaclass=_CachingProtocolMeta, ): ''' :func:`beartype.beartype`-compatible (i.e., decorated by :func:`typing.runtime_checkable`) drop-in replacement for :class:`typing.Protocol` that can lead to significant performance improvements. Uses :class:`_CachingProtocolMeta` to cache :func:`isinstance` check results. Examples ---------- .. code-block:: python >>> from abc import abstractmethod >>> from beartype import beartype >>> from beartype.typing import Protocol >>> class MyBearProtocol(Protocol): # <-- runtime-checkable through inheritance ... @abstractmethod ... def myfunc(self, arg: int) -> str: ... pass >>> my_thing: MyBearProtocol = MyImplementation() >>> isinstance(my_thing, MyBearProtocol) True >>> @beartype ... def do_somthing(thing: MyBearProtocol) -> None: ... thing.myfunc(0) ''' # ..................{ CLASS VARIABLES }.................. __slots__: Any = () # ..................{ DUNDERS }.................. @callable_cached_minimal def __class_getitem__(cls, item): # We have to redefine this method because typing.Protocol's version # is very persnickety about only working for typing.Generic and # typing.Protocol. That's an exclusive club, and we ain't in it. # (RIP, GC.) Let's see if we can sneak in, shall we? # FIXME: Once is addressed, # consider replacing the madness below with something like: # cached_gen_alias = _ProtocolSlow.__class_getitem__(_ProtocolSlow, params) # our_gen_alias = cached_gen_alias.copy_with(params) # our_gen_alias.__origin__ = cls # return our_gen_alias # Superclass __class_getitem__() dunder method, localized for # brevity, efficiency, and (most importantly) to squelch false # positive "errors" from pyright with a single pragma comment. super_class_getitem = super().__class_getitem__ # pyright: ignore # If the superclass typing.Protocol.__class_getitem__() dunder # method has been wrapped as expected with caching by the private # (and thus *NOT* guaranteed to exist) @typing._tp_cache decorator, # call that unwrapped method directly to obtain the expected # generic alias. # # Note that: # * We intentionally call the unwrapped method rather than the # decorated closure wrapping that method with memoization. Why? # Because subsequent logic monkey-patches this generic alias to # refer to this class rather than the standard "typing.Protocol". # However, doing so violates internal expectations of the # @typing._tp_cache decorator performing this memoization. # * This method is already memoized by our own @callable_cached # decorator. Calling the decorated closure wrapping that # unwrapped method with memoization would needlessly consume # excess space and time for *NO* additional benefit. if hasattr(super_class_getitem, '__wrapped__'): # Protocol class to be passed as the "cls" parameter to the # unwrapped superclass typing.Protocol.__class_getitem__() # dunder method. There exist two unique cases corresponding to # two unique branches of an "if" conditional in that method, # depending on whether either this "Protocol" superclass or a # user-defined subclass of this superclass is being # subscripted. Specifically, this class is... protocol_cls = ( # If this "Protocol" superclass is being directly # subclassed by one or more type variables (e.g., # "Protocol[S, T]"), the non-caching "typing.Protocol" # superclass underlying this caching protocol superclass. # Since the aforementioned "if" conditional performs an # explicit object identity test for the "typing.Protocol" # superclass, we *MUST* pass that rather than this # superclass to trigger that conditional appropriately. _ProtocolSlow if cls is Protocol else # Else, a user-defined subclass of this "Protocol" # superclass is being subclassed by one or more type # variables *OR* types satisfying the type variables # subscripting the superclass (e.g., # "UserDefinedProtocol[str]" for a user-defined subclass # class UserDefinedProtocol(Protocol[AnyStr]). In this # case, this subclass as is. cls ) gen_alias = super_class_getitem.__wrapped__(protocol_cls, item) # We shouldn't ever be here, but if we are, we're making the # assumption that typing.Protocol.__class_getitem__() no longer # caches. Heaven help us if that ever uses some proprietary # memoization implementation we can't see anymore because it's not # based on the standard @functools.wraps decorator. else: gen_alias = super_class_getitem(item) # Switch the origin of this generic alias from its default of # "typing.Protocol" to this caching protocol class. If *NOT* done, # CPython incorrectly sets the metaclass of subclasses to the # non-caching "type(typing.Protocol)" metaclass rather than our # caching "_CachingProtocolMeta" metaclass. # # Luddite alert: we don't fully understand the mechanics here. We # suspect no one does. gen_alias.__origin__ = cls # We're done! Time for a honey brewskie break. We earned it. return gen_alias #FIXME: Ensure that the main @beartype codebase handles protocols whose #repr() starts with "beartype.typing" as well, please. # Replace the unexpected (and thus non-compliant) fully-qualified name of # the module declaring this caching protocol superclass (e.g., # "beartype.typing._typingpep544") with the expected (and thus compliant) # fully-qualified name of the standard "typing" module declaring the # non-caching "typing.Protocol" superclass. # # If this is *NOT* done, then the machine-readable representation of this # caching protocol superclass when subscripted by one or more type # variables (e.g., "beartype.typing.Protocol[S, T]") will be differ # significantly from that of the non-caching "typing.Protocol" superclass # (e.g., beartype.typing._typingpep544.Protocol[S, T]"). Because # @beartype (and possibly other third-party packages) expect the two # representations to comply, this awkward monkey-patch preserves sanity. Protocol.__module__ = 'beartype.typing' # ....................{ PROTOCOLS }.................... class SupportsAbs(_SupportsAbsSlow[_T_co], Protocol, Generic[_T_co]): ''' Caching variant of :class:`typing.SupportsAbs`. ''' __module__: str = 'beartype.typing' __slots__: Any = () class SupportsBytes(_SupportsBytesSlow, Protocol): ''' Caching variant of :class:`typing.SupportsBytes`. ''' __module__: str = 'beartype.typing' __slots__: Any = () class SupportsComplex(_SupportsComplexSlow, Protocol): ''' Caching variant of :class:`typing.SupportsComplex`. ''' __module__: str = 'beartype.typing' __slots__: Any = () class SupportsFloat(_SupportsFloatSlow, Protocol): ''' Caching variant of :class:`typing.SupportsFloat`." ''' __module__: str = 'beartype.typing' __slots__: Any = () class SupportsInt(_SupportsIntSlow, Protocol): ''' Caching variant of :class:`typing.SupportsInt`. ''' __module__: str = 'beartype.typing' __slots__: Any = () class SupportsIndex(_SupportsIndexSlow, Protocol): ''' Caching variant of :class:`typing.SupportsIndex`. ''' __module__: str = 'beartype.typing' __slots__: Any = () class SupportsRound(_SupportsRoundSlow[_T_co], Protocol, Generic[_T_co]): ''' Caching variant of :class:`typing.SupportsRound`. ''' __module__: str = 'beartype.typing' __slots__: Any = () # ....................{ MONKEY-PATCHES }.................... # If the active Python interpreter targets Python >= 3.12, monkey-patch the # standard "typing" module to support our "Protocol" superclass. if IS_PYTHON_AT_LEAST_3_12: import typing from typing import _generic_class_getitem as _generic_class_getitem_old # type: ignore[attr-defined] def _generic_class_getitem_new(cls, params): ''' Beartype-specific wrapper for the private :func:`typing._generic_class_getitem` utility function, enabling that function to transparently support our beartype-specific :class:`beartype.typing.Protocol` superclass equivalent to the standard :class:`typing.Protocol` superclass. ''' # If the passed class is our "beartype.typing.Protocol" superclass, # silently replace that with "typing.Protocol" *BEFORE* calling the # standard typing._generic_class_getitem() utility function -- which # explicitly only supports the latter. if cls is Protocol: cls = _ProtocolSlow # Else, the passed class is *NOT* our "beartype.typing.Protocol" # superclass. In this case, preserve that class as is. # Defer to the standard typing._generic_class_getitem() implementation. return _generic_class_getitem_old(cls, params) # Replace the standard typing._generic_class_getitem() implementation with # the wrapper defined above. *gulp* typing._generic_class_getitem = _generic_class_getitem_new # type: ignore[attr-defined] beartype-0.18.5/beartype/vale/000077500000000000000000000000001461113517100161625ustar00rootroot00000000000000beartype-0.18.5/beartype/vale/__init__.py000066400000000000000000000171531461113517100203020ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype validator API.** This submodule publishes a PEP-compliant hierarchy of subscriptable (indexable) classes enabling callers to validate the internal structure of arbitrarily complex scalars, data structures, and third-party objects. Like annotation objects defined by the :mod:`typing` module (e.g., :attr:`typing.Union`), these classes dynamically generate PEP-compliant type hints when subscripted (indexed) and are thus intended to annotate callables and variables. Unlike annotation objects defined by the :mod:`typing` module, these classes are *not* explicitly covered by existing PEPs and thus *not* directly usable as annotations. Instead, callers are expected to (in order): #. Annotate callable parameters and returns to be validated with :pep:`593`-compliant :attr:`typing.Annotated` type hints. #. Subscript those hints with (in order): #. The type of those parameters and returns. #. One or more subscriptions of classes declared by this submodule. ''' # ....................{ TODO }.................... #FIXME: [FEATURE] Add a new "beartype.vale.IsInline" validator factory, #elegantly resolving issue #82 and presumably other future issues, too. The #core idea here is that "beartype.vale.IsInline" will enable callers to #directly embed arbitrary test code substrings in the bodies of wrapper #functions dynamically generated by @beartype. The signature resembles: # beartype.vale.IsInline[code: str, arg_1: object, ..., arg_N: object] #...where: #* "arg_1" through "arg_N" are optional arbitrary objects to be made available # through the corresponding format variables "{arg_1}" through "{arg_N}" in # the "code" substring. These arguments are the *ONLY* safe means of exposing # non-builtin objects to the "code" substring. #* "code" is a mandatory arbitrary test code substring. This substring *MUST* # contain at least this mandatory format variable: # * "{obj}", expanding to the current object being validated. Should this # perhaps be "{pith}" instead for disambiguity? *shrug* # Additionally, for each optional "arg_{index}" object subscripting this # "IsInline" factory, this "code" substring *MUST* contain at least one # corresponding format variable "{arg_{index}}". For example: # # This is valid. # IsInline['len({obj}) == len({arg_1})', ['muh', 'list',]] # # # This is invalid, because the "['muh', 'list',]" list argument is # # *NEVER* referenced via "{arg_1}" in this code snippet. # IsInline['len({obj}) == 2', ['muh', 'list',]] # Lastly, this substring may additionally contain these optional format # variables: # * "{indent}", expanding to the current indentation level. Specifically: # * Any "code" substring beginning with a newline *MUST* contain one or more # "{indent}" variables. # * Any "code" substring *NOT* beginning with a newline must *NOT* contain # any "{indent}" variables. #FIXME: As intelligently requested by @Saphyel at #32, add support for #additional classes support constraints resembling: # #* String constraints: # * Email. # * Uuid. # * Choice. # * Language. # * Locale. # * Country. # * Currency. #* Comparison constraints # * IdenticalTo. # * NotIdenticalTo. # * LessThan. # * GreaterThan. # * Range. # * DivisibleBy. #FIXME: Add a new BeartypeValidator.find_cause() method with the same #signature and docstring as the existing ViolationCause.find_cause() #method. This new BeartypeValidator.find_cause() method should then be #called by the "_peperrorannotated" submodule to generate human-readable #exception messages. Note that this implies that: #* The BeartypeValidator.__init__() method will need to additionally accept a new # mandatory "find_cause: Callable[[], Optional[str]]" parameter, which # that method should then localize to "self.find_cause". #* Each __class_getitem__() dunder method of each "_BeartypeValidatorFactoryABC" subclass will need # to additionally define and pass that callable when creating and returning # its "BeartypeValidator" instance. #FIXME: *BRILLIANT IDEA.* Holyshitballstime. The idea here is that we can #leverage all of our existing "beartype.is" infrastructure to dynamically #synthesize PEP-compliant type hints that would then be implicitly supported by #any runtime type checker. At present, subscriptions of "Is" (e.g., #"Annotated[str, Is[lambda text: bool(text)]]") are only supported by beartype #itself. Of course, does anyone care? I mean, if you're using a runtime type #checker, you're probably *ONLY* using beartype. Right? That said, this would #technically improve portability by allowing users to switch between different #checkers... except not really, since they'd still have to import beartype #infrastructure to do so. So, this is probably actually useless. # #Nonetheless, the idea itself is trivial. We declare a new #"beartype.is.Portable" singleton accessed in the same way: e.g., # from beartype import beartype # from beartype.is import Portable # NonEmptyStringTest = Is[lambda text: bool(text)] # NonEmptyString = Portable[str, NonEmptyStringTest] # @beartype # def munge_it(text: NonEmptyString) -> str: ... # #So what's the difference between "typing.Annotated" and "beartype.is.Portable" #then? Simple. The latter dynamically generates one new PEP 3119-compliant #metaclass and associated class whenever subscripted. Clearly, this gets #expensive in both space and time consumption fast -- which is why this won't #be the default approach. For safety, this new class does *NOT* subclass the #first subscripted class. Instead: #* This new metaclass of this new class simply defines an __isinstancecheck__() # dunder method. For the above example, this would be: # class NonEmptyStringMetaclass(object): # def __isinstancecheck__(cls, obj) -> bool: # return isinstance(obj, str) and NonEmptyStringTest(obj) #* This new class would then be entirely empty. For the above example, this # would be: # class NonEmptyStringClass(object, metaclass=NonEmptyStringMetaclass): # pass # #Well, so much for brilliant. It's slow and big, so it seems doubtful anyone #would actually do that. Nonetheless, that's food for thought for you. # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To avoid polluting the public module namespace, external attributes # should be locally imported at module scope *ONLY* under alternate private # names (e.g., "from argparse import ArgumentParser as _ArgumentParser" rather # than merely "from argparse import ArgumentParser"). #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype.vale._is._valeis import _IsFactory from beartype.vale._is._valeistype import ( _IsInstanceFactory, _IsSubclassFactory, ) from beartype.vale._is._valeisobj import _IsAttrFactory from beartype.vale._is._valeisoper import _IsEqualFactory # ....................{ SINGLETONS }.................... # Public factory singletons instantiating these private factory classes. Is = _IsFactory(basename='Is') IsAttr = _IsAttrFactory(basename='IsAttr') IsEqual = _IsEqualFactory(basename='IsEqual') IsInstance = _IsInstanceFactory(basename='IsInstance') IsSubclass = _IsSubclassFactory(basename='IsSubclass') # Delete all private factory classes imported above for safety. del ( _IsFactory, _IsAttrFactory, _IsEqualFactory, _IsInstanceFactory, _IsSubclassFactory, ) beartype-0.18.5/beartype/vale/_core/000077500000000000000000000000001461113517100172515ustar00rootroot00000000000000beartype-0.18.5/beartype/vale/_core/__init__.py000066400000000000000000000000001461113517100213500ustar00rootroot00000000000000beartype-0.18.5/beartype/vale/_core/_valecore.py000066400000000000000000000565771461113517100216060ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Core beartype validator.** This private submodule defines the core private :class:`BeartypeValidator` class instantiated by public **beartype validator factories** (i.e., instances of concrete subclasses of the private :class:`beartype._vale._factory._valeisabc._BeartypeValidatorFactoryABC` abstract base class (ABC)). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeValeSubscriptionException from beartype.vale._util._valeutilfunc import die_unless_validator_tester from beartype.vale._util._valeutiltext import format_diagnosis_line from beartype.vale._util._valeutiltyping import ( BeartypeValidatorTester, BeartypeValidatorRepresenter, ) from beartype._data.hint.datahinttyping import LexicalScope from beartype._util.func.arg.utilfuncargtest import is_func_argless from beartype._util.text.utiltextrepr import represent_object # ....................{ CLASSES }.................... class BeartypeValidator(object): ''' **Beartype validator** (i.e., object encapsulating a caller-defined validation callable returning ``True`` when an arbitrary object passed to that callable satisfies an arbitrary constraint, suitable for subscripting (indexing) :pep:`593`-compliant :attr:`typing.Annotated` type hints enforcing that validation on :mod:`beartype`-decorated callable parameters and returns annotated by those hints). Caveats ---------- **This private class is not intended to be externally instantiated** (e.g., by calling the :meth:`__init__` constructor). This class is *only* intended to be internally instantiated by subscripting (indexing) various public type hint factories (e.g., :class:`beartype.vale.Is`). Attributes ---------- _get_repr : BeartypeValidatorRepresenter **Representer** (i.e., either a string *or* caller-defined callable accepting no arguments returning a machine-readable representation of this validator). See the :data:`BeartypeValidatorRepresenter` type hint for further details. _is_valid : BeartypeValidatorTester **Validator tester** (i.e., caller-defined callable accepting a single arbitrary object and returning either ``True`` if that object satisfies an arbitrary constraint *or* ``False`` otherwise). _is_valid_code : str **Validator code** (i.e., Python code snippet validating the previously localized parameter or return value against the same validation performed by the :meth:`is_valid` function). For efficiency, callers validating data through dynamically generated code (e.g., the :func:`beartype.beartype` decorator) rather than standard function calls (e.g., the private :mod:`beartype._decor._hint._pep._error` subpackage) should prefer :attr:`is_valid_code` to :meth:`is_valid`. Despite performing the same validation as the :meth:`is_valid` callable, this code avoids the additional stack frame imposed by calling that callable and thus constitutes an optimization. _is_valid_code_locals : LexicalScope **Validator code local scope** (i.e., dictionary mapping from the name to value of each local attribute referenced in :attr:`code`) required to dynamically compile this validator code into byte code at runtime. See Also ---------- :class:`Is` Class docstring for further details. ''' # ..................{ CLASS VARIABLES }.................. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Subclasses declaring uniquely subclass-specific instance # variables *MUST* additionally slot those variables. Subclasses violating # this constraint will be usable but unslotted, which defeats the purpose. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Slot all instance variables defined on this object to reduce the costs of # both reading and writing these variables by approximately ~10%. __slots__ = ( '_get_repr', '_is_valid', '_is_valid_code', '_is_valid_code_locals', ) # ..................{ INITIALIZERS }.................. def __init__( self, *, # Mandatory keyword-only parameters. is_valid: BeartypeValidatorTester, is_valid_code: str, is_valid_code_locals: LexicalScope, get_repr: BeartypeValidatorRepresenter, ) -> None: ''' Initialize this validator from the passed metadata. Parameters ---------- is_valid : BeartypeValidatorTester **Validator tester** (i.e., caller-defined callable accepting a single arbitrary object and returning either ``True`` if that object satisfies an arbitrary constraint *or* ``False`` otherwise). is_valid_code : str **Validator code** (i.e., Python code snippet validating the previously localized parameter or return value against the same validation performed by the :func:`is_valid` function). This code: * *Must* contain one or more ``"{obj}"`` substrings, which external code generators (e.g., the :func:`beartype.beartype` decorator) will globally replace at evaluation time with the actual test subject object to be validated by this code. * *May* contain one or more ``"{indent}"`` substrings, which such code generators will globally replace at evaluation time with the line-oriented indentation required to generate a valid Python statement embedding this code. For consistency with :pep:`8`-compliant and well-established Python style guides, any additional indentation hard-coded into this code should be aligned to **four-space indentation.** is_valid_code_locals : LexicalScope **Validator code local scope** (i.e., dictionary mapping from the name to value of each local attribute referenced in :attr:`is_valid_code` code) required to dynamically compile this validator code into byte code at runtime. get_repr : BeartypeValidatorRepresenter **Representer** (i.e., either a string *or* caller-defined callable accepting no arguments returning a machine-readable representation of this validator). See the :data:`BeartypeValidatorRepresenter` type hint for further details. Raises ---------- beartype.roar.BeartypeValeSubscriptionException If either: * ``is_valid`` is either: * *Not* callable. * A C-based rather than pure-Python callable. * A pure-Python callable accepting two or more arguments. * ``is_valid_code`` is either: * *Not* a string. * A string either: * Empty. * Non-empty but **invalid** (i.e., *not* containing the test subject substring ``{obj}``). * ``is_valid_locals`` is *not* a dictionary. * ``get_repr`` is either: * *Not* callable. * A C-based rather than pure-Python callable. * A pure-Python callable accepting one or more arguments. * The empty string. ''' # Avoid circular import dependencies. from beartype.vale._is._valeisabc import _BeartypeValidatorFactoryABC # If that callable is *NOT* a validator tester, raise an exception. die_unless_validator_tester(is_valid) # Else, that callable is a validator tester. # If this code is *NOT* a string, raise an exception. if not isinstance(is_valid_code, str): raise BeartypeValeSubscriptionException( f'Validator code not string:\n' f'{represent_object(is_valid_code)}' ) # Else, this code is a string. # # If this code is the empty string, raise an exception. elif not is_valid_code: raise BeartypeValeSubscriptionException('Validator code empty.') # Else, this code is a non-empty string. # # If this code does *NOT* contain the test subject substring # "{obj}" and is invalid, raise an exception. elif '{obj}' not in is_valid_code: raise BeartypeValeSubscriptionException( f'Validator code invalid ' f'(i.e., test subject substring "{{obj}}" not found):\n' f'{is_valid_code}' ) # Else, this code is hopefully valid. # # If this code is *NOT* explicitly prefixed by "(" and suffixed by # ")", do so to ensure this code remains safely evaluable when # embedded in parent expressions. elif not ( is_valid_code[ 0] == '(' and is_valid_code[-1] == ')' ): is_valid_code = f'({is_valid_code})' # Else, this code is explicitly prefixed by "(" and suffixed by ")". # If this dictionary of code locals is *NOT* a dictionary, raise an # exception. if not isinstance(is_valid_code_locals, dict): raise BeartypeValeSubscriptionException( f'Validator locals ' f'{represent_object(is_valid_code_locals)} not dictionary.' ) # Else, this dictionary of code locals is a dictionary. # Classify this validator, effectively binding this callable to this # object as an object-specific static method. self._is_valid = is_valid # Classify this representer via a writeable property internally # validating this representer. (Embrace the magical, people.) self.get_repr = get_repr # Classify all remaining parameters. self._is_valid_code = is_valid_code self._is_valid_code_locals = is_valid_code_locals # ..................{ PROPERTIES ~ read-only }.................. # Properties with no corresponding setter and thus read-only. @property def is_valid(self) -> BeartypeValidatorTester: ''' **Validator callable** (i.e., caller-defined callable accepting a single arbitrary object and returning either ``True`` if that object satisfies an arbitrary constraint *or* ``False`` otherwise). ''' return self._is_valid # ..................{ PROPERTIES ~ writeable }.................. # Properties with a corresponding setter and thus writeable. @property def get_repr(self) -> BeartypeValidatorRepresenter: ''' **Representer** (i.e., either a string *or* caller-defined callable accepting no arguments returning a machine-readable representation of this validator). See the :data:`BeartypeValidatorRepresenter` type hint for further details. ''' return self._get_repr @get_repr.setter def get_repr(self, get_repr: BeartypeValidatorRepresenter) -> None: ''' Override the initial representer for this validator. Parameters ---------- get_repr : BeartypeValidatorRepresenter **Representer** (i.e., either a string *or* caller-defined callable accepting no arguments returning a machine-readable representation of this validator). See the :data:`BeartypeValidatorRepresenter` type hint for further details. Raises ---------- :exc:`BeartypeValeSubscriptionException` This representer is either: * *Not* callable. * A C-based rather than pure-Python callable. * A pure-Python callable accepting one or more arguments. ''' # If this representer is a string... if isinstance(get_repr, str): # If this string is empty, raise an exception. if not get_repr: raise BeartypeValeSubscriptionException( 'Representer string empty.') # Else, this representer is *NOT* a string. # # If this representer is *NOT* a pure-Python callable accepting one # argument, raise an exception. elif not is_func_argless( func=get_repr, exception_cls=BeartypeValeSubscriptionException): raise BeartypeValeSubscriptionException( f'Representer {repr(get_repr)} neither string nor ' f'argumentless pure-Python callable.' ) # Else, this representer is an argumentless pure-Python callable. # Set this representer. self._get_repr = get_repr # ..................{ DUNDERS ~ str }.................. def __repr__(self) -> str: ''' Machine-readable representation of this validator. This function is memoized for efficiency. Warns ---------- BeartypeValeLambdaWarning If this validator is implemented as a pure-Python lambda function whose definition is *not* parsable from the script or module defining that lambda. ''' # If the instance variable underlying this dunder method is a callable, # reduce this variable to the string returned by this callable. if callable(self._get_repr): self._get_repr = self._get_repr() # In either case, this variable is now a string. Guarantee this. assert isinstance(self._get_repr, str), f'{self._get_repr} not string.' # Return this string as is. return self._get_repr # ..................{ GETTERS }.................. def get_diagnosis( self, *, # Mandatory keyword-only parameters. obj: object, indent_level_outer: str, indent_level_inner: str, # Optional keyword-only parameters. is_shortcircuited: bool = False, ) -> str: ''' Human-readable **validation failure diagnosis** (i.e., substring describing how the passed object either satisfies *or* violates this validator). This method is typically called by high-level error-handling logic to unambiguously describe the failure of an arbitrary object to satisfy an arbitrary validator. Since this validator may be synthesized from one or more lower-level validators (e.g., via the :meth:`__and__`, :meth:`__or__`, and :meth:`__invert__` dunder methods), the simple machine-readable representation of this validator does *not* adequately describe how exactly the passed object satisfies or fails to satisfy this validator. Only an exhaustive description suffices. Parameters ---------- obj : object Arbitrary object to be diagnosed against this validator. indent_level_outer : str **Outermost indentation level** (i.e., zero or more adjacent spaces prefixing each line of the returned substring). indent_level_inner : str **Innermost indentation level** (i.e., zero or more adjacent spaces delimiting the human-readable representation of the tri-state boolean and validator representation in the returned substring). is_shortcircuited : bool, optional ``True`` only if this validator has been **short-circuited** (i.e., *not* required to be tested against) by a previously tested sibling validator, in which case this method will silently catch and reduce exceptions raised by the :meth:`is_valid` method to ``False``. Short-circuiting typically arises from binary validators (e.g., :class:`beartype.vale._core._valecore.BeartypeValidatorConjunction`) in which a low-level sibling validator, previously tested against by the higher-level binary validator encapsulating both this validator and that sibling validator, has already either fully satisfied *or* failed to satisfy that binary validator; a binary validator explicitly sets this parameter to ``True`` for *all* children validators except the first child validator when the first child validator either fully satisfies *or* fails to satisfy that binary validator. This is *not* merely an optimization; this is a design requirement. External users often chain validators together with set operators (e.g., ``&``, ``|``) under the standard expectation of short-circuiting, in which later validators are *not* tested when earlier validators already satisfy requirements. Violating this expectation causes later validators to trivially raise exceptions. Without short-circuiting, the otherwise valid following example raises a non-human-readable exception. The short-circuited ``IsArrayMatrix`` validator expects to be tested *only* when the preceding non-short-circuited ``IsArray2D`` validator fails: .. code-block:: python >>> import numpy as np >>> from beartype.vale import Is >>> IsArray2D = Is[lambda arr: arr.ndim == 2] >>> IsArrayMatrix = Is[lambda arr: arr.shape[0] == arr.shape[1]] >>> IsArray2DMatrix = IsArray2D & IsArrayMatrix >>> IsArray2DMatrix.get_diagnosis( ... obj=np.zeros((4,)), ... indent_level_outer='', ... indent_level_inner=' ', ... ) Traceback (most recent call last): File "/home/leycec/tmp/mopy.py", line 10, in print(IsArray2DMatrix.get_diagnosis( File "/home/leycec/py/beartype/beartype/vale/_core/_valecorebinary.py", line 149, in get_diagnosis line_inner_operand_2 = self._validator_operand_2.get_diagnosis( File "/home/leycec/py/beartype/beartype/vale/_core/_valecore.py", line 480, in get_diagnosis is_obj_valid = self.is_valid(obj) File "/home/leycec/tmp/mopy.py", line 7, in IsArrayMatrix = Is[lambda arr: arr.shape[0] == arr.shape[1]] IndexError: tuple index out of range Defaults to ``False``. Returns ---------- str Substring diagnosing this object against this validator. ''' assert isinstance(is_shortcircuited, bool), ( f'{repr(is_shortcircuited)} not boolean.') # True only if the passed object satisfies this validator. is_obj_valid = None # If this validator has been short-circuited by a prior sibling... if is_shortcircuited: # Attempt to decide whether that object satisfies this validator. try: is_obj_valid = self.is_valid(obj) # If doing so raises an exception, this short-circuited validator # was *NOT* intended to be called under short-circuiting. In this # case, silently ignore this exception. See the above discussion. except: pass # Else, this validator is *NOT* short-circuited. In this case, this # validator is *NOT* expected to raise exceptions. Nonetheless, if this # validator does so, ensure that exception is propagated up the call # stack by *NOT* silently ignoring that exception (as above). else: is_obj_valid = self.is_valid(obj) # Format the validity of this object against this validator for the # typical case of a lowest-level beartype validator *NOT* wrapping one # or more other even lower-level beartype validators (e.g., via a set # theoretic operator). return format_diagnosis_line( validator_repr=repr(self), indent_level_outer=indent_level_outer, indent_level_inner=indent_level_inner, is_obj_valid=is_obj_valid, ) # ..................{ DUNDERS ~ operator }.................. # Define a domain-specific language (DSL) enabling callers to dynamically # synthesize higher-level validators from lower-level validators via # overloaded set theoretic operators. def __and__(self, other: 'BeartypeValidator') -> 'BeartypeValidator': ''' **Conjunction** (i.e., ``self & other``), synthesizing a new :class:`BeartypeValidator` object whose validator returns :data:`True` only when the validators of both this *and* the passed :class:`BeartypeValidator` objects all return :data:`True`. Parameters ---------- other : BeartypeValidator Object to conjunctively synthesize with this object. Returns ---------- BeartypeValidator New object conjunctively synthesized with this object. Raises ---------- BeartypeValeSubscriptionException If the passed object is *not* also an instance of the same class. ''' # Avoid circular import dependencies. from beartype.vale._core._valecorebinary import ( BeartypeValidatorConjunction) # Closures for great justice. return BeartypeValidatorConjunction( validator_operand_1=self, validator_operand_2=other, ) def __or__(self, other: 'BeartypeValidator') -> 'BeartypeValidator': ''' **Disjunction** (i.e., ``self | other``), synthesizing a new :class:`BeartypeValidator` object whose validator returns :data:`True` only when the validators of either this *or* the passed :class:`BeartypeValidator` objects return :data:`True`. Parameters ---------- other : BeartypeValidator Object to disjunctively synthesize with this object. Returns ---------- BeartypeValidator New object disjunctively synthesized with this object. ''' # Avoid circular import dependencies. from beartype.vale._core._valecorebinary import ( BeartypeValidatorDisjunction) # Closures for great justice. return BeartypeValidatorDisjunction( validator_operand_1=self, validator_operand_2=other, ) #FIXME: Fun optimization: if inverting something that's already been #inverted, return the original "BeartypeValidator" object sans inversion. def __invert__(self) -> 'BeartypeValidator': ''' **Negation** (i.e., ``~self``), synthesizing a new :class:`BeartypeValidator` object whose validator returns :data:`True` only when the validators of this :class:`BeartypeValidator` object returns :data:`False`. Returns ---------- BeartypeValidator New object negating this object. ''' # Avoid circular import dependencies. from beartype.vale._core._valecoreunary import ( BeartypeValidatorNegation) # Closures for profound lore. return BeartypeValidatorNegation(validator_operand=self) beartype-0.18.5/beartype/vale/_core/_valecorebinary.py000066400000000000000000000370411461113517100227740ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Core unary beartype validators** (i.e., :class:`BeartypeValidator` subclasses implementing binary operations on pairs of lower-level beartype validators). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from abc import ABCMeta, abstractmethod from beartype.roar import BeartypeValeSubscriptionException from beartype.vale._core._valecore import BeartypeValidator from beartype.vale._util._valeutiltext import format_diagnosis_line from beartype._util.kind.map.utilmapset import merge_mappings_two from beartype._data.code.datacodeindent import CODE_INDENT_1 from beartype._util.text.utiltextrepr import represent_object # ....................{ SUPERCLASSES }.................... class BeartypeValidatorBinaryABC(BeartypeValidator, metaclass=ABCMeta): ''' Abstract base class of all **beartype binary validator** (i.e., validator modifying the boolean truthiness returned by the validation performed by a pair of lower-level beartype validators) subclasses. Attributes ---------- _validator_operand_1 : BeartypeValidator First lower-level validator operated upon by this higher-level validator. _validator_operand_2 : BeartypeValidator Second lower-level validator operated upon by this higher-level validator. ''' # ..................{ CLASS VARIABLES }.................. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Subclasses declaring uniquely subclass-specific instance # variables *MUST* additionally slot those variables. Subclasses violating # this constraint will be usable but unslotted, which defeats our purposes. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Slot all instance variables defined on this object to minimize the time # complexity of both reading and writing variables across frequently called # cache dunder methods. Slotting has been shown to reduce read and write # costs by approximately ~10%, which is non-trivial. __slots__ = ( '_validator_operand_1', '_validator_operand_2', ) # ..................{ INITIALIZERS }.................. def __init__( self, validator_operand_1: BeartypeValidator, validator_operand_2: BeartypeValidator, **kwargs ) -> None: ''' Initialize this higher-level validator from the passed validators. Parameters ---------- validator_operand_1 : BeartypeValidator First validator operated upon by this higher-level validator. validator_operand_2 : BeartypeValidator Second validator operated upon by this higher-level validator. All remaining parameters are passed as is to the superclass :meth:`BeartypeValidator.__init__` method. Raises ---------- BeartypeValeSubscriptionException If either of these operands are *not* beartype validators. ''' # Locals safely merging the locals required by the code provided by # both validators. is_valid_code_locals = merge_mappings_two( validator_operand_1._is_valid_code_locals, validator_operand_2._is_valid_code_locals, ) # Callable accepting no arguments returning a machine-readable # representation of this binary validator. get_repr = lambda: ( f'{repr(validator_operand_1)} {self._operator_symbol} ' f'{repr(validator_operand_2)}' ) # Initialize our superclass with all remaining parameters. super().__init__( is_valid_code_locals=is_valid_code_locals, # type: ignore[arg-type] get_repr=get_repr, **kwargs ) # Classify all remaining parameters. self._validator_operand_1 = validator_operand_1 self._validator_operand_2 = validator_operand_2 # ..................{ GETTERS }.................. #FIXME: Unit test us up, please. #FIXME: Overly verbose for conjunctions involving three or more #beartype validators. Contemplate compaction schemes, please. Specifically, #we need to detect this condition here and then compact based on that: # # If either of these validators are themselves conjunctions... # if isinstance(self._validator_operand_1, BeartypeValidatorConjunction): # ... # if isinstance(self._validator_operand_2, BeartypeValidatorConjunction): # ... def get_diagnosis( self, *, # Mandatory keyword-only parameters. obj: object, indent_level_outer: str, indent_level_inner: str, # Optional keyword-only parameters. is_shortcircuited: bool = False, ) -> str: # Innermost indentation level indented one level deeper than the passed # innermost indentation level. indent_level_inner_nested = indent_level_inner + CODE_INDENT_1 # Line diagnosing this object against this parent conjunction. line_outer_prefix = format_diagnosis_line( validator_repr='(', indent_level_outer=indent_level_outer, indent_level_inner=indent_level_inner, is_obj_valid=self.is_valid(obj), ) # Line diagnosing this object against this first child validator, with # an increased indentation level for readability. line_inner_operand_1 = self._validator_operand_1.get_diagnosis( obj=obj, indent_level_outer=indent_level_outer, indent_level_inner=indent_level_inner_nested, is_shortcircuited=is_shortcircuited, ) # If this binary validator has *NOT* already been short-circuited, # decide whether this first child validator short-circuits this second # child validator with respect to the passed object. if not is_shortcircuited: is_shortcircuited = self._is_shortcircuited(obj) # Else, this binary validator has already been short-circuited (e.g., # due to being embedded in a higher-level parent validator that was # short-circuited with respect to the passed object). In this case, # preserve this short-circuiting as is. # Line diagnosing this object against this second child validator, with # an increased indentation level for readability. line_inner_operand_2 = self._validator_operand_2.get_diagnosis( obj=obj, indent_level_outer=indent_level_outer, indent_level_inner=indent_level_inner_nested, is_shortcircuited=is_shortcircuited, ) # Line providing the suffixing ")" delimiter for readability. line_outer_suffix = format_diagnosis_line( validator_repr=')', indent_level_outer=indent_level_outer, indent_level_inner=indent_level_inner, ) # Return these lines concatenated. return ( f'{line_outer_prefix}\n' f'{line_inner_operand_1} {self._operator_symbol}\n' f'{line_inner_operand_2}\n' f'{line_outer_suffix}' ) # ..................{ ABSTRACT }.................. # Abstract methods required to be concretely implemented by subclasses. @property @abstractmethod def _operator_symbol(self) -> str: ''' Human-readable string embodying the operation performed by this binary validator - typically the single-character mathematical sign symbolizing this operation. ''' pass @abstractmethod def _is_shortcircuited(self, obj: object) -> bool: ''' ``True`` only if the first child validator short-circuits the second child validator underlying this parent validator with respect to the passed object. In this context, "short-circuits" is in the boolean evaluation sense. Specifically, short-circuiting: * Occurs when the first child validator either fully satisfies or violates this parent validator with respect to the passed object. * Implies the second child validator to be safely ignorable with respect to the passed object. Parameters ---------- obj : object Arbitrary object to be diagnosed against this validator. Returns ---------- bool ``True`` only if this the passed object short-circuits the second child operand validator underlying this parent binary validator. ''' pass # ....................{ SUBCLASSES ~ & }.................... class BeartypeValidatorConjunction(BeartypeValidatorBinaryABC): ''' **Beartype conjunction validator** (i.e., validator conjunctively evaluating the boolean truthiness returned by the validation performed by a pair of lower-level beartype validators, typically instantiated and returned by the :meth:`BeartypeValidator.__and__` dunder method of the first validator passed the second). ''' # ..................{ INITIALIZERS }.................. def __init__( self, validator_operand_1: BeartypeValidator, validator_operand_2: BeartypeValidator, ) -> None: ''' Initialize this higher-level validator from the passed validators. Parameters ---------- validator_operand_1 : BeartypeValidator First validator operated upon by this higher-level validator. validator_operand_2 : BeartypeValidator Second validator operated upon by this higher-level validator. Raises ---------- BeartypeValeSubscriptionException If either of these operands are *not* beartype validators. ''' # Validate the passed operands as sane. _validate_operands(self, validator_operand_1, validator_operand_2) # Initialize our superclass with all remaining parameters. super().__init__( validator_operand_1=validator_operand_1, validator_operand_2=validator_operand_2, # Lambda function conjunctively performing both validations. is_valid=lambda obj: ( validator_operand_1.is_valid(obj) and validator_operand_2.is_valid(obj) ), # Code expression conjunctively performing both validations. is_valid_code=( f'({validator_operand_1._is_valid_code} and ' f'{validator_operand_2._is_valid_code})' ), ) # ..................{ PROPERTIES }.................. @property def _operator_symbol(self) -> str: return '&' def _is_shortcircuited(self, obj: object) -> bool: # Return true only if the passed object violates this first child # validator. Why? Because if this first child validator is violated, # then this parent validator as a whole is violated; no further # validation of this second child validator is required. return not self._validator_operand_1.is_valid(obj) # ....................{ SUBCLASSES ~ | }.................... class BeartypeValidatorDisjunction(BeartypeValidatorBinaryABC): ''' **Beartype disjunction validator** (i.e., validator disjunctively evaluating the boolean truthiness returned by the validation performed by a pair of lower-level beartype validators, typically instantiated and returned by the :meth:`BeartypeValidator.__and__` dunder method of the first validator passed the second). ''' # ..................{ INITIALIZERS }.................. def __init__( self, validator_operand_1: BeartypeValidator, validator_operand_2: BeartypeValidator, ) -> None: ''' Initialize this higher-level validator from the passed validators. Parameters ---------- validator_operand_1 : BeartypeValidator First validator operated upon by this higher-level validator. validator_operand_2 : BeartypeValidator Second validator operated upon by this higher-level validator. Raises ---------- BeartypeValeSubscriptionException If either of these operands are *not* beartype validators. ''' # Validate the passed operands as sane. _validate_operands(self, validator_operand_1, validator_operand_2) # Initialize our superclass with all remaining parameters. super().__init__( validator_operand_1=validator_operand_1, validator_operand_2=validator_operand_2, # Lambda function disjunctively performing both validations. is_valid=lambda obj: ( validator_operand_1.is_valid(obj) or validator_operand_2.is_valid(obj) ), # Code expression disjunctively performing both validations. is_valid_code=( f'({validator_operand_1._is_valid_code} or ' f'{validator_operand_2._is_valid_code})' ), ) # ..................{ PROPERTIES }.................. @property def _operator_symbol(self) -> str: return '|' def _is_shortcircuited(self, obj: object) -> bool: # Return true only if the passed object satisfies this first child # validator. Why? Because if this first child validator is satisfied, # then this parent validator as a whole is satisfied; no further # validation of this second child validator is required. return self._validator_operand_1.is_valid(obj) # ....................{ PRIVATE ~ validators }.................... def _validate_operands( self: BeartypeValidatorBinaryABC, validator_operand_1: BeartypeValidator, validator_operand_2: BeartypeValidator, ) -> None: ''' Validate the passed validator operands as sane. Parameters ---------- self : BeartypeValidatorBinaryABC Beartype binary validator operating upon these operands. validator_operand_1 : BeartypeValidator First validator operated upon by this higher-level validator. validator_operand_2 : BeartypeValidator Second validator operated upon by this higher-level validator. Raises ---------- BeartypeValeSubscriptionException If either of these operands are *not* beartype validators. ''' # If either of these operands are *NOT* beartype validators, raise an # exception. if not isinstance(validator_operand_1, BeartypeValidator): raise BeartypeValeSubscriptionException( f'Beartype "{self._operator_symbol}" validator first operand ' f'{represent_object(validator_operand_1)} not beartype ' f'validator (i.e., "beartype.vale.Is*[...]" object).' ) elif not isinstance(validator_operand_2, BeartypeValidator): raise BeartypeValeSubscriptionException( f'Beartype "{self._operator_symbol}" validator second operand ' f'{represent_object(validator_operand_2)} not beartype ' f'validator (i.e., "beartype.vale.Is*[...]" object).' ) # Else, both of these operands are beartype validators. beartype-0.18.5/beartype/vale/_core/_valecoreunary.py000066400000000000000000000173771461113517100226600ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Core unary beartype validators** (i.e., :class:`BeartypeValidator` subclasses implementing unary operations on a single lower-level beartype validator). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from abc import ( ABCMeta, abstractmethod, ) from beartype.roar import BeartypeValeSubscriptionException from beartype.vale._core._valecore import BeartypeValidator from beartype.vale._util._valeutiltext import format_diagnosis_line from beartype._data.code.datacodeindent import CODE_INDENT_1 from beartype._util.text.utiltextrepr import represent_object # ....................{ SUPERCLASSES }.................... class BeartypeValidatorUnaryABC(BeartypeValidator, metaclass=ABCMeta): ''' Abstract base class of all **beartype binary validator** (i.e., validator modifying the boolean truthiness returned by the validation performed by a single lower-level beartype validator) subclasses. Attributes ---------- _validator_operand : BeartypeValidator Lower-level validator operated upon by this higher-level validator. ''' # ..................{ CLASS VARIABLES }.................. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Subclasses declaring uniquely subclass-specific instance # variables *MUST* additionally slot those variables. Subclasses violating # this constraint will be usable but unslotted, which defeats our purposes. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Slot all instance variables defined on this object to minimize the time # complexity of both reading and writing variables across frequently called # cache dunder methods. Slotting has been shown to reduce read and write # costs by approximately ~10%, which is non-trivial. __slots__ = ( '_validator_operand', ) # ..................{ INITIALIZERS }.................. def __init__( self, validator_operand: BeartypeValidator, **kwargs ) -> None: ''' Initialize this validator from the passed metadata. Parameters ---------- validator_operand : BeartypeValidator Lower-level validator operated upon by this higher-level validator. Raises ------ BeartypeValeSubscriptionException If this operand is *not* itself a beartype validator. ''' # Callable accepting no arguments returning a machine-readable # representation of this binary validator. get_repr = lambda: ( f'{self._operator_symbol}{repr(validator_operand)}') # Initialize our superclass with all remaining parameters. super().__init__( is_valid_code_locals=validator_operand._is_valid_code_locals, get_repr=get_repr, **kwargs ) # Classify all remaining passed parameters. self._validator_operand = validator_operand # ..................{ GETTERS }.................. #FIXME: Unit test us up, please. def get_diagnosis( self, *, # Mandatory keyword-only parameters. obj: object, indent_level_outer: str, indent_level_inner: str, **kwargs ) -> str: # Line diagnosing this object against this negated parent validator. line_outer_prefix = format_diagnosis_line( validator_repr='(', indent_level_outer=indent_level_outer, indent_level_inner=indent_level_inner, is_obj_valid=self.is_valid(obj), ) # Line diagnosing this object against this non-negated child validator # with an increased indentation level for readability. line_inner_operand = self._validator_operand.get_diagnosis( obj=obj, indent_level_outer=indent_level_outer, indent_level_inner=indent_level_inner + CODE_INDENT_1, **kwargs ) # Line providing the suffixing ")" delimiter for readability. line_outer_suffix = format_diagnosis_line( validator_repr=')', indent_level_outer=indent_level_outer, indent_level_inner=indent_level_inner, ) # Return these lines concatenated. return ( f'{self._operator_symbol}{line_outer_prefix}\n' f'{line_inner_operand}\n' f'{line_outer_suffix}' ) # ..................{ ABSTRACT }.................. # Abstract methods required to be concretely implemented by subclasses. @property @abstractmethod def _operator_symbol(self) -> str: ''' Human-readable string embodying the operation performed by this unary validator - typically the single-character mathematical sign symbolizing this operation. ''' pass # ....................{ SUBCLASSES }.................... class BeartypeValidatorNegation(BeartypeValidatorUnaryABC): ''' **Negation beartype validator** (i.e., validator negating the boolean truthiness returned by the validation performed by a lower-level beartype validator, typically instantiated and returned by the :meth:`BeartypeValidator.__invert__` dunder method of that validator). ''' # ..................{ INITIALIZERS }.................. def __init__(self, validator_operand: BeartypeValidator) -> None: ''' Initialize this higher-level validator from the passed validator. Parameters ---------- validator_operand : BeartypeValidator Validator operated upon by this higher-level validator. Raises ------ BeartypeValeSubscriptionException If this operand is *not* a beartype validator. ''' # Validate the passed operand as sane. _validate_operand(self, validator_operand) # Initialize our superclass with all remaining parameters. super().__init__( validator_operand=validator_operand, is_valid=lambda obj: not validator_operand.is_valid(obj), is_valid_code=f'(not {validator_operand._is_valid_code})', ) # ..................{ PROPERTIES }.................. @property def _operator_symbol(self) -> str: return '~' # ....................{ PRIVATE ~ validators }.................... def _validate_operand( self: BeartypeValidatorUnaryABC, validator_operand: BeartypeValidator, ) -> None: ''' Validate the passed validator operand as sane. Parameters ---------- self : BeartypeValidatorUnaryABC Beartype unary validator operating upon this operand. validator_operand : BeartypeValidator Validator operated upon by this higher-level validator. Raises ------ BeartypeValeSubscriptionException If this operand is *not* a beartype validator. ''' #FIXME: Unit test us up, please. # If this operand is *NOT* a beartype validator, raise an exception. if not isinstance(validator_operand, BeartypeValidator): raise BeartypeValeSubscriptionException( f'Beartype "{self._operator_symbol}" validator operand ' f'{represent_object(validator_operand)} not beartype ' f'validator (i.e., "beartype.vale.Is*[...]" object).' ) # Else, this operand is a beartype validator. beartype-0.18.5/beartype/vale/_is/000077500000000000000000000000001461113517100167345ustar00rootroot00000000000000beartype-0.18.5/beartype/vale/_is/__init__.py000066400000000000000000000000001461113517100210330ustar00rootroot00000000000000beartype-0.18.5/beartype/vale/_is/_valeis.py000066400000000000000000000647661461113517100207530ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **functional validation classes** (i.e., :mod:`beartype`-specific classes enabling callers to define PEP-compliant validators from arbitrary caller-defined callables *not* efficiently generating stack-free code). This private submodule defines the core low-level class hierarchy driving the entire :mod:`beartype` validation ecosystem. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import ( BeartypeValeLambdaWarning, BeartypeValeValidationException, ) from beartype.typing import Protocol from beartype.vale._is._valeisabc import _BeartypeValidatorFactoryABC from beartype.vale._core._valecore import BeartypeValidator from beartype.vale._util._valeutilfunc import die_unless_validator_tester from beartype.vale._util._valeutiltyping import BeartypeValidatorTester from beartype._data.hint.datahinttyping import LexicalScope from beartype._util.func.utilfuncscope import add_func_scope_attr from beartype._util.text.utiltextrepr import ( represent_func, represent_object, ) # ....................{ PRIVATE ~ protocols }.................... class _SupportsBool(Protocol): ''' Fast caching protocol matching any object whose class defines the :meth:`__bool__` dunder method. ''' def __bool__(self) -> bool: ... class _SupportsLen(Protocol): ''' Fast caching protocol matching any object whose class defines the :meth:`__len__` dunder method. ''' def __len__(self) -> bool: ... _BoolLike = (_SupportsBool, _SupportsLen) ''' :func:`isinstance`-able tuple of fast caching protocols matching any **bool-like** (i.e., object whose class defines at least one of the :meth:`__bool__` and/or :meth:`__len__` dunder methods). ''' # ....................{ PRIVATE ~ subclasses }.................... class _IsFactory(_BeartypeValidatorFactoryABC): ''' **Beartype callable validator factory** (i.e., class that, when subscripted (indexed) by an arbitrary callable returning :data:`True` when the object passed to that callable satisfies a caller-defined constraint, creates a new :class:`.BeartypeValidator` object encapsulating that callable suitable for subscripting (indexing) :obj:`typing.Annotated` type hints, enforcing that constraint on :mod:`beartype`-decorated callable parameters and returns annotated by those hints). This class validates that callable parameters and returns satisfy the arbitrary **callable validator** (i.e., callable whose signature satisfies ``collections.abc.Callable[[typing.Any], bool]``) subscripting (indexing) this class. Callable validators are caller-defined and may thus validate the internal integrity, consistency, and structure of arbitrary objects ranging from simple builtin scalars like integers and strings to complex data structures defined by third-party packages like NumPy arrays and Pandas DataFrames. This class creates one new :class:`.BeartypeValidator` object for each callable validator subscripting (indexing) this class. These objects: * Are **PEP-compliant** and thus guaranteed to *never* violate existing or future standards. * Are **Safely ignorable** by *all* static and runtime type checkers other than :mod:`beartype` itself. * **Less efficient** than :class:`BeartypeValidator` objects created by subscripting every other :mod:`beartype.vale` class. Specifically: * Every :class:`.BeartypeValidator` object created by subscripting this class necessarily calls a callable validator and thus incurs at least one additional call stack frame per :mod:`beartype`-decorated callable call. * Every :class:`.BeartypeValidator` object created by subscripting every other :mod:`beartype.vale` class directly calls *no* callable and thus incurs additional call stack frames only when the active Python interpreter internally calls dunder methods (e.g., ``__eq__()``) to satisfy their validation constraint. Usage ----- Any :mod:`beartype`-decorated callable parameter or return annotated by a :obj:`typing.Annotated` type hint subscripted (indexed) by this class subscripted (indexed) by a callable validator (e.g., ``typing.Annotated[{cls}, beartype.vale.Is[lambda obj: {expr}]]`` for any class ``{cls}`` and Python expression ``{expr}`` evaluating to a boolean) validates that parameter or return value to be an instance of that class satisfying that callable validator. Specifically, callers are expected to (in order): #. Annotate a callable parameter or return to be validated with a :pep:`593`-compliant :obj:`typing.Annotated` type hint. #. Subscript that hint with (in order): #. The type expected by that parameter or return. #. One or more subscriptions (indexations) of this class, each itself subscripted (indexed) by a **callable validator** (i.e., callable accepting a single arbitrary object and returning either :data:`True` if that object satisfies an arbitrary constraint *or* :data:`False` otherwise). If that hint is subscripted by: * Only one subscription of this class, that parameter or return satisfies that hint when both: * That parameter or return is an instance of the expected type. * That validator returns :data:`True` when passed that parameter or return. * Two or more subscriptions of this class, that parameter or return satisfies that hint when both: * That parameter or return is an instance of the expected type. * *All* callable validators subscripting *all* subscriptions of this class return :data:`True` when passed that parameter or return. Formally, the signature of each callable validator *must* resemble: .. code-block:: python def is_object_valid(obj) -> bool: return bool(obj) Equivalently, each callable validator *must* satisfy the type hint ``collections.abc.Callable[[typing.Any,], bool]``. If not the case, an exception is raised. Note that: * If that parameter or return is *not* an instance of the expected type, **no callable validator is called.** Equivalently, each callable validator is called *only* when that parameter or return is already an instance of the expected type. Callable validators need *not* revalidate that type (e.g., by passing that parameter or return and type to the :func:`isinstance` builtin). * The name of each callable validator is irrelevant. For convenience, most callable validators are defined as nameless lambda functions. For example, the following type hint only accepts non-empty strings: .. code-block:: python Annotated[str, Is[lambda text: bool(text)]] :class:`.BeartypeValidator` objects also support an expressive domain-specific language (DSL) enabling callers to trivially synthesize new objects from existing objects with standard Pythonic math operators: * **Negation** (i.e., ``not``). Negating an :class:`.BeartypeValidator` object with the ``~`` operator synthesizes a new :class:`.BeartypeValidator` object whose validator returns :data:`True` only when the validator of the original object returns :data:`False`. For example, the following type hint only accepts strings containing *no* periods: .. code-block:: python Annotated[str, ~Is[lambda text: '.' in text]] * **Conjunction** (i.e., ``and``). Conjunctively combining two or more :class:`.BeartypeValidator` objects with the ``&`` operator synthesizes a new :class:`.BeartypeValidator` object whose validator returns :data:`True` only when all data validators of the original objects return :data:`True`. For example, the following type hint only accepts non-empty strings containing *no* periods: .. code-block:: python Annotated[str, ( Is[lambda text: bool(text)] & ~Is[lambda text: '.' in text] )] * **Disjunction** (i.e., ``or``). Disjunctively combining two or more :class:`.BeartypeValidator` objects with the ``|`` operator synthesizes a new :class:`.BeartypeValidator` object whose validator returns :data:`True` only when at least one validator of the original objects returns :data:`True`. For example, the following type hint accepts both empty strings *and* non-empty strings containing at least one period: .. code-block:: python Annotated[str, ( ~Is[lambda text: bool(text)] | Is[lambda text: '.' in text] )] See also the **Examples** subsection below. Caveats ------- **This class is currently only supported by the** :func:`beartype.beartype` **decorator.** All other static and runtime type checkers silently ignore subscriptions of this class subscripting :obj:`typing.Annotated` type hints. **This class incurs a minor time performance penalty at call time.** Specifically, each type hint of a :mod:`beartype`-decorated callable subscripted by a subscription of this class adds one additional stack frame to each call of that callable. While negligible (in the average case), this cost can become non-negligible when compounded across multiple type hints annotating a frequently called :mod:`beartype`-decorated callable -- especially when those type hints are subscripted by multiple subscriptions of this class at different nesting levels. **This class prohibits instantiation.** This class is *only* intended to be subscripted. Attempting to instantiate this class into an object will raise an :exc:`.BeartypeValeSubscriptionException` exception. Examples -------- .. code-block:: python # Import the requisite machinery. >>> from beartype import beartype >>> from beartype.vale import Is >>> from typing import Annotated # Validator matching only strings with lengths ranging [4, 40]. >>> IsRangy = Is[lambda text: 4 <= len(text) <= 40] # Validator matching only unquoted strings. >>> IsUnquoted = Is[lambda text: ... text.count('"') < 2 and text.count("'") < 2] # Type hint matching only unquoted strings. >>> UnquotedString = Annotated[str, IsUnquoted] # Type hint matching only quoted strings. >>> QuotedString = Annotated[str, ~IsUnquoted] # Type hint matching only unquoted strings with lengths ranging [4, 40]. >>> UnquotedRangyString = Annotated[str, IsUnquoted & IsRangy] # Annotate callables by those type hints. >>> @beartype ... def doublequote_text(text: UnquotedString) -> QuotedString: ... """ ... Double-quote the passed unquoted string. ... """ ... return f'"{text}"' # The best things in life are one-liners. >>> @beartype ... def singlequote_prefix(text: UnquotedRangyString) -> QuotedString: ... """ ... Single-quote the prefix spanning characters ``[0, 3]`` of the ... passed unquoted string with length ranging ``[4, 40]``. ... """ ... return f"'{text[:3]}'" # "Guaranteed to work," says @beartype. # Call those callables with parameters satisfying those validators. >>> doublequote_text("You know anything about nuclear fusion?") "You know anything about nuclear fusion?" >>> singlequote_prefix("Not now, I'm too tired. Maybe later.") 'Not' # Call those callables with parameters not satisfying those validators. >>> doublequote_text('''"Everybody relax, I'm here."''') beartype.roar._roarexc.BeartypeCallHintParamViolation: @beartyped doublequote_text() parameter text='"Everybody relax, I\'m here."' violates type hint typing.Annotated[str, Is[lambda text: text.count('"') < 2 and text.count("'") < 2]], as value '"Everybody relax, I\'m here."' violates validator Is[lambda text: text.count('"') < 2 and text.count("'") < 2]. ''' # ..................{ DUNDERS }.................. def __getitem__( # type: ignore[override] self, is_valid: BeartypeValidatorTester) -> BeartypeValidator: ''' Create and return a new beartype validator from the passed **validator callable** (i.e., caller-defined callable accepting a single arbitrary object and returning either :data:`True` if that object satisfies an arbitrary constraint *or* :data:`Falsee` otherwise), suitable for subscripting :pep:`593`-compliant :obj:`typing.Annotated` type hints. This method is intentionally *not* memoized, as this method is usually subscripted only by subscription-specific lambda functions uniquely defined for each subscription of this class. Parameters ---------- is_valid : Callable[[Any,], bool] Validator callable to validate parameters and returns against. Returns ------- BeartypeValidator New object encapsulating this validator callable. Raises ------ BeartypeValeSubscriptionException If either: * This class was subscripted by two or more arguments. * This class was subscripted by one argument that either: * Is *not* callable. * Is a C-based rather than pure-Python callable. * Is a pure-Python callable accepting two or more arguments. See Also -------- :class:`._IsAttrFactory` Usage instructions. ''' # ..................{ VALIDATE }.................. # If this class was subscripted by either no arguments *OR* two or more # arguments, raise an exception. self._die_unless_getitem_args_1(is_valid) # Else, this class was subscripted by exactly one argument. # If that callable is *NOT* a validator tester, raise an exception. die_unless_validator_tester(is_valid) # Else, that callable is a validator tester. # Lambda function dynamically generating the machine-readable # representation of this validator, deferred due to the computational # expense of accurately retrieving the source code for this validator # (especially when this validator is itself a lambda function). get_repr = lambda: ( f'{self._basename}[' f'{represent_func(func=is_valid, warning_cls=BeartypeValeLambdaWarning)}' f']' ) # ..................{ CLOSURE }.................. #FIXME: Unit test edge cases extensively, please. def _is_valid_bool(obj: object) -> bool: ''' :data:`True` only if the passed object satisfies the caller-defined validation callable subscripting this :attr:`beartype.vale.Is` validator factory. This closure wraps that possibly unsafe callable with an implicit type cast, guaranteeing that either: * If that callable returns a boolean, this closure returns that boolean as is. * If that callable returns a non-boolean object, either: * If that non-boolean is implicitly convertible into a boolean (i.e., if passing that non-boolean to the :class:`bool` type succeeds *without* raising an exception), this closure coerces that non-boolean into a boolean and returns that boolean. * Else, this closure raises a human-readable exception. This closure is principally intended to massage non-standard validation callables defined by popular third-party packages like NumPy, which commonly return non-boolean objects that are implicitly convertible into boolean objects: e.g., .. code-block:: >>> import numpy as np >>> matrix = np.array([[2, 1], [1, 2]]) >>> is_all = np.all(matrix > 0)) >>> type(is_all) >>> is_all True # <-- y u lie, numpy >>> bool(is_all) True Caveats ---------- **This closure is comparatively slower than the passed callable.** This closure should *never* be called directly from code snippets embedded in wrapper functions dynamically generated by the :func:`beartype.beartype` decorator. This closure should *only* be called indirectly by exception-handling functionality performed by those wrapper functions in the event of a type-checking violation, at which time efficiency is no longer a driving force. This implies that wrapper functions dynamically generated by the :func:`beartype.beartype` decorator *could* implicitly coerce non-boolean objects returned by the passed callable into the ;data;`True` singleton. Although non-ideal, debugging such concerns is squarely the user's concern; attempting to safeguard users from semantic issues like this would destroy runtime performance for *no* tangible gain in the general case. The best :mod:`beartype` can (and should) do is defer validation until a type-checking violation. Parameters ---------- obj : object Object to be validated by that validation callable. Returns ------- bool :data:`True` only if that object satisfies that validation callable. Raises ------ BeartypeValeValidationException If that validation callable returns a **non-bool-like**, where "non-bool-like" is any object that both: * Is *not* a **boolean** (i.e., :class:`bool` instance). * Is *not* **implicitly convertible** into a boolean (i.e., is an object whose class defines neither the :meth:`__bool__` nor :meth:`__len__` dunder methods). ''' # Object returned by validating this object against that callable. is_obj_valid = is_valid(obj) # If that object is a boolean, return that object as is. if isinstance(is_obj_valid, bool): return is_obj_valid # Else, that object is *NOT* a boolean. # "True" *ONLY* if that object is a bool-like (i.e., object whose # class defines the __bool__() and/or __len__() dunder methods). # # Note that we intentionally avoid the Easier to Ask for Permission # than Forgiveness (EAFP) approach typically favoured by the Python # community for coercing types. Namely, we avoid doing this: # # Attempt to coerce this boolean into a non-boolean. # try: # is_obj_valid_bool = bool(is_obj_valid) # except Exception as exception: # raise SomeBeartypeException(...) from exception # # Why? Because the bool() constructor is overly permissive to the # point of being *FRANKLY BROKEN.* Why? Because that constructor # *NEVER* raises an exception (unless the class of that object # defines a __bool__() dunder method raising an exception). Why? # Because that constructor implicitly coerces *ALL* objects whose # classes define *NO* __bool__() dunder method to "True" except for # the following, which the bool() constructor explicitly detects # and hard-codes to be coerced to "False": # * The "None" singleton. # * The "False" singleton. # * Numeric 0 across all numeric types, including: # * Integer 0. # * Floating-point 0.0. # * Empty containers across all container types, including: # * The empty tuple singleton (i.e., "()"). # * The empty string singleton (i.e., "''"). # * Any empty list (e.g., "[]"). # # The proof is in the gelatinous spaghetti code: # >>> class OhMyGods(object): pass # >>> bool(OhMyGods()) # True # <-- WHAT THE HECK IS THIS, GUIDO. SRSLY, BRO. SRSLY. # # This is, of course, unbelievable. This is, of course, all true. # What is this, Guido? Visual Basic in my Python? *facepalm* # # Note also that there are several means of testing for booliness. # The obvious approach of calling getattr() is also the slowest, # because getattr() internally performs the EAFP approach and # exception handling in Python is known to be an obvious bottleneck. # Ergo, we intentionally avoid doing this: # is_obj_valid_bool_method = getattr(is_obj_valid, '__bool__', None) # # Ideally, we would instead defer to a beartype-specific fast # caching protocol that also internally performs a similar getattr() # call wrapped within caching logic that amortizes the cost of that # call across all isinstance() calls passed an object of that same # type. Since there exists *NO* standard "SupportsBool" protocol, # we would then trivially define our own like so: # from beartype.typing import Protocol # class SupportsBool(Protocol): # def __bool__(self) -> bool: ... # # Surprisingly, that fails. Why? Because the bool() constructor # internally coerces objects into booleans like so: # * If the passed object defines the __bool__() dunder method, that # constructor defers to that method. # * Else if the passed object defines the __len__() dunder method, # that constructor defers to that method. # * Else if the passed object is one of several hard-coded objects # evaluating to "False", that constructor returns "False". # * Else, that constructor returns "True". # # To handle the first two cases, we instead: # * Define both our own "SupportsBool" and "SupportsLen" protocols. # * Decide whether that object is bool-like by deferring to those # protocols. is_obj_valid_boollike = isinstance(is_obj_valid, _BoolLike) # pyright: ignore # If that object is *NOT* bool-like, raise an exception. if not is_obj_valid_boollike: #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Synchronize with the exception raised below, please. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! raise BeartypeValeValidationException( f'Validator {get_repr()} ' f'return value {repr(is_obj_valid)} not bool-like ' f'(i.e., instance of neither "bool" nor ' f'class defining __bool__() or __len__() dunder methods) ' f'for subject object:\n{represent_object(obj)}' ) # Else, that object is bool-like. # Boolean coerced from this non-boolean via the __bool__() or # __len__() dunder methods declared by the type of this non-boolean, # initialized to "False" for safety. is_obj_valid_bool = False # Attempt to perform this coercion. try: is_obj_valid_bool = bool(is_obj_valid) # If whichever of the __bool__() or __len__() dunder methods is # called by the above bool() constructor raises an exception, wrap # that exception in a higher-level @beartype exception. # # Note that this is *NOT* simply an uncommon edge case. In # particular, the Pandas "DataFrame" type defines a __bool__() # dunder method that unconditionally raises an exception. *facepalm* except Exception as exception: #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Synchronize with the exception raised above, please. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! raise BeartypeValeValidationException( f'Validator {get_repr()} ' f'return value {repr(is_obj_valid)} erroneously bool-like ' f'(i.e., instance of class defining __bool__() or __len__() ' f'dunder methods raising unexpected exception) ' f'for subject object:\n{represent_object(obj)}' ) from exception # Return this boolean. return is_obj_valid_bool # ..................{ VALIDATOR }.................. # Dictionary mapping from the name to value of each local attribute # referenced in the "is_valid_code" snippet defined below. is_valid_code_locals: LexicalScope = {} # Name of a new parameter added to the signature of each # @beartype-decorated wrapper function whose value is this validator, # enabling this validator to be called directly in the body of those # functions *WITHOUT* imposing additional stack frames. is_valid_attr_name = add_func_scope_attr( attr=_is_valid_bool, func_scope=is_valid_code_locals) # One one-liner to rule them all and in "pdb" bind them. return BeartypeValidator( is_valid=_is_valid_bool, # Python code snippet calling this validator (via this new # parameter), passed an object to be interpolated into this snippet # by downstream logic. is_valid_code=f'{is_valid_attr_name}({{obj}})', is_valid_code_locals=is_valid_code_locals, get_repr=get_repr, ) beartype-0.18.5/beartype/vale/_is/_valeisabc.py000066400000000000000000000146771461113517100214150ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype validation superclasses** (i.e., :mod:`beartype`-specific abstract base classes (ABCs) from all concrete beartype validation subclasses derive). This private submodule defines the core low-level class hierarchy driving the entire :mod:`beartype` data validation ecosystem. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from abc import ABCMeta, abstractmethod from beartype.roar import BeartypeValeSubscriptionException from beartype.typing import Any from beartype.vale._core._valecore import BeartypeValidator from beartype._util.text.utiltextrepr import represent_object # ....................{ METACLASSES }.................... class _BeartypeValidatorFactoryABCMeta(ABCMeta): ''' Metaclass all **beartype validator factory subclasses** (i.e., :class:`_BeartypeValidatorFactoryABC` subclasses). ''' # ..................{ INITIALIZERS }.................. def __init__(cls, classname, superclasses, attr_name_to_value) -> None: super().__init__(classname, superclasses, attr_name_to_value) # Sanitize the fully-qualified name of the module declaring this class # from the private name of the module implementing this classes to the # public name of the module exporting this class, improving end user # clarity and usability. cls.__module__ = 'beartype.vale' # ....................{ SUPERCLASSES }.................... #FIXME: Pyright appears to be extremely confused. It thinks that the #"_BeartypeValidatorFactoryABCMeta" metaclass is a "generic" (i.e., subclasses #"typing.Generic"), when in fact that metaclass merely subclasses the standard #"abc.ABCMeta" metaclass. Consider submitting an upstream pyright issue, please. class _BeartypeValidatorFactoryABC( object, metaclass=_BeartypeValidatorFactoryABCMeta): # pyright: ignore[reportGeneralTypeIssues] ''' Abstract base class of all **beartype validator factory subclasses** (i.e., subclasses that, when subscripted (indexed) by subclass-specific objects, create new :class:`BeartypeValidator` objects encapsulating those objects, themselves suitable for subscripting (indexing) :attr:`typing.Annotated` type hints, themselves enforcing subclass-specific validation constraints and contracts on :mod:`beartype`-decorated callable parameters and returns annotated by those hints). Attributes ---------- _basename : str Machine-readable basename of the public factory singleton instantiating this private factory subclass (e.g., ``"IsAttr"``). _getitem_exception_prefix : str Human-readable substring prefixing exceptions raised by the subclass implementation of the abstract :meth:__getitem__` dunder method. ''' # ..................{ INITIALIZERS }.................. def __init__(self, basename: str) -> None: ''' Initialize this subclass instance. Parameters ---------- basename : str Machine-readable basename of the public factory singleton instantiating this private factory subclass (e.g., ``"IsAttr"``). ''' assert isinstance(basename, str), f'{repr(basename)} not string.' # Classify all passed parameters. self._basename = basename # Initialize all remaining instance variables. self._getitem_exception_prefix = ( f'Beartype validator factory "{self._basename}" ' f'subscripted by ' ) # ..................{ ABSTRACT ~ dunder }.................. @abstractmethod def __getitem__(self, *args, **kwargs) -> BeartypeValidator: ''' Create and return a new beartype validator validating the subclass constraint parametrized by the passed arguments subscripting this beartype validator factory. Like standard type hints (e.g., :attr:`typing.Union`), instances of concrete subclasses of this abstract base class (ABC) are *only* intended to be subscripted (indexed). Concrete subclasses are required to implement this abstract method. Concrete subclasses are strongly recommended (but *not* required) to memoize their implementations by the :func:`beartype._util.cache.utilcachecall.callable_cached` decorator. Returns ---------- BeartypeValidator Beartype validator encapsulating this validation. ''' pass # ..................{ PRIVATE ~ validator }.................. #FIXME: Unit test us up, please. def _die_unless_getitem_args_1(self, args: Any) -> None: ''' Raise an exception unless this beartype validator factory was subscripted (indexed) by exactly one argument. This validator is intended to be called by concrete subclass implementations of the :meth:`__getitem__` dunder method to validate the arguments subscripting this beartype validator factory. Parameters ---------- args : Any Variadic positional arguments to be inspected. Raises ---------- BeartypeValeSubscriptionException If the caller dunder method was passed either: * No arguments. * Two or more arguments. ''' # If this object was subscripted by either no arguments or two or more # arguments, raise an exception. Specifically... if isinstance(args, tuple): # If this object was subscripted by two or more arguments, raise a # human-readable exception. if args: raise BeartypeValeSubscriptionException( f'{self._getitem_exception_prefix}two or more arguments ' f'{represent_object(args)}.' ) # Else, this object was subscripted by *NO* arguments. In this case, # raise a human-readable exception. else: raise BeartypeValeSubscriptionException( f'{self._getitem_exception_prefix}empty tuple.') # Else, this object was subscripted by exactly one argument. beartype-0.18.5/beartype/vale/_is/_valeisobj.py000066400000000000000000000425731461113517100214360ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **declarative object validation classes** (i.e., :mod:`beartype`-specific classes enabling callers to define PEP-compliant validators from arbitrary caller-defined objects tested via explicitly supported object introspectors efficiently generating stack-free code). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... # All "FIXME:" comments for this submodule reside in this package's "__init__" # submodule to improve maintainability and readability here. # ....................{ IMPORTS }.................... from beartype.roar import BeartypeValeSubscriptionException from beartype.typing import Any, Tuple from beartype.vale._is._valeisabc import _BeartypeValidatorFactoryABC from beartype.vale._util._valeutilsnip import ( VALE_CODE_CHECK_ISATTR_TEST_format, VALE_CODE_CHECK_ISATTR_VALUE_EXPR_format, VALE_CODE_INDENT_1, ) from beartype.vale._core._valecore import BeartypeValidator from beartype._data.hint.datahinttyping import LexicalScope from beartype._util.cache.utilcachecall import callable_cached from beartype._util.kind.map.utilmapset import update_mapping from beartype._util.func.utilfuncscope import add_func_scope_attr from beartype._util.text.utiltextrepr import represent_object from beartype._util.utilobject import SENTINEL # ....................{ SUBCLASSES ~ attr }.................... class _IsAttrFactory(_BeartypeValidatorFactoryABC): ''' **Beartype object attribute validator factory** (i.e., object creating and returning a new beartype validator when subscripted (indexed) by both the name of any object attribute *and* any **attribute validator** (i.e., other beartype validator created by subscripting any :mod:`beartype.vale` class), validating that :mod:`beartype`-decorated callable parameters and returns annotated by :attr:`typing.Annotated` type hints subscripted by the former validator define an attribute with that name satisfying that attribute validator). This class efficiently validates that callable parameters and returns define arbitrary object attributes satisfying arbitrary validators subscripting this factory. Any :mod:`beartype`-decorated callable parameter or return annotated by a :attr:`typing.Annotated` type hint subscripted by this factory subscripted by any object attribute name and validator (e.g., ``typing.Annotated[{cls}, beartype.vale.IsAttr[{attr_name}, {attr_validator}]]`` for any class ``{cls}``, object attribute name ``{attr_name}`, and object attribute validator ``{attr_validator}``) validates that parameter or return value to be an instance of that class defining an attribute with that name satisfying that attribute validator. **This factory incurs no time performance penalties at call time.** Whereas the general-purpose :class:`beartype.vale.Is` factory necessarily calls the caller-defined callable subscripting that factory at call time and thus incurs a minor time performance penalty, this factory efficiently reduces to one-line tests in :mod:`beartype`-generated wrapper functions *without* calling any callables and thus incurs *no* time performance penalties. Examples ---------- .. code-block:: python # Import the requisite machinery. >>> from beartype import beartype >>> from beartype.vale import IsAttr, IsEqual >>> from typing import Annotated >>> import numpy as np # Type hint matching only two-dimensional NumPy arrays of 64-bit floats, # generating code resembling: # (isinstance(array, np.ndarray) and # array.ndim == 2 and # array.dtype == np.dtype(np.float64)) >>> Numpy2dFloat64Array = Annotated[ ... np.ndarray, ... IsAttr['ndim', IsEqual[2]], ... IsAttr['dtype', IsEqual[np.dtype(np.float64)]], ... ] # Type hint matching only one-dimensional NumPy arrays of 64-bit floats, # generating code resembling: # (isinstance(array, np.ndarray) and # array.ndim == 2 and # array.dtype.type == np.float64) >>> Numpy1dFloat64Array = Annotated[ ... np.ndarray, ... IsAttr['ndim', IsEqual[2]], ... # Nested attribute validators test equality against a "."-delimited ... # attribute lookup (e.g., "dtype.type"), as expected. ... IsAttr['dtype', IsAttr['type', IsEqual[np.float64]]], ... ] # NumPy arrays of well-known real number series. >>> FAREY_2D_FLOAT64_ARRAY = np.array( ... [[0/1, 1/8,], [1/7, 1/6,], [1/5, 1/4], [2/7, 1/3], [3/8, 2/5]]) >>> FAREY_1D_FLOAT64_ARRAY = np.array( ... [3/7, 1/2, 4/7, 3/5, 5/8, 2/3, 5/7, 3/4, 4/5, 5/6, 6/7, 7/8]) # Annotate callables by those type hints. >>> @beartype ... def sqrt_sum_2d( ... array: Numpy2dFloat64Array) -> Numpy1dFloat64Array: ... """ ... One-dimensional NumPy array of 64-bit floats produced by first ... summing the passed two-dimensional NumPy array of 64-bit floats ... along its second dimension and then square-rooting those sums. ... """ ... return np.sqrt(array.sum(axis=1)) # Call those callables with parameters satisfying those hints. >>> sqrt_sum_2d(FAREY_2D_FLOAT64_ARRAY) [0.35355339 0.55634864 0.67082039 0.78679579 0.88034084] # Call those callables with parameters violating those hints. >>> sqrt_sum_2d(FAREY_1D_FLOAT64_ARRAY) beartype.roar.BeartypeCallHintParamViolation: @beartyped sqrt_sum_2d() parameter array="array([0.42857143, 0.5, 0.57142857, 0.6, 0.625, ...])" violates type hint typing.Annotated[numpy.ndarray, IsAttr['ndim', IsEqual[2]], IsAttr['dtype', IsEqual[dtype('float64')]]], as value "array([0.42857143, 0.5, 0.57142857, 0.6, 0.625, ...])" violates validator IsAttr['ndim', IsEqual[2]]. See Also ---------- :class:`beartype.vale.Is` Further commentary. ''' # ..................{ DUNDERS }.................. @callable_cached def __getitem__( # type: ignore[override] self, args: Tuple[str, BeartypeValidator]) -> BeartypeValidator: ''' Create and return a new beartype validator validating object attributes with the passed name satisfying the passed validator, suitable for subscripting :pep:`593`-compliant :attr:`typing.Annotated` type hints. This method is memoized for efficiency. Parameters ---------- args : Tuple[str, BeartypeValidator] 2-tuple ``(attr_name, attr_validator)``, where: * ``attr_name`` is the arbitrary attribute name to validate that parameters and returns define satisfying the passed validator. * ``attr_validator`` is the attribute validator to validate that attributes with the passed name of parameters and returns satisfy. Returns ---------- BeartypeValidator Beartype validator encapsulating this validation. Raises ---------- BeartypeValeSubscriptionException If this factory was subscripted by either: * *No* arguments. * One argument. * Three or more arguments. See Also ---------- :class:`_IsAttrFactory` Usage instructions. ''' # If this class was subscripted by one non-tuple argument, raise an # exception. if not isinstance(args, tuple): raise BeartypeValeSubscriptionException( f'{self._getitem_exception_prefix}non-tuple argument ' f'{represent_object(args)}.' ) # Else, this class was subscripted by either no *OR* two or more # arguments (contained in this tuple). # # If this class was *NOT* subscripted by two arguments... elif len(args) != 2: # If this class was subscripted by one or more arguments, then by # deduction this class was subscripted by three or more arguments. # In this case, raise a human-readable exception. if args: raise BeartypeValeSubscriptionException( f'{self._getitem_exception_prefix}three or more arguments ' f'{represent_object(args)}.' ) # Else, this class was subscripted by *NO* arguments. In this case, # raise a human-readable exception. else: raise BeartypeValeSubscriptionException( f'{self._getitem_exception_prefix}empty tuple.') # Else, this class was subscripted by exactly two arguments. # Localize these arguments to human-readable local variables. attr_name, attr_validator = args # Representer (i.e., callable accepting *NO* arguments returning a # machine-readable representation of this validator), defined *AFTER* # localizing these validator arguments. get_repr = lambda: ( f'{self._basename}[{repr(attr_name)}, {repr(attr_validator)}]') # If this name is *NOT* a string, raise an exception. if not isinstance(attr_name, str): raise BeartypeValeSubscriptionException( f'{get_repr()} first argument ' f'{represent_object(attr_name)} not string.' ) # Else, this name is a string. # # If this name is the empty string, raise an exception. elif not attr_name: raise BeartypeValeSubscriptionException( f'{get_repr()} first argument is empty string.') # Else, this name is a non-empty string. # # Note that this name has *NOT* yet been validated to be valid Python # identifier. While we could do so here by calling our existing # is_identifier() tester, doing so would inefficiently repeat # the split on "." characters performed below. Instead, we iteratively # validate each split substring to be a valid Python identifier below. # Callable inefficiently validating object attributes with this name # against this validator. # is_valid: BeartypeValidatorTester = None # type: ignore[assignment] # Code snippet efficiently validating object attributes with this name # against this validator. is_valid_code = '' # Dictionary mapping from the name to value of each local attribute # referenced in the "is_valid_code" snippet defined below. is_valid_code_locals: LexicalScope = {} # If this attribute name is unqualified (i.e., contains no "." # delimiters), prefer an efficient optimization avoiding iteration. if '.' not in attr_name: # If this name is *NOT* a valid Python identifier, raise an # exception. if not attr_name.isidentifier(): raise BeartypeValeSubscriptionException( f'{get_repr()} first argument {repr(attr_name)} not ' f'syntactically valid Python identifier.' ) # Else, this name is a valid Python identifier. def is_valid(pith: Any) -> bool: f''' ``True`` only if the passed object defines an attribute named "{attr_name}" whose value satisfies the validator {repr(attr_validator)}. ''' # Attribute of this object with this name if this object # defines such an attribute *OR* a sentinel placeholder # otherwise (i.e., if this object defines *NO* such attribute). pith_attr = getattr(pith, attr_name, SENTINEL) # Return true only if... return ( # This object defines an attribute with this name *AND*... pith_attr is not SENTINEL and # This attribute satisfies this validator. attr_validator.is_valid(pith_attr) ) # Names of new parameters added to the signature of wrapper # functions enabling this validator to be tested in those functions # *WITHOUT* additional stack frames whose values are: # * The sentinel placeholder. # # Add these parameters *BEFORE* generating locals. local_name_sentinel = add_func_scope_attr( attr=SENTINEL, func_scope=is_valid_code_locals) # Generate locals safely merging the locals required by both the # code generated below *AND* the externally provided code # validating this attribute. update_mapping( mapping_trg=is_valid_code_locals, mapping_src=attr_validator._is_valid_code_locals, ) #FIXME: Unfortunately, "local_name_attr_value" still isn't a #sufficiently unique name below, because "IsAttr['name', #IsAttr['name', IsEqual[True]]]" is a trivial counter-example where #the current approach breaks down. For true uniquification here, #we're going to need to instead: #* Define a global private counter: # _local_name_obj_attr_value_counter = Counter(0) #* Replace the assignment below with: # local_name_obj_attr_value = ( # f'{{obj}}_isattr_' # f'{next(_local_name_obj_attr_value_counter)}' # ) #Of course, this assumes "Counter" objects are thread-safe. If #they're not, we'll need to further obfuscate all this behind a #[R]Lock of some sort. *sigh* #FIXME: Oh, right. We mixed up "collections.Counter" with #"itertools.count". The former is orthogonal to our interests here; #the latter is of interest but *NOT* thread-safe. The solution is #for us to implement a new "FastWriteCounter" class resembling that #published in this extremely clever (and thus awesome) article: # https://julien.danjou.info/atomic-lock-free-counters-in-python # Name of a local variable in this code whose: # * Name is sufficiently obfuscated as to be hopefully unique to # the code generated by this validator. # * Value is the value of this attribute of the arbitrary object # being validated by this code. local_name_attr_value = f'{{obj}}_isattr_{attr_name}' # Python expression expanding to the value of this attribute, # efficiently optimized under Python >= 3.8 with an assignment # expression to avoid inefficient access of this value. attr_value_expr = VALE_CODE_CHECK_ISATTR_VALUE_EXPR_format( attr_name_expr=repr(attr_name), local_name_attr_value=local_name_attr_value, local_name_sentinel=local_name_sentinel, ) # Python expression validating the value of this attribute, # formatted so as to be safely embeddable in the larger code # expression defined below. attr_value_is_valid_expr = ( attr_validator._is_valid_code.format( # Replace the placeholder substring "{obj}" in this code # with the expression expanding to this attribute's value, # defined as the name of the local variable previously # assigned the value of this attribute by the # "VALE_CODE_CHECK_ISATTR_VALUE_EXPR" code snippet # subsequently embedded in the # "VALE_CODE_CHECK_ISATTR_VALUE_TEST" code snippet. obj=local_name_attr_value, # Replace the placeholder substring "{indent}" in this code # with an indentation increased by one level. indent=VALE_CODE_INDENT_1, )) # Code snippet efficiently validating against this object. is_valid_code = VALE_CODE_CHECK_ISATTR_TEST_format( attr_value_expr=attr_value_expr, attr_value_is_valid_expr=attr_value_is_valid_expr, local_name_sentinel=local_name_sentinel, ) # Else, this attribute name is qualified (i.e., contains one or more # "." delimiters), fallback to a general solution performing iteration. else: #FIXME: Implement us up when we find the time, please. We currently #raise an exception simply because we ran out of time for this. :{ raise BeartypeValeSubscriptionException( f'{get_repr()} first argument ' f'{repr(attr_name)} not unqualified Python identifier ' f'(i.e., contains one or more "." characters).' ) # Create and return this subscription. return BeartypeValidator( is_valid=is_valid, is_valid_code=is_valid_code, is_valid_code_locals=is_valid_code_locals, get_repr=get_repr, ) beartype-0.18.5/beartype/vale/_is/_valeisoper.py000066400000000000000000000274151461113517100216270ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype declarative operator validation classes** (i.e., :mod:`beartype`-specific classes enabling callers to define PEP-compliant validators from arbitrary caller-defined objects tested via explicitly supported operators efficiently generating stack-free code). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ TODO }.................... #FIXME: *Useful optimization.* For "_IsEqualFactory", we can (and should) #directly embed the values of builtins when comparing against builtins (e.g., #integers, strings). Specifically, we should only conditionally perform this #line below: # param_name_obj_value = add_func_scope_attr( # attr=obj, func_scope=is_valid_code_locals) #...when we absolutely must. So when mustn't we? We see two simple approaches #to detecting builtin objects: #* Detect the types of those objects. While obvious, this presents several # subtleties: # * Fake builtin objects, which would naturally need to be excluded. # * Subclasses of builtin objects, which would *ALSO* need to be excluded. # In short, "isinstance(param_name_obj_value, TUPLE_OF_TRUE_BUILTIN_TYPES)" # fails to suffice -- although something more brute-force like # "type(param_name_obj_value) in SET_OF_TRUE_BUILTIN_TYPES" might suffice. #* Detect the first character of their repr() strings as belonging to the set: # BUILTIN_OBJ_REPR_CHARS_FIRST = { # "'", '"', 0, 1, 2, 3, 4, 5, 6, 7, 8, 9} # repr(param_name_obj_value) in BUILTIN_OBJ_REPR_CHARS_FIRST #We like the latter quite a bit more, as it has *NO* obvious edge cases, #requires *NO* hard-coding of types, and appears to scale gracefully. The only #downside is that it assumes third-party repr() strings to be sane, but... if #that *ISN'T* the case, that is a bug in those third-parties. *shrug* #FIXME: Generalize to support arbitrary binary operators by: #* Define a new "_IsOperatorBinaryABC(_BeartypeValidatorFactoryABC, metaclass=ABCMeta)" superclass. #* In that superclass: # * Define a stock __class_getitem__() method whose implementation is # sufficiently generic so as to be applicable to all subclasses. To do so, # this method should access class variables defined by those subclasses. # * Note that there is absolutely no reason or point to define abstract class # methods forcing subclasses to define various metadata, for the unfortunate # reason that abstract class methods do *NOT* actually enforce subclasses # that aren't instantiable anyway to implement those methods. *sigh* #* Refactor "_IsEqualFactory" to: # * Subclass that superclass. # * Define the following class variables, which the superclass # __class_getitem__() method will internally access to implement itself: # from operator import __eq__ # # class _IsEqualFactory(_IsOperatorBinaryABC): # _operator = __eq__ # _operator_code = '==' # #Ridiculously sweet, eh? We know. # ....................{ IMPORTS }.................... from beartype.roar import BeartypeValeSubscriptionException from beartype.typing import Any from beartype.vale._is._valeisabc import _BeartypeValidatorFactoryABC from beartype.vale._util._valeutilsnip import ( VALE_CODE_CHECK_ISEQUAL_TEST_format) from beartype.vale._core._valecore import BeartypeValidator from beartype._data.hint.datahinttyping import LexicalScope from beartype._util.cache.utilcachecall import callable_cached from beartype._util.func.utilfuncscope import add_func_scope_attr # ....................{ SUBCLASSES ~ equal }.................... class _IsEqualFactory(_BeartypeValidatorFactoryABC): ''' **Beartype object equality validator factory** (i.e., object creating and returning a new beartype validator when subscripted (indexed) by any object, validating that :mod:`beartype`-decorated callable parameters and returns annotated by :attr:`typing.Annotated` type hints subscripted by that validator equal that object). This class efficiently validates that callable parameters and returns are equal to the arbitrary object subscripting this factory. Any :mod:`beartype`-decorated callable parameter or return annotated by a :attr:`typing.Annotated` type hint subscripted by this factory subscripted by any object (e.g., ``typing.Annotated[{cls}, beartype.vale.IsEqual[{obj}]]`` for any class ``{cls}`` and object ``{obj}`) validates that parameter or return value to equal that object under the standard ``==`` equality comparison. This factory is a generalization of the :pep:`586`-compliant :attr:`typing.Literal` type hint factory, because this factory does everything that factory does and substantially more. Superficially, :attr:`typing.Literal` type hints also validate that callable parameters and returns are equal to (i.e., ``==``) the literal object subscripting those hints. The similarity ends there, however. :attr:`typing.Literal` is only subscriptable by literal :class:`bool`, :class:`bytes`, :class:`int`, :class:`str`, :class:`Enum`, and ``type(None)`` objects; meanwhile, this factory is subscriptable by *any* object. **This factory incurs no time performance penalties at call time.** Whereas the general-purpose :class:`beartype.vale.Is` factory necessarily calls the caller-defined callable subscripting that factory at call time and thus incurs a minor time performance penalty, this factory efficiently reduces to one-line tests in :mod:`beartype`-generated wrapper functions *without* calling any callables and thus incurs *no* time performance penalties. Caveats ---------- **This class is intentionally subscriptable by only a single object.** Why? Disambiguity. When subscripted by variadic positional (i.e., one or more) objects, this class internally treats those objects as items of a tuple to validate equality against rather than as independent objects to iteratively validate equality against. Since this is non-intuitive, callers should avoid subscripting this class by multiple objects. Although non-intuitive, this is also unavoidable. The ``__class_getitem__()`` dunder method obeys the same semantics as the ``__getitem__()`` dunder method, which is unable to differentiate between being subscripted two or more objects and being subscripted by a tuple of two or more objects. Since being able to validate equality against tuples of two or more objects is essential and since this class being subscripted by two or more objects would trivially reduce to shorthand for the existing ``|`` set operator already supported by this class, this class preserves support for tuples of two or more objects at a cost of non-intuitive results when subscripted by multiple objects. Don't blame us. We didn't vote for :pep:`560`. Examples ---------- .. code-block:: python # Import the requisite machinery. >>> from beartype import beartype >>> from beartype.vale import IsEqual >>> from typing import Annotated # Lists of the first ten items of well-known simple whole number series. >>> WHOLE_NUMBERS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> WHOLE_NUMBERS_EVEN = [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] >>> WHOLE_NUMBERS_ODD = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19] # Type hint matching only lists of integers equal to one of these lists. >>> SimpleWholeNumberSeries = Annotated[ ... list[int], ... IsEqual[WHOLE_NUMBERS] | ... IsEqual[WHOLE_NUMBERS_EVEN] | ... IsEqual[WHOLE_NUMBERS_ODD] ... ] # Annotate callables by those type hints. >>> @beartype ... def guess_next(series: SimpleWholeNumberSeries) -> int: ... """ ... Guess the next whole number in the passed whole number series. ... """ ... if series == WHOLE_NUMBERS: return WHOLE_NUMBERS[-1] + 1 ... else: return series[-1] + 2 # Call those callables with parameters equal to one of those objects. >>> guess_next(list(range(10))) 10 >>> guess_next([number*2 for number in range(10)]) 20 # Call those callables with parameters unequal to one of those objects. >>> guess_next([1, 2, 3, 6, 7, 14, 21, 42,]) beartype.roar.BeartypeCallHintParamViolation: @beartyped guess_next() parameter series=[1, 2, 3, 6, 7, 14, 21, 42] violates type hint typing.Annotated[list[int], IsEqual[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]] | IsEqual[[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]] | IsEqual[[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]]], as value [1, 2, 3, 6, 7, 14, 21, 42] violates validator IsEqual[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]] | IsEqual[[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]] | IsEqual[[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]]. See Also ---------- :class:`beartype.vale.Is` Further commentary. ''' # ..................{ DUNDERS }.................. @callable_cached def __getitem__(self, obj: Any) -> BeartypeValidator: # type: ignore[override] ''' Create and return a new beartype validator validating equality against the passed object, suitable for subscripting :pep:`593`-compliant :attr:`typing.Annotated` type hints. This method is memoized for efficiency. Parameters ---------- obj : Any Arbitrary object to validate equality against. Returns ---------- BeartypeValidator Beartype validator encapsulating this validation. Raises ---------- BeartypeValeSubscriptionException If this factory was subscripted by either: * *No* arguments. * Two or more arguments. See Also ---------- :class:`_IsEqualFactory` Usage instructions. ''' # If... if ( # This factory was subscripted by either no arguments *OR* two or # more arguments *AND*... isinstance(obj, tuple) and # This factory was subscripted by no arguments... not obj # Then raise an exception. ): raise BeartypeValeSubscriptionException( f'{self._getitem_exception_prefix}empty tuple.') # Else, this factory was subscripted by one or more arguments. In any # case, accept this object as is. See the class docstring for details. # print(f'_IsEqualFactory[{repr(obj)}]') # Callable inefficiently validating against this object. is_valid = lambda pith: pith == obj # Dictionary mapping from the name to value of each local attribute # referenced in the "is_valid_code" snippet defined below. is_valid_code_locals: LexicalScope = {} # Name of a new parameter added to the signature of wrapper functions # whose value is this object, enabling this object to be tested in # those functions *WITHOUT* additional stack frames. param_name_obj_value = add_func_scope_attr( attr=obj, func_scope=is_valid_code_locals) # Code snippet efficiently validating against this object. is_valid_code = VALE_CODE_CHECK_ISEQUAL_TEST_format( param_name_obj_value=param_name_obj_value) # Create and return this subscription. return BeartypeValidator( is_valid=is_valid, is_valid_code=is_valid_code, is_valid_code_locals=is_valid_code_locals, get_repr=lambda: f'{self._basename}[{repr(obj)}]', ) beartype-0.18.5/beartype/vale/_is/_valeistype.py000066400000000000000000000447551461113517100216510ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype declarative type validation classes** (i.e., :mod:`beartype`-specific classes enabling callers to define PEP-compliant validators from arbitrary caller-defined classes tested via explicitly supported object introspectors efficiently generating stack-free code). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeValeSubscriptionException from beartype.vale._is._valeisabc import _BeartypeValidatorFactoryABC from beartype.vale._util._valeutilsnip import ( VALE_CODE_CHECK_ISINSTANCE_TEST_format, VALE_CODE_CHECK_ISSUBCLASS_TEST_format, ) from beartype.vale._core._valecore import BeartypeValidator from beartype._data.hint.datahinttyping import ( LexicalScope, TypeOrTupleTypes, ) from beartype._util.cache.utilcachecall import callable_cached from beartype._util.cls.utilclstest import is_type_subclass from beartype._util.cls.pep.utilpep3119 import ( die_unless_type_isinstanceable, die_unless_object_isinstanceable, die_unless_type_issubclassable, die_unless_object_issubclassable, ) from beartype._util.func.utilfuncscope import add_func_scope_attr from beartype._util.utilobject import get_object_name # ....................{ SUBCLASSES ~ instance }.................... class _IsInstanceFactory(_BeartypeValidatorFactoryABC): ''' **Beartype type instance validator factory** (i.e., object creating and returning a new beartype validator when subscripted (indexed) by any class, validating that :mod:`beartype`-decorated callable parameters and returns annotated by :attr:`typing.Annotated` type hints subscripted by that validator are objects whose classes subclass that class). This class efficiently validates that callable parameters and returns are instances of the arbitrary class subscripting (indexing) this factory. Any :mod:`beartype`-decorated callable parameter or return annotated by a :attr:`typing.Annotated` type hint subscripted by this factory subscripted by any class (e.g., ``typing.Annotated[type, beartype.vale.IsInstance[{cls}]]`` for any class ``{cls}``) validates that parameter or return value to be a subclass of that class. This factory generalizes :pep:`484`-compliant **isinstanceable types** (i.e., normal pure-Python and C-based classes that may be passed as the second parameter to the :func:`isinstance` builtin), because this factory does everything those types do and considerably more. Superficially, isinstanceable types also validate that callable parameters and returns are instances of those types. The similarity ends there, however. Isinstanceable types only narrowly apply to callable parameters and returns; meanwhile, this factory produces beartype validators universally applicable to both: * Callable parameters and returns. * **Attributes** of callable parameters and returns via the :class:`beartype.vale.IsAttr` factory. **This factory incurs no time performance penalties at call time.** Whereas the general-purpose :class:`beartype.vale.Is` factory necessarily calls the caller-defined callable subscripting that factory at call time and thus incurs a minor time performance penalty, this factory efficiently reduces to one-line tests in :mod:`beartype`-generated wrapper functions *without* calling any callables and thus incurs *no* time performance penalties. Examples -------- .. code-block:: python # Import the requisite machinery. >>> from beartype import beartype >>> from beartype.vale import IsInstance >>> from math import factorial as loose_factorial >>> from typing import Annotated # Type hint matching any non-boolean integer, generating code like: # (isinstance(number, int) and not isinstance(number, bool))) # Surprisingly, booleans are literally integers in Python (e.g., # ``issubclass(bool, int) is True``). Callable parameters and returns # annotated as accepting only integers thus implicitly accept booleans # as well by default. This type hint explicitly prevents that ambiguity. >>> IntNonbool = Annotated[int, ~IsInstance[bool]] # Annotate callables by those type hints. >>> @beartype ... def strict_factorial(integer: IntNonbool) -> IntNonbool: ... """ ... Factorial of the passed integer, explicitly prohibiting booleans ... masquerading as integers. ... """ ... return loose_factorial(integer) # Call those callables with parameters satisfying those hints. >>> strict_factorial(42) 1405006117752879898543142606244511569936384000000000 # Call those callables with parameters violating those hints. >>> strict_factorial(True) beartype.roar.BeartypeCallHintParamViolation: @beartyped strict_factorial() parameter integer=True violates type hint typing.Annotated[int, ~IsInstance[builtins.bool]], as True violates validator ~IsInstance[builtins.bool]: See Also -------- :class:`beartype.vale.Is` Further commentary. ''' # ..................{ DUNDERS }.................. @callable_cached def __getitem__(self, types: TypeOrTupleTypes) -> BeartypeValidator: # type: ignore[override] ''' Create and return a new beartype validator validating type instancing against at least one of the passed classes, suitable for subscripting :pep:`593`-compliant :attr:`typing.Annotated` type hints. This method is memoized for efficiency. Parameters ---------- types : TypeOrTupleTypes One or more arbitrary classes to validate type instancing against. Returns ------- BeartypeValidator Beartype validator encapsulating this validation. Raises ------ BeartypeValeSubscriptionException If this factory was subscripted by either: * *No* arguments. * One or more arguments that are *not* **isinstanceable types** (i.e., classes passable as the second argument to the :func: `isinstance` builtin). See Also -------- :class:`_IsAttrFactory` Usage instructions. ''' # Machine-readable string representing this type or tuple of types. types_repr = '' # If this factory was subscripted by either no arguments *OR* two or # more arguments... if isinstance(types, tuple): # If this factory was subscripted by *NO* arguments, raise an # exception. if not types: raise BeartypeValeSubscriptionException( f'{self._getitem_exception_prefix}empty tuple.') # Else, this factory was subscripted by two or more arguments. # If any such argument is *NOT* an isinstanceable type, raise an # exception. die_unless_object_isinstanceable( obj=types, exception_cls=BeartypeValeSubscriptionException, exception_prefix=self._getitem_exception_prefix, ) # Else, all such arguments are isinstanceable types. # Append the fully-qualified name of each such type to this string. for cls in types: types_repr += f'{get_object_name(cls)}, ' # Strip the suffixing ", " from this string for readability. types_repr = types_repr[:-2] # Else, this factory was subscripted by one argument. In this case... else: # If this argument is *NOT* an isinstanceable type, raise an # exception. die_unless_type_isinstanceable( cls=types, exception_cls=BeartypeValeSubscriptionException, exception_prefix=self._getitem_exception_prefix, ) # Else, this argument is an isinstanceable type. # Fully-qualified name of this type. types_repr = get_object_name(types) # Callable inefficiently validating against this type. is_valid = lambda pith: isinstance(pith, types) # Dictionary mapping from the name to value of each local attribute # referenced in the "is_valid_code" snippet defined below. is_valid_code_locals: LexicalScope = {} # Name of a new parameter added to the signature of wrapper functions # whose value is this type or tuple of types, enabling this type or # tuple of types to be tested in those functions *WITHOUT* additional # stack frames. param_name_types = add_func_scope_attr( attr=types, func_scope=is_valid_code_locals) # Code snippet efficiently validating against this type. is_valid_code = VALE_CODE_CHECK_ISINSTANCE_TEST_format( param_name_types=param_name_types) # Create and return this subscription. return BeartypeValidator( is_valid=is_valid, is_valid_code=is_valid_code, is_valid_code_locals=is_valid_code_locals, # Intentionally pass this subscription's machine-readable # representation as a string rather than lambda function returning # a string, as this string is safely, immediately, and efficiently # constructable from these arguments' representation. get_repr=f'{self._basename}[{types_repr}]', ) # ....................{ SUBCLASSES ~ subclass }.................... class _IsSubclassFactory(_BeartypeValidatorFactoryABC): ''' **Beartype type inheritance validator factory** (i.e., object creating and returning a new beartype validator when subscripted (indexed) by any class, validating that :mod:`beartype`-decorated callable parameters and returns annotated by :attr:`typing.Annotated` type hints subscripted by that validator subclass that class). This class efficiently validates that callable parameters and returns are subclasses of the arbitrary class subscripting (indexing) this factory. Any :mod:`beartype`-decorated callable parameter or return annotated by a :attr:`typing.Annotated` type hint subscripted by this factory subscripted by any class (e.g., ``typing.Annotated[type, beartype.vale.IsSubclass[{cls}]]`` for any class ``{cls}``) validates that parameter or return value to be a subclass of that class. This factory generalizes the :pep:`484`-compliant :attr:`typing.Type` and : pep:`585`-compliant :class:`type` type hint factories, because this factory does everything those factories do and substantially more. Superficially, : attr:`typing.Type` and :class:`type` type hints also validate that callable parameters and returns are subclasses of the classes subscripting those hints. The similarity ends there, however. Those hints only narrowly apply to callable parameters and returns; meanwhile, this factory produces beartype validators universally applicable to both: * Callable parameters and returns. * **Attributes** of callable parameters and returns via the :class:`beartype.vale.IsAttr` factory. **This factory incurs no time performance penalties at call time.** Whereas the general-purpose :class:`beartype.vale.Is` factory necessarily calls the caller-defined callable subscripting that factory at call time and thus incurs a minor time performance penalty, this factory efficiently reduces to one-line tests in :mod:`beartype`-generated wrapper functions *without* calling any callables and thus incurs *no* time performance penalties. Examples -------- .. code-block:: python # Import the requisite machinery. >>> from beartype import beartype >>> from beartype.vale import IsAttr, IsSubclass >>> from typing import Annotated >>> import numpy as np # Type hint matching only NumPy arrays of floats of arbitrary precision, # generating code resembling: # (isinstance(array, np.ndarray) and # np.issubdtype(array.dtype, np.floating)) >>> NumpyFloatArray = Annotated[ ... np.ndarray, IsAttr['dtype', IsAttr['type', IsSubclass[np.floating]]]] # Type hint matching only NumPy arrays of integers of arbitrary # precision, generating code resembling: # (isinstance(array, np.ndarray) and # np.issubdtype(array.dtype, np.integer)) >>> NumpyIntArray = Annotated[ ... np.ndarray, IsAttr['dtype', IsAttr['type', IsSubclass[np.integer]]]] # NumPy arrays of well-known real number series. >>> E_APPROXIMATIONS = np.array( ... [1+1, 1+1+1/2, 1+1+1/2+1/6, 1+1+1/2+1/6+1/24,]) >>> FACTORIALS = np.array([1, 2, 6, 24, 120, 720, 5040, 40320, 362880,]) # Annotate callables by those type hints. >>> @beartype ... def round_int(array: NumpyFloatArray) -> NumpyIntArray: ... """ ... NumPy array of integers rounded from the passed NumPy array of ... floating-point numbers to the nearest 64-bit integer. ... """ ... return np.around(array).astype(np.int64) # Call those callables with parameters satisfying those hints. >>> round_int(E_APPROXIMATIONS) [2, 3, 3, 3] # Call those callables with parameters violating those hints. >>> round_int(FACTORIALS) beartype.roar.BeartypeCallHintParamViolation: @beartyped round_int() parameter array="array([ 1, 2, 6, 24, 120, 720, 5040, 40320, ...])" violates type hint typing.Annotated[numpy.ndarray, IsAttr['dtype', IsAttr['type', IsSubclass[numpy.floating]]]], as "array([ 1, 2, 6, 24, 120, 720, 5040, 40320, ...])" violates validator IsAttr['dtype', IsAttr['type', IsSubclass[numpy.floating]]] See Also ---------- :class:`beartype.vale.Is` Further commentary. ''' # ..................{ DUNDERS }.................. @callable_cached def __getitem__(self, types: TypeOrTupleTypes) -> BeartypeValidator: # type: ignore[override] ''' Create and return a new beartype validator validating type inheritance against at least one of the passed classes, suitable for subscripting :pep:`593`-compliant :attr:`typing.Annotated` type hints. This method is memoized for efficiency. Parameters ---------- types : TypeOrTupleTypes One or more arbitrary classes to validate type inheritance against. Returns ------- BeartypeValidator Beartype validator encapsulating this validation. Raises ------ BeartypeValeSubscriptionException If this factory was subscripted by either: * *No* arguments. * One or more arguments that are *not* **issubclassable types** (i.e., classes passable as the second argument to the :func: `issubclass` builtin). See Also -------- :class:`_IsAttrFactory` Usage instructions. ''' # Machine-readable string representing this type or tuple of types. types_repr = '' # If this factory was subscripted by either no arguments *OR* two or # more arguments... if isinstance(types, tuple): # If this factory was subscripted by *NO* arguments, raise an # exception. if not types: raise BeartypeValeSubscriptionException( f'{self._getitem_exception_prefix}empty tuple.') # Else, this factory was subscripted by two or more arguments. # If any such argument is *NOT* an issubclassable type, raise an # exception. die_unless_object_issubclassable( obj=types, exception_cls=BeartypeValeSubscriptionException, exception_prefix=self._getitem_exception_prefix, ) # Else, all such arguments are issubclassable types. # Append the fully-qualified name of each such type to this string. for cls in types: types_repr += f'{get_object_name(cls)}, ' # Strip the suffixing ", " from this string for readability. types_repr = types_repr[:-2] # Else, this factory was subscripted by one argument. In this case... else: # If this argument is *NOT* an issubclassable type, raise an # exception. die_unless_type_issubclassable( cls=types, exception_cls=BeartypeValeSubscriptionException, exception_prefix=self._getitem_exception_prefix, ) # Else, this argument is an issubclassable type. # Fully-qualified name of this type. types_repr = get_object_name(types) # Callable inefficiently validating against this type. is_valid = lambda pith: is_type_subclass(pith, types) # Dictionary mapping from the name to value of each local attribute # referenced in the "is_valid_code" snippet defined below. is_valid_code_locals: LexicalScope = {} # Name of a new parameter added to the signature of wrapper functions # whose value is this type or tuple of types, enabling this type or # tuple of types to be tested in those functions *WITHOUT* additional # stack frames. param_name_types = add_func_scope_attr( attr=types, func_scope=is_valid_code_locals) # Code snippet efficiently validating against this type. is_valid_code = VALE_CODE_CHECK_ISSUBCLASS_TEST_format( param_name_types=param_name_types) # Create and return this subscription. return BeartypeValidator( is_valid=is_valid, is_valid_code=is_valid_code, is_valid_code_locals=is_valid_code_locals, # Intentionally pass this subscription's machine-readable # representation as a string rather than lambda function returning # a string, as this string is safely, immediately, and efficiently # constructable from these arguments' representation. get_repr=f'{self._basename}[{types_repr}]', ) beartype-0.18.5/beartype/vale/_util/000077500000000000000000000000001461113517100172765ustar00rootroot00000000000000beartype-0.18.5/beartype/vale/_util/__init__.py000066400000000000000000000000001461113517100213750ustar00rootroot00000000000000beartype-0.18.5/beartype/vale/_util/_valeutilfunc.py000066400000000000000000000045121461113517100225120ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype validator callable utilities** (i.e., callables performing low-level callable-centric operations on behalf of higher-level beartype validators). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeValeSubscriptionException from beartype.vale._util._valeutiltyping import BeartypeValidatorTester from beartype._util.func.arg.utilfuncargtest import ( die_unless_func_args_len_flexible_equal) # ....................{ FORMATTERS }.................... def die_unless_validator_tester( validator_tester: BeartypeValidatorTester) -> None: ''' Raise an exception unless the passed object is a **validator tester** (i.e., caller-defined callable accepting a single arbitrary object and returning either :data:`True` if that object satisfies an arbitrary constraint *or* :data:`False` otherwise). Parameters ---------- validator_tester : BeartypeValidatorTester Object to be validated. Raises ------ beartype.roar.BeartypeValeSubscriptionException If that object is either: * *Not* callable. * A C-based rather than pure-Python callable. * A pure-Python callable accepting two or more arguments. ''' # If this validator is either uncallable, a C-based callable, *OR* a # pure-Python callable accepting more or less than one parameter, raise # an exception. die_unless_func_args_len_flexible_equal( func=validator_tester, func_args_len_flexible=1, exception_cls=BeartypeValeSubscriptionException, ) # Else, this validator is a pure-Python callable accepting exactly one # parameter. Since no further validation can be performed on this # callable without unsafely calling that callable, we accept this # callable as is for now. # # Note that we *COULD* technically inspect annotations if defined on # this callable as well. Since this callable is typically defined as a # lambda, annotations are typically *NOT* defined on this callable. beartype-0.18.5/beartype/vale/_util/_valeutilsnip.py000066400000000000000000000074001461113517100225270ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype validator code snippets** (i.e., triple-quoted pure-Python code constants formatted and concatenated together into wrapper functions type-checking decorated callables annotated by one or more beartype validators). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype._data.code.datacodeindent import CODE_INDENT_1 # ....................{ INDENTATION }.................... VALE_CODE_INDENT_1 = f'{{indent}}{CODE_INDENT_1}' ''' Code snippet prefixed by the placeholder substring ``"{indent}"`` (which the :func:`beartype._check.code.codemake.make_func_pith_code` replaces with the indentation level required by the current beartype validator) followed by a single level of indentation. ''' # ....................{ CHECK ~ factory }.................... VALE_CODE_CHECK_ISEQUAL_TEST = ''' {{indent}}# True only if this pith equals this object. {{indent}}{{obj}} == {param_name_obj_value}''' ''' :attr:`beartype.vale.IsEqual`-specific code snippet validating an arbitrary object to be equal to another arbitrary object. ''' VALE_CODE_CHECK_ISINSTANCE_TEST = ''' {{indent}}# True only if this pith is of this type. {{indent}}isinstance({{obj}}, {param_name_types})''' ''' :attr:`beartype.vale.IsInstance`-specific code snippet validating an arbitrary object to instance an arbitrary type. ''' VALE_CODE_CHECK_ISSUBCLASS_TEST = ''' {{indent}}# True only if this pith is a class subclassing this superclass. {{indent}}(isinstance({{obj}}, type) and issubclass({{obj}}, {param_name_types}))''' ''' :attr:`beartype.vale.IsSubclass`-specific code snippet validating an arbitrary type to subclass another arbitrary type. ''' # ....................{ CHECK ~ factory : isattr }.................... VALE_CODE_CHECK_ISATTR_TEST = '''( {{indent}} # True only if this pith defines an attribute with this name. {{indent}} {attr_value_expr} {{indent}} is not {local_name_sentinel} and {attr_value_is_valid_expr} {{indent}})''' ''' :attr:`beartype.vale.IsAttr`-specific code snippet validating an arbitrary object to define an attribute with an arbitrary name satisfying an arbitrary expression evaluating to a boolean. ''' _VALE_CODE_CHECK_ISATTR_VALUE_EXPR_RAW = ( 'getattr({{obj}}, {attr_name_expr}, {local_name_sentinel})') ''' :attr:`beartype.vale.IsAttr`-specific Python expression inefficiently yielding the value of the attribute with an arbitrary name of an arbitrary object to be validated. ''' VALE_CODE_CHECK_ISATTR_VALUE_EXPR = ( f'({{local_name_attr_value}} := {_VALE_CODE_CHECK_ISATTR_VALUE_EXPR_RAW})') ''' :attr:`beartype.vale.IsAttr`-specific Python expression efficiently yielding the value of the attribute with an arbitrary name of an arbitrary object to be validated. For efficiency, this expression is optimized to localize this value to a local variable whose name *must* be uniquified and formatted by the caller into the ``local_name_attr_value`` format variable. ''' # ....................{ METHODS }.................... # Format methods of the code snippets declared above as a microoptimization. VALE_CODE_CHECK_ISATTR_TEST_format = VALE_CODE_CHECK_ISATTR_TEST.format VALE_CODE_CHECK_ISATTR_VALUE_EXPR_format = ( VALE_CODE_CHECK_ISATTR_VALUE_EXPR.format) VALE_CODE_CHECK_ISEQUAL_TEST_format = VALE_CODE_CHECK_ISEQUAL_TEST.format VALE_CODE_CHECK_ISINSTANCE_TEST_format = VALE_CODE_CHECK_ISINSTANCE_TEST.format VALE_CODE_CHECK_ISSUBCLASS_TEST_format = VALE_CODE_CHECK_ISSUBCLASS_TEST.format beartype-0.18.5/beartype/vale/_util/_valeutiltext.py000066400000000000000000000107061461113517100225450ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype validator text utilities** (i.e., callables performing low-level string-centric operations on behalf of higher-level beartype validators). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.roar._roarexc import _BeartypeValeUtilException from beartype.typing import Optional from beartype._cave._cavemap import NoneTypeOr # ....................{ FORMATTERS }.................... def format_diagnosis_line( # Mandatory parameters. validator_repr: str, indent_level_outer: str, indent_level_inner: str, # Optional parameters. is_obj_valid: Optional[bool] = None, ) -> str: ''' Single line of a larger human-readable **validation failure diagnosis** (i.e., substring describing how an arbitrary object either satisfies *or* violates an arbitrary validator), formatted with the passed indentation level and boolean value. Parameters ---------- validator_repr : str **Validator representation** (i.e., unformatted single line of a larger diagnosis report to be formatted by this function). indent_level_outer : str **Outermost indentation level** (i.e., zero or more adjacent spaces prefixing each line of the returned substring). indent_level_inner : str **Innermost indentation level** (i.e., zero or more adjacent spaces delimiting the human-readable representation of the tri-state boolean and validator representation in the returned substring). is_obj_valid : Optional[bool] Tri-state boolean such that: * If ``True``, that arbitrary object satisfies the beartype validator described by this specific line. * If ``False``, that arbitrary object violates the beartype validator described by this specific line. * If ``None``, this specific line is entirely syntactic (e.g., a suffixing ")" delimiter) isolated to its own discrete line for readability. In this case, this line does *not* describe how an arbitrary object either satisfies *or* violates an arbitrary validator. Defaults to ``None``. Returns ---------- str This diagnosis line formatted with this indentation level. Raises ---------- _BeartypeValeUtilException If ``is_obj_valid`` is *not* a **tri-state boolean** (i.e., either ``True``, ``False``, or ``None``). ''' assert isinstance(validator_repr, str), ( f'{repr(validator_repr)} not string.') assert isinstance(indent_level_outer, str), ( f'{repr(indent_level_outer)} not string.') assert isinstance(indent_level_inner, str), ( f'{repr(indent_level_inner)} not string.') # If "is_obj_valid" is *NOT* a tri-state boolean, raise an exception. # # Note that this condition is intentionally validated with full-blown # exception handling rather than a simple "assert" statement. This condition # was previously implemented via a simple "assert" statement, which then # raised a non-human-readable assertion in an end user issue. *OH, GODS!* if not isinstance(is_obj_valid, NoneTypeOr[bool]): raise _BeartypeValeUtilException( f'beartype.vale._valeutiltext.format_diagnosis_line() parameter ' f'"is_obj_valid" value {repr(is_obj_valid)} ' f'not tri-state boolean for ' f'validator representation: {validator_repr}' ) # Else, "is_obj_valid" is a tri-state boolean. # String representing this boolean value, padded with spaces on the left as # needed to produce a column-aligned line diagnosis resembling: # False == ( # True == Is[lambda foo: foo.x + foo.y >= 0] & # False == Is[lambda foo: foo.x + foo.y <= 10] # ) is_obj_valid_str = '' if is_obj_valid is True: is_obj_valid_str = ' True == ' elif is_obj_valid is False: is_obj_valid_str = 'False == ' else: is_obj_valid_str = ' ' # Do one thing and do it well. return ( f'{indent_level_outer}' f'{is_obj_valid_str}' f'{indent_level_inner}' f'{validator_repr}' ) beartype-0.18.5/beartype/vale/_util/_valeutiltyping.py000066400000000000000000000047251461113517100230770ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype validator PEP-compliant type hints** (i.e., hints annotating callables declared throughout the :mod:`beartype.vale` subpackage, either for compliance with :pep:`561` or simply for documentation purposes). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from beartype.typing import ( Callable, Union, ) # ....................{ HINTS }.................... BeartypeValidatorRepresenter = Union[str, Callable[[], str]] ''' PEP-compliant type hint matching a **beartype validator representer** (i.e., either a string *or* caller-defined callable accepting no arguments returning a machine-readable representation of a beartype validator). Technically, this representation *could* be passed by the caller rather than this callable dynamically generating that representation. Pragmatically, generating this representation is sufficiently slow for numerous types of validators that deferring their generation until required by a call to the :meth:`__repr__` dunder method externally called by a call to the :func:`repr` builtin` on this validator is effectively mandatory. Validators whose representations are particularly slow to generate include: * The :class:`beartype.vale.Is` class subscripted by a lambda rather than non-lambda function. Generating the representation of that class subscripted by a non-lambda function only requires introspecting the name of that function and is thus trivially fast. However, lambda functions have no names and are thus *only* distinguishable by their source code; generating the representation of that class subscripted by a lambda function requires parsing the source code of the file declaring that lambda for the exact substring of that code declaring that lambda. ''' BeartypeValidatorTester = Callable[[object], bool] ''' PEP-compliant type hint matching a **beartype validator tester** (i.e., caller-defined callable accepting a single arbitrary object and returning either :data:`True` if that object satisfies an arbitrary constraint *or* :data:`True` otherwise). Beartype validator testers are suitable for subscripting functional beartype validator factories (e.g., :attr:`beartype.vale.Is`). ''' beartype-0.18.5/beartype_test/000077500000000000000000000000001461113517100162725ustar00rootroot00000000000000beartype-0.18.5/beartype_test/__init__.py000066400000000000000000000000001461113517100203710ustar00rootroot00000000000000beartype-0.18.5/beartype_test/_util/000077500000000000000000000000001461113517100174065ustar00rootroot00000000000000beartype-0.18.5/beartype_test/_util/README.rst000066400000000000000000000007421461113517100211000ustar00rootroot00000000000000.. # ------------------( SYNOPSIS )------------------ ====================== Project Test Utilities ====================== This subpackage provides pytest_-based **utilities** (i.e., general-purpose lower-level pytest_ fixtures and related functions leveraged by higher-level unit and functional tests) for this project's test suite. .. # ------------------( LINKS )------------------ .. _pytest: https://docs.pytest.org beartype-0.18.5/beartype_test/_util/__init__.py000066400000000000000000000000001461113517100215050ustar00rootroot00000000000000beartype-0.18.5/beartype_test/_util/command/000077500000000000000000000000001461113517100210245ustar00rootroot00000000000000beartype-0.18.5/beartype_test/_util/command/__init__.py000066400000000000000000000000001461113517100231230ustar00rootroot00000000000000beartype-0.18.5/beartype_test/_util/command/pytcmdexit.py000066400000000000000000000027051461113517100235740ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **external command exit status** (i.e., integer signifying whether a process terminated successfully or unsuccessfully) utilities. This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... # ....................{ CONSTANTS }.................... SUCCESS = 0 ''' Exit status signifying a process to have terminated successfully. ''' FAILURE_DEFAULT = 1 ''' Exit status typically signifying a process to have terminated prematurely with a fatal error. Although any exit status in the range ``[1, 255]`` signifies failure, this particular exit status is the most common and thus preferred default. ''' # ....................{ TESTERS }.................... def is_success(exit_status: int) -> bool: ''' ``True`` only if the passed exit status signifies success. ''' assert isinstance(exit_status, int), f'{exit_status} not integer.' return exit_status == SUCCESS def is_failure(exit_status: int) -> bool: ''' ``True`` only if the passed exit status signifies failure. ''' assert isinstance(exit_status, int), f'{exit_status} not integer.' return exit_status != SUCCESS beartype-0.18.5/beartype_test/_util/command/pytcmdpath.py000066400000000000000000000042071461113517100235560ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **pathable utilities** (i.e., callables querying commands residing in the current ``${PATH}`` and hence executable by specifying merely their basename rather than either relative or absolute path). This private submodule is *not* intended for importation by downstream callers. ''' # ....................{ IMPORTS }.................... from shutil import which # ....................{ TESTERS }.................... #FIXME: Unit test us up, please. def is_pathable(command_basename: str) -> bool: ''' ``True`` only if a **pathable** (i.e., external command with the passed basename corresponding to an executable file in the current ``${PATH}``) exists. Caveats ---------- For safety, avoid appending the passed basename by a platform-specific filetype -- especially, a Windows-specific filetype. On that platform, this function iteratively appends this basename by each filetype associated with executable files listed by the ``%PATHEXT%`` environment variable (e.g., ``.bat``, ``.cmd``, ``.com``, ``.exe``) until the resulting basename is that of an executable file in the current ``%PATH%``. Parameters ---------- command_basename : str Basename of the command to be searched for. Returns ---------- bool ``True`` only if this pathable exists. Raises ---------- BeartypeTestPathException If the passed string is *not* a basename (i.e., contains one or more directory separators). ''' assert isinstance(command_basename, str), ( f'{repr(command_basename)} not string.') # Defer test-specific imports. from beartype_test._util.path.pytpathname import die_unless_basename # If this string is *NOT* a pure basename, raise an exception. die_unless_basename(command_basename) # Return whether this command exists or not. return which(command_basename) is not None beartype-0.18.5/beartype_test/_util/command/pytcmdrun.py000066400000000000000000000434051461113517100234310ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **command runners** (i.e., callables running external commands as subprocesses of the active Python process). This private submodule is *not* intended for importation by downstream callers. Command Words Arguments ---------- Most runners accept a mandatory ``command_words`` parameter, a list of one or more shell words comprising this command whose: * Mandatory first item is either: * This command's absolute or relative path. * This command's basename, in which case the first command with that basename in the current ``${PATH}`` environment variable will be run. If no such command is found, an exception is raised. * Optional subsequent items are this command's arguments (in order). Note that these arguments are passed as is to a low-level system call rather than intprereted by a high-level shell (e.g., ``/bin/sh`` on POSIX-compatible platforms) and hence should *not* be shell-quoted. Indeed, shell quoting these arguments is likely to result in erroneous command behaviour. The principal exception to this heuristic are **GNU-style long value options** (i.e., ``--``-prefixed options accepting a ``=``-delimited value), whose values should either be: * Passed as a separate argument *without* being shell-quoted. * Concatenated to the current ``--``-prefixed argument delimited by ``=`` and shell-quoted. ``Popen()`` Keyword Arguments ---------- Most runners accept the same optional keyword arguments accepted by the :meth:`subprocess.Popen.__init__` constructor, including: * ``cwd``, the absolute path of the current working directory (CWD) from which this command is to be run. Defaults to the current CWD. **Unfortunately, note that this keyword argument appears to be erroneously ignored on numerous platforms** (e.g., Windows). For safety, a context manager temporarily changing the current directory should typically be leveraged instead. * ``timeout``, the maximum number of milliseconds this command is to be run for. Commands with execution time exceeding this timeout will be mercifully killed. Defaults to :data:`None`, in which case this command is run indefinitely. ''' # ....................{ IMPORTS }.................... from beartype.typing import ( Iterable, Mapping, Optional, Tuple, ) from beartype._data.hint.datahinttyping import CommandWords from collections.abc import ( Iterable as IterableABC, Mapping as MappingABC, ) from os import environ from subprocess import ( CalledProcessError, TimeoutExpired, call as subprocess_call, check_output as subprocess_check_output, run as subprocess_run, ) # ....................{ GLOBALS }.................... BUFFER_SIZE_DEFAULT = -1 ''' Default subprocess buffer size for the current platform (synonymous with the current :data:`io.DEFAULT_BUFFER_SIZE`) suitable for passing as the ``bufsize`` parameter accepted by :meth:`subprocess.Popen.__init__` method. ''' BUFFER_SIZE_NONE = 0 ''' Unbuffered subprocess buffer size suitable for passing as the ``bufsize`` parameter accepted by :meth:`subprocess.Popen.__init__` method. Both reading from and writing to an unbuffered subprocess is guaranteed to perform exactly one system call (``read()`` and ``write()``, respectively) and can return short (i.e., produce less bytes than the requested number of bytes). ''' BUFFER_SIZE_LINE = 1 ''' Line-buffered subprocess buffer size suitable for passing as the ``bufsize`` parameter accepted by :meth:`subprocess.Popen.__init__` method. Reading from a line-buffered subprocess is guaranteed to block until the subprocess emits a newline, at which point all output emitted between that newline inclusive and the prior newline exclusive is consumed. ''' # ....................{ PRIVATE ~ hints }.................... _HINT_POPEN_KWARGS = Mapping[str, object] ''' PEP-compliant type hint matching the return value of the private :func:`_init_popen_kwargs` function. ''' _HINT_POPEN_KWARGS_OPTIONAL = Optional[_HINT_POPEN_KWARGS] ''' PEP-compliant type hint matching the ``popen_kwargs`` parameter passed to most callables declared by this submodule. ''' # ....................{ RUNNERS ~ command }.................... #FIXME: Unit test us up, please. def run_command_forward_stderr_return_stdout( # Mandatory parameters. command_words: CommandWords, # Optional parameters. popen_kwargs: _HINT_POPEN_KWARGS_OPTIONAL = None, ) -> str: ''' Run the passed command as a subprocess of the active Python process, raising an exception on subprocess failure while both forwarding all standard error of this subprocess to the standard error file handle of the active Python process *and* capturing and returning all standard output of this subprocess. This exception contains the exit status of this subprocess. Parameters ---------- command_words : CommandWords Iterable of one or more shell words comprising this command. popen_kwargs : _HINT_POPEN_KWARGS_OPTIONAL Dictionary of all keyword arguments to be passed to the :meth:`subprocess.Popen.__init__` method. Defaults to :data:`None`, in which case the empty dictionary is assumed. Returns ---------- str All standard output captured from this subprocess, stripped of all trailing newlines (as under most POSIX shells) *and* decoded with the current locale's preferred encoding (e.g., UTF-8). Raises ---------- CalledProcessError If the subprocess running this command report non-zero exit status. ''' # Sanitize these arguments. popen_kwargs = _init_popen_kwargs(command_words, popen_kwargs) # Capture this command's stdout, raising an exception on command failure # (including failure due to an expired timeout). command_stdout = subprocess_check_output(command_words, **popen_kwargs) # Return this stdout, stripped of all trailing newlines. return command_stdout.rstrip('\n') #FIXME: Unit test us up, please. def run_command_forward_output( # Mandatory parameters. command_words: CommandWords, # Optional parameters. popen_kwargs: _HINT_POPEN_KWARGS_OPTIONAL = None, ) -> None: ''' Run the passed command as a subprocess of the active Python process, raising an exception on subprocess failure while forwarding all standard output and error output by this subprocess to the standard output and error file handles of the active Python process. This exception contains the exit status of this subprocess. Parameters ---------- command_words : CommandWords Iterable of one or more shell words comprising this command. popen_kwargs : _HINT_POPEN_KWARGS_OPTIONAL Dictionary of all keyword arguments to be passed to the :meth:`subprocess.Popen.__init__` method. Defaults to :data:`None`, in which case the empty dictionary is assumed. Raises ---------- CalledProcessError If the subprocess running this command report non-zero exit status. ''' # Defer test-specific imports. from beartype_test._util.command.pytcmdexit import is_failure # 0-based exit status reported by running this command. exit_status = run_command_forward_output_return_status( command_words=command_words, popen_kwargs=popen_kwargs) # If this command failed, raising an exception on command failure. For # reusability, we reimplement the subprocess.check_call() function here # rather than explicitly call that function. The latter approach would # require duplicating logic between this and the # run_command_forward_output_return_status() runner called above. if is_failure(exit_status): raise CalledProcessError(exit_status, command_words) #FIXME: Unit test us up, please. def run_command_forward_output_return_status( # Mandatory parameters. command_words: CommandWords, # Optional parameters. popen_kwargs: _HINT_POPEN_KWARGS_OPTIONAL = None ) -> int: ''' Run the passed command as a subprocess of the active Python process, returning only the exit status of this subprocess while forwarding all standard output and error output by this subprocess to the standard output and error file handles of the active Python process. Caveats ---------- **This function raises no exceptions on subprocess failure.** To do so, consider calling the :func:`run_command_forward_output` function instead. Parameters ---------- command_words : CommandWords Iterable of one or more shell words comprising this command. popen_kwargs : _HINT_POPEN_KWARGS_OPTIONAL Dictionary of all keyword arguments to be passed to the :meth:`subprocess.Popen.__init__` method. Defaults to :data:`None`, in which case the empty dictionary is assumed. Returns ---------- int Exit status returned by this subprocess. ''' # Defer test-specific imports. from beartype_test._util.command.pytcmdexit import FAILURE_DEFAULT # Sanitize these arguments. popen_kwargs = _init_popen_kwargs(command_words, popen_kwargs) # Run this command *WITHOUT* raising an exception on command failure. try: exit_status = subprocess_call(command_words, **popen_kwargs) # If this command failed to halt before triggering a timeout, the "timeout" # keyword argument was passed *AND* this command has effectively failed. # Since the prior call has already guaranteeably terminated this command, # this exception is safely convertible into default failure exit status. except TimeoutExpired: exit_status = FAILURE_DEFAULT # Return this exit status. return exit_status def run_command_return_stdout_stderr( # Mandatory parameters. command_words: CommandWords, # Optional parameters. popen_kwargs: _HINT_POPEN_KWARGS_OPTIONAL = None, ) -> Tuple[str, str]: ''' Run the passed command as a subprocess of the active Python process, raising an exception on subprocess failure while capturing and returning both all standard output and error of this subprocess. This exception contains the exit status of this subprocess. Parameters ---------- command_words : CommandWords Iterable of one or more shell words comprising this command. popen_kwargs : _HINT_POPEN_KWARGS_OPTIONAL Dictionary of all keyword arguments to be passed to the :meth:`subprocess.Popen.__init__` method. Defaults to :data:`None`, in which case the empty dictionary is assumed. Returns ---------- Tuple[str, str] All standard output and error (in that order) captured from this subprocess, stripped of all trailing newlines (as under most POSIX shells) *and* decoded with the current locale's preferred encoding (e.g., UTF-8). Raises ---------- CalledProcessError If the subprocess running this command reports non-zero exit status. ''' # If these keyword arguments are empty, default to the empty dictionary # *BEFORE* setting dictionary keys below. if popen_kwargs is None: popen_kwargs = {} # Else, these keyword arguments are non-empty. # In either case, these keyword arguments are now a dictionary. Assert this. assert isinstance(popen_kwargs, MappingABC), ( f'{repr(popen_kwargs)} not mapping.') # Enable capturing of both standard output and error. popen_kwargs['capture_output'] = True # Enable raising of a "CalledProcessError" exception when this command # reports non-zero exit status. popen_kwargs['check'] = True # Sanitize these arguments. popen_kwargs = _init_popen_kwargs(command_words, popen_kwargs) # "subprocess.CompletedProcess" object encapsulating the result of running # this command. command_result = subprocess_run(command_words, **popen_kwargs) # Standard output and error emitted by this command, stripped of all # trailing newlines. command_stdout = command_result.stdout.rstrip('\n') command_stderr = command_result.stderr.rstrip('\n') # Return this standard output and error. return command_stdout, command_stderr # ....................{ PRIVATE ~ constants }.................... _INIT_POPEN_KWARGS_POPEN_KWARGS_NAMES_CLOSE_FDS_CONFLICTING = frozenset(( 'stdin', 'stdout', 'stderr', 'close_fds')) ''' Frozen set of the names of all keyword parameters which if passed would prevent the :func:`_init_popen_kwargs` function from safely defaulting the ``close_fds`` parameter to false under **vanilla Microsoft Windows** (i.e., *not* running the Cygwin POSIX compatibility layer). See Also ---------- :func:`_init_popen_kwargs` Further details. ''' # ....................{ PRIVATE ~ initializers }.................... def _init_popen_kwargs( # Mandatory parameters. command_words: CommandWords, # Optional parameters. popen_kwargs: _HINT_POPEN_KWARGS_OPTIONAL = None ) -> _HINT_POPEN_KWARGS: ''' Sanitized dictionary of all keyword arguments to pass to the :class:`subprocess.Popen` callable when running the command specified by the passed shell words with the passed user-defined keyword arguments. Caveats ---------- If the current platform is vanilla Windows *and* none of the ``stdin``, ``stdout``, ``stderr``, or ``close_fds`` parameters are passed, this function defaults the ``close_fds`` parameter if unpassed to :data:`False`. Doing so causes this command to inherit all file handles (including stdin, stdout, and stderr) from the active Python process. Note that the :class:`subprocess.Popen` docstring insists that: On Windows, if ``close_fds`` is :data:`True` then no handles will be inherited by the child process. The child process will then open new file handles for stdin, stdout, and stderr. If the current terminal is a Windows Console, the underlying terminal devices and hence file handles will remain the same, in which case this is *not* an issue. If the current terminal is Cygwin-based (e.g., MinTTY), however, the underlying terminal devices and hence file handles will differ, in which case this behaviour prevents interaction between the current shell and the vanilla Windows command to be run below. In particular, all output from this command will be squelched. If at least one of stdin, stdout, or stderr are redirected to a blocking pipe, setting ``close_fds`` to :data:`False` can induce deadlocks under certain edge-case scenarios. Since all such file handles default to :data:`None` and hence are *not* redirected in this case, ``close_fds`` may be safely set to :data:`False`. On all other platforms, if ``close_fds`` is :data:`True`, no file handles *except* stdin, stdout, and stderr will be inherited by the child process. This function fundamentally differs in subtle (and only slightly documented ways) between vanilla Windows and all other platforms. These discrepancies appear to be harmful but probably unavoidable, given the philosophical gulf between vanilla Windows and all other platforms. Parameters ---------- command_words : CommandWords Iterable of one or more shell words comprising this command. popen_kwargs : _HINT_POPEN_KWARGS_OPTIONAL Dictionary of all keyword arguments to be passed to the :meth:`subprocess.Popen.__init__` method. Defaults to :data:`None`, in which case the empty dictionary is assumed. Returns ---------- _HINT_POPEN_KWARGS This dictionary of keyword arguments sanitized. ''' # Defer test-specific imports. from beartype._util.kind.map.utilmaptest import is_mapping_keys_any from beartype._util.os.utilostest import is_os_windows_vanilla # If these keyword arguments are empty, default to the empty dictionary # *BEFORE* validating these arguments as a dictionary below. if popen_kwargs is None: popen_kwargs = {} # Else, these keyword arguments are non-empty. # # In either case, these keyword arguments are now a dictionary. # Validate these parameters *AFTER* defaulting them above if needed. assert isinstance(command_words, IterableABC), ( f'{repr(command_words)} not iterable.') assert bool(command_words), '"command_words" empty.' assert isinstance(popen_kwargs, MappingABC), ( f'{repr(popen_kwargs)} not mapping.') #FIXME: Uncomment if we ever feel like implementing this additional #validation. For the moment, we simply let lower-level functionality in the #stdlib do the dirty work for us. :p # If the first shell word is this list is unrunnable, raise an exception. # die_unless_command(command_words[0]) # Log the command to be run before doing so. # log_debug('Running command: %s', ' '.join(command_words)) # If this is vanilla Windows *AND* the caller passed no keyword arguments # that would prevent us from safely defaulting the "close_fds" parameter to # false, sanitize that parameter to false. if is_os_windows_vanilla() and not is_mapping_keys_any( mapping=popen_kwargs, keys=_INIT_POPEN_KWARGS_POPEN_KWARGS_NAMES_CLOSE_FDS_CONFLICTING, ): popen_kwargs['close_fds'] = False # Isolate the current set of environment variables to this command, # preventing concurrent changes in these variables in the active process # from affecting this command's subprocess. popen_kwargs['env'] = environ.copy() # Decode command output with the current locale's preferred encoding. popen_kwargs['universal_newlines'] = True # Return these keyword arguments. return popen_kwargs beartype-0.18.5/beartype_test/_util/fixture/000077500000000000000000000000001461113517100210745ustar00rootroot00000000000000beartype-0.18.5/beartype_test/_util/fixture/__init__.py000066400000000000000000000000001461113517100231730ustar00rootroot00000000000000beartype-0.18.5/beartype_test/_util/kind/000077500000000000000000000000001461113517100203335ustar00rootroot00000000000000beartype-0.18.5/beartype_test/_util/kind/__init__.py000066400000000000000000000000001461113517100224320ustar00rootroot00000000000000beartype-0.18.5/beartype_test/_util/kind/pytkindmake.py000066400000000000000000000044011461113517100232240ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' :mod:`pytest` **object factories** (i.e., low-level test-specific utility functions creating and returning objects, typically for use in higher-level :mod:`pytest` fixtures defined elsewhere in this test suite). ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from collections.abc import ( Iterable, ) # ....................{ CONTEXTS }.................... def make_container_from_funcs(func_names: 'Iterable[str]') -> list: ''' List created by iterating over the passed iterable and, for the fully-qualified name of each callable in that iterable, dynamically importing that callable, calling that callable, and adding the items of the container returned by that callable to this list.. Parameters ---------- func_names : Iterable[str] Iterable of the fully-qualified names of all callables to be called. Returns ------- T Instance of this container, iteratively composed from the containers returned by these callables. ''' assert isinstance(func_names, Iterable), f'{repr(func_names)} not iterable.' # Defer utility-specific imports. from beartype._util.module.utilmodimport import import_module_attr # Temporary list with which to compose this container. main_list = [] # For the passed name of each callable... for func_name in func_names: assert isinstance(func_name, str), f'{repr(func_name)} not string.' # This callable dynamically imported as this name. func = import_module_attr(func_name) # List produced by calling this callable. func_list = func() # Append all items in this list to this larger list to be returned. main_list.extend(func_list) # Return this list. return main_list beartype-0.18.5/beartype_test/_util/mark/000077500000000000000000000000001461113517100203405ustar00rootroot00000000000000beartype-0.18.5/beartype_test/_util/mark/__init__.py000066400000000000000000000000001461113517100224370ustar00rootroot00000000000000beartype-0.18.5/beartype_test/_util/mark/pytmark.py000066400000000000000000000067251461113517100224130ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' :mod:`pytest` **test-marking decorators.** This submodule provides decorators *not* conditionally marking their decorated tests as either failed, parametrized, or skipped. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! import pytest # ....................{ MARKS }.................... noop = pytest.mark.noop ''' Preserve the decorated test or test parameter as is with *no* modification. Caveats ---------- This decorator has the unintended side effect of also marking all tests and test parameters decorated by this decorated with the ``noop`` tag. This harmless albeit unfortunate side effect is the result of the :mod:`pytest.mark` API, which strictly requires that *all* decorators passed to the ``marks`` parameter of the :mod:`pytest.param` function be strictly generated by the :mod:`pytest.mark` API, which then imposes this seemingly arbitrary constraint. In other words, there's absolutely nothing to see here, folks. ''' # ....................{ IGNORERS }.................... def ignore_warnings(warning_cls: type) -> 'Callable': ''' Decorate the passed test to ignore all warnings subclassing the passed :class:`Warning` class. Caveats ---------- **This high-level decorator should always be called in lieu of the low-level** :func:`pytest.mark.filterwarnings` **decorator,** whose syntax is fragile, poorly documented, and likely to silently fail. Parameters ---------- warning_cls : type :class:`Warning` class to be ignored when running the decorated test. Returns ---------- Callable This test decorated to ignore all warnings of this class. Raises ---------- BeartypeTestMarkException If this object is either: * *Not* a class. * A class that is either *not* the builtin :class:`Warning` class or a subclass of that class. ''' # Defer test-specific imports. from beartype._util.utilobject import get_object_type_name from beartype_test._util.pytroar import BeartypeTestMarkException # If this object is *NOT* a class, raise an exception. if not isinstance(warning_cls, type): raise BeartypeTestMarkException(f'{repr(warning_cls)} not type.') # Else, this object is a class. # # If this class is *NOT* a "Warning" subclass, raise an exception. if not issubclass(warning_cls, Warning): raise BeartypeTestMarkException( f'{repr(warning_cls)} not {repr(Warning)} subclass.') # Else, this class is a "Warning" subclass. # Fully-qualified name of this class. warning_classname = get_object_type_name(warning_cls) # Return the low-level pytest mark decorator ignoring all warnings of this # "Warning" subclass with a filter adhering to Python's peculiar warning # filter syntax. See also: # https://docs.python.org/3/library/warnings.html#describing-warning-filters return pytest.mark.filterwarnings(f'ignore::{warning_classname}') beartype-0.18.5/beartype_test/_util/mark/pytskip.py000066400000000000000000000436561461113517100224330ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' :mod:`pytest` **test-skipping decorators.** This submodule provides decorators conditionally marking their decorated tests as skipped depending on whether the conditions signified by the passed parameters are satisfied (e.g., the importability of the passed module name). ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! import pytest, sys from collections.abc import ( Mapping, Sequence, ) from types import FunctionType from typing import ( Optional, Type, ) # Sadly, the following imports require private modules and packages. from _pytest.runner import Skipped # ....................{ SKIP }.................... skip_if = pytest.mark.skipif ''' Conditionally skip the decorated test or fixture with the passed human-readable justification if the passed boolean is ``False``. Parameters ---------- boolean : bool Boolean to be tested. reason : str Human-readable message justifying the skipping of this test or fixture. ''' def skip(reason: str): ''' Unconditionally skip the decorated test with the passed human-readable justification. This decorator is intended to be called both directly as a function *and* indirectly as a decorator, which differs from both: * :func:`pytest.skip`, intended to be called only directly as a function. Attempting to call that function indirectly as a decorator produces extraneous ignorable messages on standard output resembling ``"SKIP [1] beartype_test/unit/test_import.py:66: could not import 'xdist'"``, for unknown (and probably uninteresting) reasons. * :func:`pytest.mark.skip`, intended to be called only indirectly as a decorator. Attempting to call that decorator directly as a function reduces to a noop, for unknown (and probably uninteresting) reasons. Parameters ---------- reason : str Human-readable message justifying the skipping of this test. Returns ------- pytest.skipif Decorator skipping the decorated test with this justification. ''' assert isinstance(reason, str), f'{repr(reason)} not string.' return skip_if(True, reason=reason) def skip_unless_godmode(): ''' Skip the decorated test or fixture unless the **current user** (i.e., as identified by the POSIX-compatible ``${USER}`` shell variable) has **God-mode privileges** (i.e., is the principal maintainer of this package). An *extremely* small subset of tests and fixtures are sufficiently fragile as to warrant their isolation to God-mode-endowed users, typically due to making unsafe assumptions (e.g., hardware- and computational load-sensitive timing thresholds) that render those tests and fixtures unsuitable for a general audience. *There by God-mode dragons here.* Returns ------- pytest.skipif Decorator describing these requirements if unmet *or* the identity decorator reducing to a noop otherwise. ''' # Defer heavyweight imports. from beartype._util.os.utilosshell import get_shell_var_value_or_none # Local name of the current user invoking the active Python interpreter if # the current platform is POSIX-compatible *OR* "None" otherwise (e.g., if # the current platform is vanilla Windows). username = get_shell_var_value_or_none('USER') # Skip this test if the current platform is POSIX-compatible *AND* the # current user invoking the active Python interpreter is *NOT* a # God-mode-endowed user (i.e., the principal maintainer of this package). return skip_if( username != 'leycec', reason=( ( f'User "{username}" lacks God-mode test privileges ' f"(i.e., is smart and knows what's good for them)." ) if username else 'Current platform POSIX-noncompliant.' ), ) # ....................{ SKIP ~ command }.................... def skip_unless_pathable(command_basename: str): ''' Skip the decorated test or fixture unless the passed **pathable** (i.e., executable command with the passed basename residing in the current ``${PATH}``) exists on the local filesystem. Parameters ---------- command_basename : str Basename of the command to be searched for. Returns ------- pytest.skipif Decorator describing these requirements if unmet *or* the identity decorator reducing to a noop otherwise. ''' # Defer heavyweight imports. from beartype_test._util.command.pytcmdpath import is_pathable # Skip this test if *NO* command with this basename resides in the ${PATH}. return skip_if( not is_pathable(command_basename), reason=f'Command "{command_basename}" not found.' ) # ....................{ SKIP ~ host }.................... def skip_if_ci(): ''' Skip the decorated test or fixture if the active Python interpreter is running under a remote continuous integration (CI) workflow. Returns ------- pytest.skipif Decorator skipping this text or fixture if this interpreter is CI-hosted *or* the identity decorator reducing to a noop otherwise. ''' # Defer heavyweight imports. from beartype_test._util.pytci import is_ci # Skip this test if the active Python interpreter is CI-hosted. return skip_if(is_ci(), reason='Incompatible with CI workflows.') # ....................{ SKIP ~ os }.................... def skip_unless_os_linux(): ''' Skip the decorated test or fixture unless the active Python interpreter is running under a Linux distribution. Equivalently, skip the decorated test or fixture if this interpreter is running under either Microsoft Windows *or* Apple macOS. Returns ------- pytest.skipif Decorator skipping this text or fixture unless this interpreter is running under a Linux distribution *or* the identity decorator reducing to a noop otherwise. ''' # Defer heavyweight imports. from beartype._util.os.utilostest import is_os_linux # Skip this test unless the current platform is Linux return skip_if(not is_os_linux(), reason='OS not Linux.') # ....................{ SKIP ~ pep }.................... #FIXME: Currently unused, but preserved in the likelihood of us requiring #similar PEP-specific conditionality at some point. # def skip_unless_pep544(): # ''' # Skip the decorated test or fixture unless the active Python interpreter # supports :pep:`544` via the :class:`beartype.typing.Protocol` superclass. # # Specifically, this decorator skips this text or function unless this # interpreter targets: # # * Python >= 3.8, which unconditionally supports :pep:`544`. # * Python 3.7 *and* the third-party :mod:`typing_extensions` module is # importable, in which case the :class:`beartype.typing.Protocol` # superclass is a Python 3.7-compatible backport. # # Returns # ------- # pytest.skipif # Decorator skipping this text or fixture if this interpreter is PyPy # *or* the identity decorator reducing to a noop otherwise. # ''' # # # Avoid circular import dependencies. # from beartype._util.py.utilpyversion import ( # IS_PYTHON_3_7, # IS_PYTHON_AT_LEAST_3_8, # ) # from beartype_test._util.module.pytmodtest import is_package_typing_extensions # # # True only if the active Python interpreter supports PEP 544. See the # # decorator docstring for further details. # IS_PEP_544 = ( # IS_PYTHON_AT_LEAST_3_8 or ( # IS_PYTHON_3_7 and is_package_typing_extensions() # ) # ) # # print(f'IS_PEP_544: {IS_PEP_544}') # # print(f'IS_PYTHON_3_7: {IS_PYTHON_3_7}') # # print(f'te: {is_package_typing_extensions()}') # # # Skip this test unless the active Python interpreter supports PEP 544. # return skip_if( # not IS_PEP_544, # reason=f'Python {_PYTHON_VERSION_STR} lacks PEP 544 support.', # ) # ....................{ SKIP ~ py }.................... def skip_if_pypy(): ''' Skip the decorated test or fixture if the active Python interpreter is the PyPy, a third-party implementation emphasizing Just In Time (JIT) bytecode optimization. Returns ------- pytest.skipif Decorator skipping this text or fixture if this interpreter is PyPy *or* the identity decorator reducing to a noop otherwise. ''' # Defer test-specific imports. from beartype._util.py.utilpyinterpreter import is_python_pypy # Skip this test if the active Python interpreter is PyPy. return skip_if(is_python_pypy(), reason='Incompatible with PyPy.') def skip_if_python_version_greater_than_or_equal_to(version: str): ''' Skip the decorated test or fixture if the version of the active Python interpreter is strictly greater than or equal to the passed maximum version. Parameters ---------- version : str Maximum version of the Python interpreter required by this test or fixture as a dot-delimited string (e.g., ``3.5.0``). Returns ------- pytest.skipif Decorator describing these requirements if unmet *or* the identity decorator reducing to a noop otherwise. See Also -------- :mod:`beartype.meta` Similar logic performed at :mod:`beartype` importation time. ''' assert isinstance(version, str), f'{repr(version)} not string.' # Defer test-specific imports. from beartype.meta import _convert_version_str_to_tuple # Machine-readable required version of Python as a tuple of integers. version_tuple = _convert_version_str_to_tuple(version) # Skip this test if the current Python version exceeds this requirement. return skip_if( _PYTHON_VERSION_TUPLE >= version_tuple, reason=f'Python {_PYTHON_VERSION_STR} >= {version}.' ) def skip_if_python_version_less_than(version: str): ''' Skip the decorated test or fixture if the version of the active Python interpreter is strictly less than the passed minimum version. Parameters ---------- version : str Minimum version of the Python interpreter required by this test or fixture as a dot-delimited string (e.g., ``3.5.0``). Returns ------- pytest.skipif Decorator describing these requirements if unmet *or* the identity decorator reducing to a noop otherwise. See Also -------- :mod:`beartype.meta` Similar logic performed at :mod:`beartype` importation time. ''' assert isinstance(version, str), f'{repr(version)} not string.' # Defer test-specific imports. from beartype.meta import _convert_version_str_to_tuple # Machine-readable required version of Python as a tuple of integers. version_tuple = _convert_version_str_to_tuple(version) # Skip this test if the current Python version is less than this # requirement. return skip_if( _PYTHON_VERSION_TUPLE < version_tuple, reason=f'Python {_PYTHON_VERSION_STR} < {version}.' ) # ....................{ SKIP ~ py : module }.................... def skip_unless_package( package_name: str, minimum_version: Optional[str] = None): ''' Skip the decorated test or fixture if the package with the passed name is **unsatisfied** (i.e., either dynamically unimportable *or* importable but of a version less than the passed minimum version if non-:data:`None`). Parameters ---------- package_name : str Fully-qualified name of the package to be skipped. minimum_version : Optional[str] Optional minimum version of this package as a dot-delimited string (e.g., ``0.4.0``) to be tested for if any *or* :data:`None` otherwise, in which case any version is acceptable. Defaults to :data:`None`. Returns ------- pytest.skipif Decorator describing these requirements if unmet *or* the identity decorator reducing to a noop otherwise. ''' assert isinstance(package_name, str), ( f'{repr(package_name)} not string.') # Skip the decorated test or fixture unless the requisite dunder submodule # declared by this package satisfies these requirements. return skip_unless_module( module_name=package_name, #FIXME: Fascinatingly, this submodule importation worked for *ALL* #third-party packages except PyTorch. For unknown reasons, attempting to #dynamically import the "torch.__init__" submodule raises: # NameError: name '_C' is not defined # #Thankfully, directly importing the package as is suffices. *shrug* # module_name=f'{package_name}.__init__', minimum_version=minimum_version, ) def skip_unless_module( module_name: str, minimum_version: Optional[str] = None): ''' Skip the decorated test or fixture if the module with the passed name is **unsatisfied** (i.e., either dynamically unimportable *or* importable but of a version less than the passed minimum version if non-:data:`None`). Caveats ------- **This decorator should never be passed the fully-qualified name of a package.** Consider calling the :func:`skip_unless_package` decorator instead to skip unsatisfied packages. Calling this decorator with package names guarantees those packages to be skipped, as packages are *not* directly importable as modules. Parameters ---------- module_name : str Fully-qualified name of the module to be skipped. minimum_version : Optional[str] Optional minimum version of this module as a dot-delimited string (e.g., ``0.4.0``) to be tested for if any *or* :data:`None` otherwise, in which case any version is acceptable. Defaults to :data:`None`. Returns ------- pytest.skipif Decorator describing these requirements if unmet *or* the identity decorator reducing to a noop otherwise. ''' assert isinstance(module_name, str), ( f'{repr(module_name)} not string.') assert isinstance(minimum_version, (str, type(None))), ( f'{repr(minimum_version)} neither string nor "None".') return _skip_if_callable_raises_exception( exception_type=Skipped, func=pytest.importorskip, args=(module_name, minimum_version), ) # ....................{ SKIP ~ private }.................... def _skip_if_callable_raises_exception( # Mandatory parameters. exception_type: Type[BaseException], func: FunctionType, # Optional parameters. args: Optional[Sequence] = None, kwargs: Optional[Mapping] = None, ): ''' Skip the decorated test or fixture if calling the passed callable with the passed positional and keyword arguments raises an exception of the passed type. Specifically, if calling this callable raises: * The passed type of exception, this test is marked as skipped. * Any other type of exception, this test is marked as a failure. * No exception, this test continues as expected. Parameters ---------- exception_type : Type[BaseException] Type of exception expected to be raised by this callable. func : FunctionType Callable to be called. args : Optional[Sequence] Sequence of all positional arguments to unconditionally pass to the passed callable if any *or* :data:`None` otherwise. Defaults to :data:`None`. kwargs : Optional[Mapping] Mapping of all keyword arguments to unconditionally pass to the passed callable if any *or* :data:`None` otherwise. Defaults to :data:`None`. Returns ------- pytest.skipif Decorator skipping this test if this callable raises this exception *or* the identity decorator reducing to a noop otherwise. ''' # Avoid circular import dependencies. from beartype_test._util.mark.pytmark import noop # Default all unpassed arguments to sane values. if args is None: args = () if kwargs is None: kwargs = {} # Validate *AFTER* defaulting these arguments. assert isinstance(exception_type, type), ( f'{repr((exception_type))} not type.') assert callable(func), f'{repr(func)} uncallable.' assert isinstance(args, Sequence), f'{repr(args)} not sequence.' assert isinstance(kwargs, Mapping), f'{repr(kwargs)} not mapping.' # Attempt to call this callable with these arguments. try: func(*args, **kwargs) # If this callable raises an expected exception, skip this test. except exception_type as exception: return skip(str(exception)) # Else if this callable raises an unexpected exception, fail this test by # permitting this exception to unwind the current call stack. # Else, this callable raised no exception. Silently reduce to a noop. return noop # ....................{ PRIVATE ~ constants }.................... _PYTHON_VERSION_TUPLE = sys.version_info[:3] ''' Machine-readable version of the active Python interpreter as a tuple of integers. See Also -------- :mod:`beartype.meta` Similar logic performed at :mod:`beartype` importation time. ''' _PYTHON_VERSION_STR = '.'.join( str(version_part) for version_part in sys.version_info[:3]) ''' Human-readable version of the active Python interpreter as a dot-delimited string. See Also -------- :mod:`beartype.meta` Similar logic performed at :mod:`beartype` importation time. ''' beartype-0.18.5/beartype_test/_util/module/000077500000000000000000000000001461113517100206735ustar00rootroot00000000000000beartype-0.18.5/beartype_test/_util/module/__init__.py000066400000000000000000000000001461113517100227720ustar00rootroot00000000000000beartype-0.18.5/beartype_test/_util/module/pytmodtest.py000066400000000000000000000110441461113517100234610ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Test-specific **Python module detection** utilities. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype._util.cache.utilcachecall import callable_cached # ....................{ TESTERS ~ beartype }.................... @callable_cached def is_package_beartype_vale_usable() -> bool: ''' :data:`True` only if **beartype validators** (i.e., subscriptions of public classes declared by the :class:`beartype.vale` subpackage) are creatable under the active Python interpreter. Specifically, this tester returns true only if either: * This interpreter targets Python >= 3.9 and thus provides the :attr:`typing.Annotated` type hint required by beartype validators. * This interpreter targets Python >= 3.8 *and* a reasonably recent version of the third-party :mod:`typing_extensions` package is importable under this interpreter and thus provides the alternative :attr:`typing_extensions.Annotated` type hint required by beartype validators. ''' # Defer test-specific imports. from beartype._util.api.utilapityping import is_typing_attr # Return true only if the "Annotated" type hint is importable from either # the official "typing" or third-party "typing_extensions" modules. return is_typing_attr('Annotated') # ....................{ TESTERS ~ lib }.................... @callable_cached def is_package_sphinx() -> bool: ''' :data:`True` only if a reasonably recent version of Sphinx is importable under the active Python interpreter. ''' # Defer test-specific imports. from beartype.meta import _LIB_DOCTIME_MANDATORY_VERSION_MINIMUM_SPHINX from beartype._util.module.utilmodtest import is_module_version_at_least # Return true only if this version of this package is importable. return is_module_version_at_least( 'sphinx', _LIB_DOCTIME_MANDATORY_VERSION_MINIMUM_SPHINX) @callable_cached def is_package_typing_extensions() -> bool: ''' :data:`True` only if a reasonably recent version of the third-party :mod:`typing_extensions` package is importable under the active Python interpreter. ''' # Defer test-specific imports. from beartype.meta import ( _LIB_RUNTIME_OPTIONAL_VERSION_MINIMUM_TYPING_EXTENSIONS) from beartype._util.module.utilmodtest import is_module_version_at_least # Return true only if this version of this package is importable. return is_module_version_at_least( 'typing_extensions', _LIB_RUNTIME_OPTIONAL_VERSION_MINIMUM_TYPING_EXTENSIONS, ) # ....................{ TESTERS ~ lib : numpy }.................... @callable_cached def is_package_numpy() -> bool: ''' :data:`True` only if a reasonably recent version of NumPy is importable under the active Python interpreter. ''' # Defer test-specific imports. from beartype.meta import _LIB_RUNTIME_OPTIONAL_VERSION_MINIMUM_NUMPY from beartype._util.module.utilmodtest import is_module_version_at_least # Return true only if this version of this package is importable. return is_module_version_at_least( 'numpy', _LIB_RUNTIME_OPTIONAL_VERSION_MINIMUM_NUMPY) @callable_cached def is_package_numpy_typing_ndarray_deep() -> bool: ''' :data:`True` only if :attr:`numpy.typing.NDArray` type hints are deeply supported by the :func:`beartype.beartype` decorator under the active Python interpreter. Specifically, this tester returns true only if: * A reasonably recent version of NumPy is importable under the active Python interpreter. * Beartype validators are usable under the active Python interpreter, as :func:`beartype.beartype` internally reduces these hints to equivalent beartype validators. See :func:`.is_package_beartype_vale_usable`. ''' # Return true only if... return ( # A recent version of NumPy is importable *AND*... is_package_numpy() and # Beartype validators are usable under the active Python interpreter. is_package_beartype_vale_usable() ) beartype-0.18.5/beartype_test/_util/module/pytmodtyping.py000066400000000000000000000072621461113517100240230ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Testing-specific **typing attribute utilities** (i.e., functions introspecting attributes with arbitrary names dynamically imported from typing modules). ''' # ....................{ TODO }.................... #FIXME: Consider excising this submodule. Ideally, *ALL* functionality defined #by this submodule should instead reside in the central #"beartype._util.api.utilapityping" submodule, which significantly overlaps #with this submodule in harmful way. We appear to have duplicated work between #these two submodules unknowingly, which is simply horrid. Gah! I cry at night. # ....................{ IMPORTS }.................... from typing import Any # ....................{ IMPORTERS }.................... def import_typing_attr_or_none_safe(typing_attr_basename: str) -> Any: ''' Dynamically import and return the **typing attribute** (i.e., object declared at module scope by either the :mod:`typing` or :mod:`typing_extensions` modules) with the passed unqualified name if importable from one or more of these modules *or* ``None`` otherwise otherwise (i.e., if this attribute is *not* importable from these modules). Caveats ------- **This higher-level wrapper should typically be called in lieu of the lower-level** :func:`beartype._util.api.utilapityping.import_typing_attr_or_none` **function.** Unlike the latter, this wrapper imports from the third-party :mod:`typing_extensions` module *only* if the version of that module is sufficiently new and thus satisfies test-time requirements. Parameters ---------- typing_attr_basename : str Unqualified name of the attribute to be imported from a typing module. Returns ------- object Attribute with this name dynamically imported from a typing module. Raises ------ beartype.roar._roarexc._BeartypeUtilModuleException If this name is syntactically invalid. Warns ----- BeartypeModuleUnimportableWarning If any of these modules raise module-scoped exceptions at importation time. That said, the :mod:`typing` and :mod:`typing_extensions` modules are scrupulously tested and thus unlikely to raise such exceptions. ''' # Defer test-specific imports. from beartype._util.api.utilapityping import import_typing_attr_or_none from beartype._util.module.utilmodimport import import_module_attr_or_none from beartype._util.utilobject import SENTINEL from beartype_test._util.module.pytmodtest import ( is_package_typing_extensions) # print(f'is_package_typing_extensions: {is_package_typing_extensions()}') # If a reasonably recent version of the third-party "typing_extensions" # package is importable under the active Python interpreter, defer to this # higher-level importer possibly returning an attribute from that package; if is_package_typing_extensions(): return import_typing_attr_or_none(typing_attr_basename) # Else, either "typing_extensions" is unimportable or only an obsolete # version of "typing_extensions" is importable; in either case, avoid # possibly returning a possibly broken attribute from that package by # importing only from the official "typing" module. # Return the typing attribute imported from the official "typing" module if # defined by this version of Python *OR* "None" otherwise. return import_module_attr_or_none(f'typing.{typing_attr_basename}') beartype-0.18.5/beartype_test/_util/os/000077500000000000000000000000001461113517100200275ustar00rootroot00000000000000beartype-0.18.5/beartype_test/_util/os/__init__.py000066400000000000000000000000001461113517100221260ustar00rootroot00000000000000beartype-0.18.5/beartype_test/_util/os/pytosshell.py000066400000000000000000000051521461113517100226120ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Testable path factories** (i.e., callables creating and returning :class:`pathlib.Path` instances encapsulating testing-specific paths). ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ RELATIVIZERS }.................... def shell_quote(text: str) -> str: ''' Shell-quote the passed string in a platform-specific manner. If the current platform is: * *Not* vanilla Windows (e.g., Linux, macOS), the returned string is guaranteed to be suitable for passing as an arbitrary positional argument to external commands. * Windows, the returned string is suitable for passing *only* to external commands parsing arguments in the same manner as the Microsoft C runtime. While *all* applications on POSIX-compliant systems are required to parse arguments in the same manner (i.e., according to Bourne shell lexing), no such standard applies to Windows applications. Shell quoting is therefore fragile under Windows -- like pretty much everything. Parameters ---------- text : str String to be shell-quoted. Returns ---------- str This string shell-quoted. ''' assert isinstance(text, str), f'{repr(text)} not string.' # Defer heavyweight imports. from beartype._util.os.utilostest import is_os_windows_vanilla # If the current platform is vanilla Windows (i.e., neither Cygwin *NOR* the # Windows Subsystem for Linux (WSL)), do *NOT* perform POSIX-compatible # quoting. Vanilla Windows is POSIX-incompatible and hence does *NOT* parse # command-line arguments according to POSIX standards. In particular, # Windows does *NOT* treat single-quoted arguments as single arguments but # rather as multiple shell words delimited by the raw literal `'`. This is # circumventable by calling an officially undocumented Windows-specific # function. (Awesome.) if is_os_windows_vanilla(): import subprocess return subprocess.list2cmdline([text]) # Else, perform POSIX-compatible quoting. else: import shlex return shlex.quote(text) beartype-0.18.5/beartype_test/_util/path/000077500000000000000000000000001461113517100203425ustar00rootroot00000000000000beartype-0.18.5/beartype_test/_util/path/__init__.py000066400000000000000000000000001461113517100224410ustar00rootroot00000000000000beartype-0.18.5/beartype_test/_util/path/pytpathlib.py000066400000000000000000000115471461113517100231040ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Testable path factories** (i.e., callables creating and returning :class:`pathlib.Path` instances encapsulating testing-specific paths). ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype_test._util.pytroar import BeartypeTestPathException from pathlib import Path # ....................{ RELATIVIZERS }.................... # These functions are explicitly camelcased to enable their future refactoring # into actual classes. def DirRelative(parent_dir: Path, relative_dirname: str) -> Path: ''' Concrete platform-agnostic :mod:`Path` object encapsulating the absolute dirname of a directory relative to the passed :mod:`Path` object encapsulating an arbitrary directory if found *or* raise an exception otherwise. Parameters ---------- parent_dir : Path :mod:`Path` encapsulating an arbitrary **parent directory** (i.e., directory containing the subdirectory to be returned). relative_dirname : str Relative dirname of this subdirectory relative to this parent directory. Returns ---------- Path :mod:`Path` directory relative to the passed :mod:`Path` directory. Raises ---------- BeartypeTestPathException If this path exists but is either: * *Not* a directory. * A directory *not* satisfying the expected filesystem structure. FileNotFoundError If this path does *not* exist. RuntimeError If this path exists but whose resolution to a physical path requires resolving one or more cyclic symbolic links inducing an infinite loop. ''' assert isinstance(parent_dir, Path), ( f'{repr(parent_dir)} not "pathlib.Path" object.') assert isinstance(relative_dirname, str), ( f'{repr(relative_dirname)} not string.') # Path encapsulating the relative dirname of this subdirectory relative to # this parent directory, which has yet to be validated. subdir_unresolved = parent_dir / relative_dirname # Canonicalize this relative dirname into an absolute dirname if this path # exists *OR* raise a "FileNotFoundError" or "RuntimeError" exception # otherwise. subdir = subdir_unresolved.resolve() # If this path is *NOT* a directory, raise an exception. if not subdir.is_dir(): raise BeartypeTestPathException(f'Directory "{subdir}" not found.') # Else, this path is a directory. # Return this directory. return subdir def FileRelative(parent_dir: Path, relative_filename: str) -> Path: ''' Concrete platform-agnostic :mod:`Path` object encapsulating the absolute filename of a file relative to the passed :mod:`Path` object encapsulating an arbitrary directory if found *or* raise an exception otherwise. Parameters ---------- parent_dir : Path :mod:`Path` encapsulating an arbitrary **parent directory** (i.e., directory containing the file to be returned). relative_filename : str Relative filename of this file relative to this parent directory. Returns ---------- Path :mod:`Path` file relative to the passed :mod:`Path` directory. Raises ---------- BeartypeTestPathException If this path exists but is either: * *Not* a directory. * A directory *not* satisfying the expected filesystem structure. FileNotFoundError If this path does *not* exist. RuntimeError If this path exists but whose resolution to a physical path requires resolving one or more cyclic symbolic links inducing an infinite loop. ''' assert isinstance(parent_dir, Path), ( f'{repr(parent_dir)} not "pathlib.Path" object.') assert isinstance(relative_filename, str), ( f'{repr(relative_filename)} not string.') # Path encapsulating the relative filename of this file relative to this # parent directory, which has yet to be validated. file_unresolved = parent_dir / relative_filename # Canonicalize this relative filename into an absolute filename if this # path exists *OR* raise a "FileNotFoundError" or "RuntimeError" exception # otherwise. file = file_unresolved.resolve() # If this path is *NOT* a file, raise an exception. if not file.is_file(): raise BeartypeTestPathException(f'File "{file}" not found.') # Else, this path is a file. # Return this file. return file beartype-0.18.5/beartype_test/_util/path/pytpathmain.py000066400000000000000000000110371461113517100232540ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Test-specific **main codebase paths** (i.e., :class:`pathlib.Path` instances encapsulating test-agnostic paths applicable to the codebase being tested). ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype.meta import ( PACKAGE_NAME, PACKAGE_TEST_NAME, ) from beartype._util.cache.utilcachecall import callable_cached from beartype_test._util.path.pytpathlib import ( DirRelative, FileRelative, ) from pathlib import Path # ....................{ GETTERS ~ dir }.................... @callable_cached def get_main_dir() -> Path: ''' :mod:`Path` encapsulating the absolute dirname of the **top-level project directory** (i.e., directory containing both a ``.git/`` subdirectory and a subdirectory providing this project's package) if found *or* raise an exception otherwise. ''' # print(f'current module paths: {__package__} [{__file__}]') # Path encapsulating the current module. MODULE_FILE = Path(__file__) # Path encapsulating the current module's package. MODULE_PACKAGE_DIR = MODULE_FILE.parent # Path encapsulating the relative dirname of this project's directory # relative to the dirname of the package defining the current module. MAIN_DIR = DirRelative(MODULE_PACKAGE_DIR, '../../..') # If this project's directory either does not contain a "beartype_test" # subdirectory *OR* does but this path is not a directory, raise an # exception. This basic sanity check improves the likelihood that this # project directory is what we assume it is. # # Note that we intentionally avoid testing paths *NOT* bundled with release # tarballs (e.g., a root ".git/" directory), as doing so would prevent # external users and tooling from running tests from release tarballs. DirRelative(MAIN_DIR, PACKAGE_TEST_NAME) # Return this path. return MAIN_DIR @callable_cached def get_main_package_dir() -> Path: ''' :mod:`Path` encapsulating the absolute dirname of the **top-level project package** (i.e., directory providing this package's top-level package containing at least an ``__init__.py`` file) if found *or* raise an exception otherwise. ''' # Terrifying terseness! return DirRelative(get_main_dir(), PACKAGE_NAME) # ....................{ GETTERS ~ dir : sphinx }.................... @callable_cached def get_main_sphinx_dir() -> Path: ''' :mod:`Path` encapsulating the absolute dirname of the **top-level Sphinx documentation tree** (i.e., directory containing both the input and output subdirectories leveraged by Sphinx to build documentation for this project) if found *or* raise an exception otherwise. ''' # Immense propensity! return DirRelative(get_main_dir(), 'doc') @callable_cached def get_main_sphinx_source_dir() -> Path: ''' :mod:`Path` encapsulating the absolute dirname of the **source Sphinx documentation tree** (i.e., directory containing the input subdirectory leveraged by Sphinx to build documentation for this project) if found *or* raise an exception otherwise. ''' # Immense propensity! return DirRelative(get_main_sphinx_dir(), 'src') # ....................{ GETTERS ~ file }.................... @callable_cached def get_main_mypy_config_file() -> Path: ''' :mod:`Path` encapsulating the absolute filename of this project's **mypy configuration file** (i.e., top-level ``.mypy.ini`` file) if found *or* raise an exception otherwise. ''' # Obverse obviation! return FileRelative(get_main_dir(), 'mypy.ini') @callable_cached def get_main_readme_file() -> Path: ''' :mod:`Path` encapsulating the absolute filename of the **project readme file** (i.e., this project's front-facing ``README.rst`` file) if found *or* raise an exception otherwise. Note that the :meth:`Path.read_text` method of this object trivially yields the decoded plaintext contents of this file as a string. ''' # Perverse pomposity! return FileRelative(get_main_dir(), 'README.rst') beartype-0.18.5/beartype_test/_util/path/pytpathname.py000066400000000000000000000047511461113517100232550ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **pathname utilities** (i.e., callables inspecting all categories of pathnames, including basenames, dirnames, filenames, and filetypes). ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ IMPORTS }.................... from os.path import sep as DIRNAME_DELIMITER # ....................{ EXCEPTIONS ~ basename }.................... #FIXME: Unit test us up, please. def die_unless_basename(pathname: str) -> None: ''' Raise an exception unless the passed pathname is a **pure basename** (i.e., contains one or more directory separators). Parameters ---------- pathname : str Pathname to be validated. Raises ---------- beartype_test._util.pytroar.BeartypeTestPathException If this pathname is *not* a pure basename. See Also ---------- :func:`is_basename` Further details. ''' # Defer test-specific imports. from beartype_test._util.pytroar import BeartypeTestPathException # If this pathname is *NOT* a pure basename, raise an exception. if not is_basename(pathname): raise BeartypeTestPathException( f'Pathname "{pathname}" not pure basename ' f'(i.e., contains one or more "{DIRNAME_DELIMITER}" ' f'directory separator characters).' ) # ....................{ TESTERS ~ basename }.................... #FIXME: Unit test us up, please. def is_basename(pathname: str) -> bool: ''' ``True`` only if the passed pathname is a **pure basename** (i.e., contains no directory separators and hence no directory components). Parameters ---------- pathname : str Pathname to be tested. Returns ---------- bool ``True`` only if this pathname is a pure basename. ''' assert isinstance(pathname, str), f'{repr(pathname)} not string.' # One-liners justify college education. *OR DO THEY*!?!? return DIRNAME_DELIMITER not in pathname beartype-0.18.5/beartype_test/_util/path/pytpathtest.py000066400000000000000000000144151461113517100233120ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Test-specific **test suite paths** (i.e., :class:`pathlib.Path` instances encapsulating test-specific paths unique to this test suite). ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype.meta import PACKAGE_NAME from beartype._util.cache.utilcachecall import callable_cached from beartype_test._util.path.pytpathlib import ( DirRelative, FileRelative, ) from pathlib import Path # ....................{ GETTERS ~ dir }.................... @callable_cached def get_test_package_dir() -> Path: ''' :mod:`Path` encapsulating the absolute dirname of the **top-level test package** (i.e., directory providing this project's top-level test package containing at least an ``__init__.py`` file) if found *or* raise an exception otherwise. ''' # Avoid circular import dependencies. from beartype_test._util.path.pytpathmain import get_main_dir # Objectionable action! return DirRelative(get_main_dir(), f'{PACKAGE_NAME}_test') # ....................{ GETTERS ~ dir : func }.................... @callable_cached def get_test_func_subpackage_dir() -> Path: ''' :mod:`Path` encapsulating the absolute dirname of the **mid-level functional test subpackage** (i.e., directory providing all functional tests of this project's test suite) if found *or* raise an exception otherwise. ''' # Ostensible stencils! return DirRelative(get_test_package_dir(), 'a90_func') @callable_cached def get_test_func_data_dir() -> Path: ''' :mod:`Path` encapsulating the absolute dirname of the **mid-level functional test data directory** (i.e., directory providing sample data used throughout this project's functional tests) if found *or* raise an exception otherwise. ''' # Questionable destination! return DirRelative(get_test_func_subpackage_dir(), 'data') # ....................{ GETTERS ~ dir : func : lib }.................... @callable_cached def get_test_func_data_lib_dir() -> Path: ''' :mod:`Path` encapsulating the absolute dirname of the **mid-level third-party dependency functional test data directory** (i.e., directory providing sample data used throughout this project's functional tests exercising third-party dependencies) if found *or* raise an exception otherwise. ''' # Ejective bijection! return DirRelative(get_test_func_data_dir(), 'lib') @callable_cached def get_test_func_data_lib_nuitka_dir() -> Path: ''' :mod:`Path` encapsulating the absolute dirname of the **low-level nuitka functional test data directory** (i.e., directory providing sample code used throughout this project's :mod:`nuitka`-specific functional tests) if found *or* raise an exception otherwise. ''' # Nascent ascendency! return DirRelative(get_test_func_data_lib_dir(), 'nuitka') @callable_cached def get_test_func_data_lib_sphinx_dir() -> Path: ''' :mod:`Path` encapsulating the absolute dirname of the **low-level Sphinx functional test data directory** (i.e., directory providing sample data used throughout this project's :mod:`sphinx`-specific functional tests) if found *or* raise an exception otherwise. ''' # Flamboyant buoyancy! return DirRelative(get_test_func_data_lib_dir(), 'sphinx') # ....................{ GETTERS ~ file : func : lib }.................... @callable_cached def get_test_func_data_lib_nuitka_file() -> Path: ''' :mod:`Path` encapsulating the absolute filename of the **low-level nuitka functional test data file** (i.e., file providing sample code used throughout this project's :mod:`nuitka`-specific functional tests) if found *or* raise an exception otherwise. ''' # Ergastically eristic! return FileRelative( get_test_func_data_lib_nuitka_dir(), 'beartype_nuitka.py') # ....................{ GETTERS ~ dir : unit }.................... @callable_cached def get_test_unit_subpackage_dir() -> Path: ''' :mod:`Path` encapsulating the absolute dirname of the **mid-level unit test subpackage** (i.e., directory providing all unit tests of this project's test suite) if found *or* raise an exception otherwise. ''' # Redacted didactic! return DirRelative(get_test_package_dir(), 'a00_unit') @callable_cached def get_test_unit_data_dir() -> Path: ''' :mod:`Path` encapsulating the absolute dirname of the **mid-level unit test data directory** (i.e., directory providing sample data used throughout this project's unit tests) if found *or* raise an exception otherwise. ''' # Galactic antacid! return DirRelative(get_test_unit_subpackage_dir(), 'data') # ....................{ GETTERS ~ dir : func : claw }.................... @callable_cached def get_test_unit_data_claw_dir() -> Path: ''' :mod:`Path` encapsulating the absolute dirname of the **mid-level import hook unit test data directory** (i.e., directory providing sample data used throughout this project's unit tests exercising import hooks published by the :mod:`beartype.claw` subpackage) if found *or* raise an exception otherwise. ''' # Supernal diurnal! return DirRelative(get_test_unit_data_dir(), 'claw') @callable_cached def get_test_unit_data_claw_extraprocess_dir() -> Path: ''' :mod:`Path` encapsulating the absolute dirname of the **mid-level extraprocess import hook unit test data directory** (i.e., directory providing sample data used throughout this project's unit tests exercising import hooks published by the :mod:`beartype.claw` subpackage is Python subprocesses forked from the active Python process) if found *or* raise an exception otherwise. ''' # Charnel caramel! return DirRelative(get_test_unit_data_claw_dir(), 'extraprocess') beartype-0.18.5/beartype_test/_util/pytci.py000066400000000000000000000024721461113517100211150ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **:mod:`pytest` context manager utilities.** ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTERS }.................... def is_ci() -> bool: ''' ``True`` only if the active Python process is running under a remote continuous integration (CI) workflow. ''' # One-liners for a brighter, bolder, better future... today. return is_ci_github_actions() def is_ci_github_actions() -> bool: ''' ``True`` only if the active Python process is running under a GitHub Actions-based continuous integration (CI) workflow. ''' # Defer test-specific imports. from os import environ # Return true only if the current shell environment declares a GitHub # Actions-specific environment variable. return 'GITHUB_ACTIONS' in environ beartype-0.18.5/beartype_test/_util/pytcontext.py000066400000000000000000000021631461113517100222030ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' :mod:`pytest` **context manager utilities.** ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from contextlib import ( AbstractContextManager, contextmanager, nullcontext, ) # ....................{ CONTEXTS }.................... noop_context_manager = nullcontext ''' **Noop context manager** (i.e., context manager trivially yielding the passed parameter if any *or* :data:`None` otherwise). Parameters ---------- enter_result : object, optional Value to be yielded from this context manager. Defaults to :data:`None`. Returns ------- AbstractContextManager Noop context manager. ''' beartype-0.18.5/beartype_test/_util/pytroar.py000066400000000000000000000075561461113517100214750ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Hear beartype tests roar** as it handles errors and warnings. This submodule defines hierarchies of :mod:`beartype_test`-specific exceptions and warnings emitted by unit and functional tests and fixtures. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To avoid polluting the public module namespace, external attributes # should be locally imported at module scope *ONLY* under alternate private # names (e.g., "from argparse import ArgumentParser as _ArgumentParser" rather # than merely "from argparse import ArgumentParser"). #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from abc import ABCMeta as _ABCMeta from beartype.roar import BeartypeException from beartype._data.hint.datahinttyping import TypeException from contextlib import contextmanager from pytest import raises # ....................{ CONTEXTS }.................... @contextmanager def raises_uncached(exception_cls: TypeException) -> 'ExceptionInfo': ''' Context manager validating that the block exercised by this manager raises a **cached exception** (i.e., whose message previously containing one or more instances of the magic :data:`beartype._data.error.dataerrmagic.EXCEPTION_PLACEHOLDER` substring since replaced by the :func:`beartype._util.error.utilerrraise.reraise_exception_placeholder` function) of the passed type. Parameters ---------- exception_cls : str Type of cached exception expected to be raised by this block. Returns ------- :class:`pytest.nodes.ExceptionInfo` :mod:`pytest`-specific object collecting metadata on the cached exception of this type raised by this block. See Also -------- https://docs.pytest.org/en/stable/reference.html#pytest._code.ExceptionInfo Official :class:`pytest.nodes.ExceptionInfo` documentation. ''' # Defer test-specific imports. from beartype._data.error.dataerrmagic import EXCEPTION_PLACEHOLDER # Within a "pytest"-specific context manager validating this contextual # block to raise an exception of this type, perform the body of that block. with raises(exception_cls) as exception_info: yield exception_info # Exception message raised by the body of that block. exception_message = str(exception_info.value) # Assert this exception message does *NOT* contain this magic substring. assert EXCEPTION_PLACEHOLDER not in exception_message #FIXME: Inadvisable in the general case, but preserved for posterity. # Assert this exception message does *NOT* contain two concurrent spaces, # implying an upstream failure in substring concatenation. # assert ' ' not in exception_message # ....................{ SUPERCLASS }.................... class BeartypeTestException(BeartypeException, metaclass=_ABCMeta): ''' Abstract base class of all **beartype test exceptions.** Instances of subclasses of this exception are raised at test time from :mod:`beartype_test`-specific unit and functional tests and fixtures. ''' pass class BeartypeTestPathException(BeartypeTestException): ''' **Beartype test path exception.** This exception is raised at test time from callables and classes defined by the :mod:`beartype_test._util.path` subpackage. ''' pass class BeartypeTestMarkException(BeartypeTestException): ''' **Beartype test mark exception.** This exception is raised at test time from decorators defined by the :mod:`beartype_test._util.mark` subpackage. ''' pass beartype-0.18.5/beartype_test/a00_unit/000077500000000000000000000000001461113517100177115ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/README.rst000066400000000000000000000007511461113517100214030ustar00rootroot00000000000000.. # ------------------( SYNOPSIS )------------------ ================== Project Unit Tests ================== This subpackage provides *all* pytest_-based **unit tests** (i.e., callables collectively exercising this project's public API but individually exercising only a small subset of that API referred to as a "unit") for this project. .. # ------------------( LINKS )------------------ .. _pytest: https://docs.pytest.org beartype-0.18.5/beartype_test/a00_unit/__init__.py000066400000000000000000000000001461113517100220100ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a00_core/000077500000000000000000000000001461113517100213015ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a00_core/__init__.py000066400000000000000000000000001461113517100234000ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a00_core/test_a00_package.py000066400000000000000000000066051461113517100247540ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype API unit tests.** This submodule unit tests the public API of the :mod:`beartype` package itself as implemented by the :mod:`beartype.__init__` submodule. Note that these tests are intentionally performed before *any* other tests. Why? Because several of these tests (notably, the critical :func:`test_api_deprecations` unit test) erroneously reports false negatives and is thus largely useless when run at a later test time. Why? We have *no* idea, honestly. Tests that fail should *always* fail, regardless of when :mod:`pytest` runs those tests. Sadly, they don't. Since we have *no* clear insights into why this might be occurring, we have *no* recourse but to perform these tests early. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_api_beartype() -> None: ''' Test the public API of the :mod:`beartype` package itself. ''' # Defer test-specific imports. import beartype from beartype._cave._cavefast import DecoratorTypes from enum import Enum # Assert this package's public attributes to be of the expected types. assert isinstance(beartype.beartype, DecoratorTypes) assert isinstance(beartype.BeartypeConf, type) assert isinstance(beartype.BeartypeStrategy, type) assert issubclass(beartype.BeartypeStrategy, Enum) assert isinstance(beartype.__version__, str) assert isinstance(beartype.__version_info__, tuple) def test_api_deprecations() -> None: ''' Test all deprecated attributes importable from the public APIs of all subpackages of the :mod:`beartype` package (including itself). ''' # Defer test-specific imports. from beartype._util.module.utilmodimport import import_module_attr from pytest import warns # Tuple of the fully-qualified names of all deprecated attributes. DEPRECATED_ATTRIBUTES = ( 'beartype.abby', 'beartype.cave.HintPep585Type', 'beartype.roar.BeartypeAbbyException', 'beartype.roar.BeartypeAbbyHintViolation', 'beartype.roar.BeartypeAbbyTesterException', 'beartype.roar.BeartypeCallHintPepException', 'beartype.roar.BeartypeCallHintPepParamException', 'beartype.roar.BeartypeCallHintPepReturnException', 'beartype.roar.BeartypeDecorHintNonPepException', 'beartype.roar.BeartypeDecorHintNonPepNumPyException', 'beartype.roar.BeartypeDecorHintPep563Exception', 'beartype.roar.BeartypeDecorHintPepDeprecatedWarning', 'beartype.roar.BeartypeDecorPepException', ) # For each deprecated attribute declared by beartype... for deprecated_attribute in DEPRECATED_ATTRIBUTES: # Assert that importing this attribute both emits the expected warning # and returns a non-"None" value. with warns(DeprecationWarning): assert import_module_attr(deprecated_attribute) is not None beartype-0.18.5/beartype_test/a00_unit/a00_core/test_a90_typing.py000066400000000000000000000235331461113517100247030ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **typing API attribute** unit tests. This submodule unit tests that the :mod:`beartype.typing` submodule publishes the expected public attributes for the active Python interpreter. Specifically, this submodule validates that there exists a one-to-one mapping between public attributes exported by the :mod:`beartype.typing` and :mod:`typing` submodules. Caveats ---------- **This submodule only tests correspondence** (i.e., the one-to-one attribute mapping detailed above). This submodule does *not* test low-level functionality of attributes declared by the :mod:`beartype.typing` submodule. Why? Because this submodule is intentionally situated in this unit test hierarchy so as to be tested *before* all other unit tests. Why? Because the :mod:`beartype` codebase widely imports from the :mod:`beartype.typing` submodule at module scope and thus requires that submodule to declare the expected attributes. Conversely, testing the actual behaviour of these attributes often requires exercising those attributes against the high-level :func:`beartype.beartype` decorator. Since that decorator has yet to be validated as functional at this extremely early stage in the running of this test suite, we necessarily defer testing the behaviour of these attributes to the subsequent :mod:`a40_api.typing` subpackage. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_api_typing() -> None: ''' Test the public API of the :mod:`beartype.meta` submodule. This test exercises that there exists a one-to-one mapping between public attributes exported by the :mod:`beartype.typing` and :mod:`typing` submodules. See the class docstring for relevant commentary. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. import typing as official_typing from beartype import typing as beartype_typing from beartype._util.py.utilpyversion import ( IS_PYTHON_AT_LEAST_3_9, ) # ..................{ LOCALS }.................. # Frozen set of the basenames of all erroneously publicized public # attributes of all "typing" modules across all Python versions. Ideally, # these attributes would have been privatized by prefixing these basenames # by "_". Ideally, the "typing.__all__" list would accurately list the # basenames of all explicitly exported public attributes. Since neither of # these two ideals is reflected by the "typing" module, this set exists. OFFICIAL_TYPING_ATTR_PUBLIC_BAD_NAMES = frozenset(( 'ABCMeta', 'EXCLUDED_ATTRIBUTES', 'CT_co', 'KT', 'T', 'T_co', 'T_contra', 'V_co', 'VT', 'VT_co', 'CallableMeta', 'GenericAlias', 'GenericMeta', 'TupleMeta', 'TypingMeta', 'MethodDescriptorType', 'MethodWrapperType', 'NamedTupleMeta', 'WrapperDescriptorType', 'abc', 'abstractmethod', 'abstractproperty', 'collections', 'collections_abc', 'contextlib', 'copyreg', 'defaultdict', 'functools', 'io', 'operator', 're', 'stdlib_re', 'sys', 'types', 'warnings', )) # Dictionaries mapping from the basenames of all public attributes declared # by the "beartype.typing" and "typing" modules to those attributes. BEARTYPE_TYPING_ATTR_NAME_TO_VALUE = { # Public attribute declared by the "beartype.typing" submodule. beartype_typing_attr_name: getattr( beartype_typing, beartype_typing_attr_name) # For the basename of each attribute declared by this submodule... for beartype_typing_attr_name in dir(beartype_typing) # If this basename is prefixed by "_", this is a private rather than # public attribute. If this basename is prefixed by "@", it is most # likely either "@pytest_ar" or "@py_builtins" inserted from pytest # during test execution. In either case, ignore this attribute. if beartype_typing_attr_name[0] not in '@_' # Else, this attribute is public and thus unignorable. } OFFICIAL_TYPING_ATTR_NAME_TO_VALUE = { # Public attribute declared by the "typing" submodule. official_typing_attr_name: getattr( official_typing, official_typing_attr_name) # For the basename of each attribute declared by this submodule... for official_typing_attr_name in dir(official_typing) # If this basename is... # # In this case, ignore this attribute. if ( # Prefixed by "_" (implying this attribute to be a private rather # than public attribute) *AND*... official_typing_attr_name[0] != '_' and # This attribute was *NOT* erroneously publicized but should have # instead been privatized. Work with me here, CPython developers. official_typing_attr_name not in OFFICIAL_TYPING_ATTR_PUBLIC_BAD_NAMES ) # Else, this attribute is public and thus unignorable. } # Set of the basenames of all public attributes declared by the "typing" # module whose *VALUES* differ from those declared by the "beartype.typing" # submodule. TYPING_ATTR_UNEQUAL_NAMES = { # Names of all inefficient PEP 544-specific "typing" attributes # overridden by efficient "beartype.typing" variants of the same name. 'Protocol', 'SupportsAbs', 'SupportsBytes', 'SupportsComplex', 'SupportsFloat', 'SupportsIndex', 'SupportsInt', 'SupportsRound', } # Sets of all public attributes exposed by "beartype.typing" and "typing". BEARTYPE_TYPING_ATTR_NAMES = BEARTYPE_TYPING_ATTR_NAME_TO_VALUE.keys() OFFICIAL_TYPING_ATTR_NAMES = OFFICIAL_TYPING_ATTR_NAME_TO_VALUE.keys() # Set of all desynchronized public attributes (i.e., exposed in exactly one # of either "beartype.typing" or "typing" but *NOT* both). DIFFERENT_TYPING_ATTR_NAMES = ( BEARTYPE_TYPING_ATTR_NAMES ^ OFFICIAL_TYPING_ATTR_NAMES) # ..................{ LOCALS ~ version }.................. # If the active Python interpreter targets Python >= 3.9 and thus supports # PEP 585, add all "typing" attributes deprecated by PEP 585 to this set. if IS_PYTHON_AT_LEAST_3_9: TYPING_ATTR_UNEQUAL_NAMES.update({ 'AbstractSet', 'AsyncContextManager', 'AsyncGenerator', 'AsyncIterable', 'AsyncIterator', 'Awaitable', 'ByteString', 'Callable', 'ChainMap', 'Collection', 'Container', 'ContextManager', 'Coroutine', 'Counter', 'DefaultDict', 'Deque', 'Dict', 'FrozenSet', 'Generator', 'ItemsView', 'Iterable', 'Iterator', 'KeysView', 'List', 'Mapping', 'MappingView', 'Match', 'Pattern', 'MutableMapping', 'MutableSequence', 'MutableSet', 'OrderedDict', 'Reversible', 'Set', 'Tuple', 'Type', 'Sequence', 'ValuesView', }) # Set of the basenames of all public attributes declared by the "typing" # module whose values are identical to those declared by the # "beartype.typing" submodule. TYPING_ATTR_EQUAL_NAMES = ( BEARTYPE_TYPING_ATTR_NAME_TO_VALUE.keys() - TYPING_ATTR_UNEQUAL_NAMES) # ..................{ ASSERTS }.................. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: When this assertion fails, the culprit is *USUALLY* the "typing" # module for the active Python module, which has probably erroneously # publicized one or more public attributes. In this case, the names of these # attributes *MUST* be manually added to the # "OFFICIAL_TYPING_ATTR_PUBLIC_BAD_NAMES" set defined far above. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Assert that these two modules expose the same number of public attributes. # Since a simple assertion statement would produce non-human-readable # output, we expand this assertion to identify all differing attributes. assert DIFFERENT_TYPING_ATTR_NAMES == set() # For the basename of each typing attribute whose values are identical # across these two modules... for typing_attr_equal_name in TYPING_ATTR_EQUAL_NAMES: # Assert these values are indeed identical. assert ( BEARTYPE_TYPING_ATTR_NAME_TO_VALUE[typing_attr_equal_name] is OFFICIAL_TYPING_ATTR_NAME_TO_VALUE[typing_attr_equal_name] ) # For the basename of each typing attribute whose values differ across # these two modules... for typing_attr_unequal_name in TYPING_ATTR_UNEQUAL_NAMES: # Assert these values are indeed different. assert ( BEARTYPE_TYPING_ATTR_NAME_TO_VALUE[typing_attr_unequal_name] is not OFFICIAL_TYPING_ATTR_NAME_TO_VALUE[typing_attr_unequal_name] ) beartype-0.18.5/beartype_test/a00_unit/a10_data/000077500000000000000000000000001461113517100212635ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a10_data/__init__.py000066400000000000000000000000001461113517100233620ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a10_data/code/000077500000000000000000000000001461113517100221755ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a10_data/code/__init__.py000066400000000000000000000000001461113517100242740ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a10_data/code/test_datacodeindent.py000066400000000000000000000044571461113517100265660ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **Python expression indentation substring unit tests.** This submodule unit tests the public API of the public :mod:`beartype._data.hint.pep.sign.datapepsignset` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_data_indent_level_to_code() -> None: ''' Test the :obj:`beartype._data.code.datacodeindent.INDENT_LEVEL_TO_CODE` dictionary singleton. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype._data.code.datacodeindent import INDENT_LEVEL_TO_CODE from pytest import raises # ....................{ PASS }.................... # Assert this dictionary indexed by various indentation levels creates and # returns the expected indentation string constants. assert INDENT_LEVEL_TO_CODE[1] == ' ' assert INDENT_LEVEL_TO_CODE[2] == ' ' assert INDENT_LEVEL_TO_CODE[3] == ' ' # Assert this dictionary internally caches these constants. assert INDENT_LEVEL_TO_CODE[1] is INDENT_LEVEL_TO_CODE[1] assert INDENT_LEVEL_TO_CODE[2] is INDENT_LEVEL_TO_CODE[2] assert INDENT_LEVEL_TO_CODE[3] is INDENT_LEVEL_TO_CODE[3] # ....................{ FAIL }.................... # Assert that attempting to index this dictionary by non-integer # indentation levels raises the expected exception. with raises(AssertionError): INDENT_LEVEL_TO_CODE[2.34] # Assert that attempting to index this dictionary by non-positive # indentation levels raises the expected exception. with raises(AssertionError): INDENT_LEVEL_TO_CODE[0] with raises(AssertionError): INDENT_LEVEL_TO_CODE[-1] beartype-0.18.5/beartype_test/a00_unit/a10_data/hint/000077500000000000000000000000001461113517100222255ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a10_data/hint/__init__.py000066400000000000000000000000001461113517100243240ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a10_data/hint/pep/000077500000000000000000000000001461113517100230115ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a10_data/hint/pep/__init__.py000066400000000000000000000000001461113517100251100ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a10_data/hint/pep/sign/000077500000000000000000000000001461113517100237515ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a10_data/hint/pep/sign/__init__.py000066400000000000000000000000001461113517100260500ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a10_data/hint/pep/sign/test_datapepsignset.py000066400000000000000000000071121461113517100303760ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **type hint sign set unit tests.** This submodule unit tests the public API of the public :mod:`beartype._data.hint.pep.sign.datapepsignset` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ testers }.................... def test_hint_signs_origin_isinstanceable_args() -> None: ''' Test all global frozen sets defined by the :mod:`beartype._data.hint.pep.sign.datapepsignset` submodule whose names are prefixed by ``"HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_"``. This test guarantees conformance between these sets and their corresponding type hint factories published by the :mod:`typing` module. ''' # Defer test-specific imports. import typing from beartype._data.hint.pep.sign.datapepsignset import ( HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_1, HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_2, HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_3, ) from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_9 # Tuple of 2-tuples "(args_len, args_signs)", where: # * "args_len" is the number of arguments accepted by this kind of type hint # factory originating from an isinstanceable origin type. # * "args_signs" is the set of all signs uniquely identifying type hint # factories originating from an isinstanceable origin type which are # subscriptable (i.e., indexable) by this number of arguments. ARGS_LEN_TO_SIGNS = ( (1, HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_1), (2, HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_2), (3, HINT_SIGNS_ORIGIN_ISINSTANCEABLE_ARGS_3), ) # For each number of arguments and set of signs uniquely identifying type # hint factories subscriptable by this number of arguments... for args_len_belief, args_signs in ARGS_LEN_TO_SIGNS: # For each such sign... for args_sign in args_signs: # Type hint factory published by the "typing" module with this name. args_factory = getattr(typing, args_sign.name) # Number of arguments actually accepted by this factory. args_len_actual = None # If the active Python interpreter targets Python >= 3.9, this # type hint factory conveniently defines a private "_nparams" # instance variable with this number. if IS_PYTHON_AT_LEAST_3_9: args_len_actual = args_factory._nparams # Else, this interpreter targets Python < 3.9. In this case, this # type hint factory instead defines a private "__args__" instance # variable providing the tuple of all "TypeVar" instances # constraining the arguments accepted by this factory. else: args_len_actual = len(args_factory.__args__) # Assert that these numbers of arguments correspond. assert args_len_belief == args_len_actual, ( f'"typing.{args_sign.name}" accepts ' f'{args_len_actual} arguments, but expected to accept ' f'{args_len_belief} arguments.' ) beartype-0.18.5/beartype_test/a00_unit/a20_util/000077500000000000000000000000001461113517100213305ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/__init__.py000066400000000000000000000000001461113517100234270ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/a00_cache/000077500000000000000000000000001461113517100230335ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/a00_cache/map/000077500000000000000000000000001461113517100236105ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/a00_cache/map/__init__.py000066400000000000000000000000001461113517100257070ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/a00_cache/map/test_utilmapbig.py000066400000000000000000000052521461113517100273620ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. """ Project-wide **unbounded cache** utility unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.cache.map.utilmapbig` submodule. """ # ....................{ IMPORTS }.................... # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_cacheunboundedstrong() -> None: """ Test successful usage of the :class:`beartype._util.cache.map.utilmapbig.CacheUnboundedStrong` class. """ # Defer test-specific imports. from beartype._util.cache.map.utilmapbig import CacheUnboundedStrong # Initially empty unbounded cache. cache_unbounded = CacheUnboundedStrong() # Arbitrary key-value pairs. KEY_A = 'My own, my human mind, which passively' VALUE_A = 'Now renders and receives fast influencings,' KEY_B = 'Holding an unremitting interchange' VALUE_B = 'With the clear universe of things around;' def value_factory(key) -> object: ''' Arbitrary function accepting an arbitrary key and dynamically returning the value to be associated with this key. ''' # Trivially return the hash of this key. return hash(key) # Assert that statically getting an uncached key returns the passed value # (i.e., caches that key with that value). assert cache_unbounded.cache_or_get_cached_value( key=KEY_A, value=VALUE_A) is VALUE_A # Assert that statically getting a cached key returns the previously # (rather than currently) passed value. assert cache_unbounded.cache_or_get_cached_value( key=KEY_A, value=VALUE_B) is VALUE_A # Assert that dynamically getting a cached key returns the previously # passed value rather than a value returned by the passed value factory. assert cache_unbounded.cache_or_get_cached_func_return_passed_arg( key=KEY_A, value_factory=value_factory, arg=KEY_A) is VALUE_A # Assert that dynamically getting an uncached key returns the value # returned by the passed value factory (i.e., caches that key with that # value). assert cache_unbounded.cache_or_get_cached_func_return_passed_arg( key=KEY_B, value_factory=value_factory, arg=KEY_B) == hash(KEY_B) beartype-0.18.5/beartype_test/a00_unit/a20_util/a00_cache/map/test_utilmaplru.py000066400000000000000000000116251461113517100274240ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. """ Project-wide **Least Recently Used (LRU) cache** utility unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.cache.map.utilmaplru` submodule. """ # ....................{ IMPORTS }.................... # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from pytest import raises from beartype.roar._roarexc import _BeartypeUtilCacheLruException # ....................{ TESTS }.................... def test_lrucachestrong_one_pass() -> None: """ Test successful usage of the :func:`beartype._util.cache.map.utilmaplru.CacheLruStrong` class against an LRU cache caching at most one key-value pair. """ # Defer test-specific imports. from beartype._util.cache.map.utilmaplru import CacheLruStrong # Arbitrary key-value pair. LRU_CACHE_KEY_A = 'KEY_A' LRU_CACHE_VALUE_A = 'VALUE_A' # Another arbitrary key-value pair. LRU_CACHE_KEY_B = 'KEY_B' LRU_CACHE_VALUE_B = 'VALUE_B' lru_cache = CacheLruStrong(size=1) assert len(lru_cache) == 0 # Cache and confirm the first and only key-value pair of this cache is this # pair. lru_cache[LRU_CACHE_KEY_A] = LRU_CACHE_VALUE_A assert len(lru_cache) == 1 assert lru_cache[LRU_CACHE_KEY_A] == LRU_CACHE_VALUE_A # Test the implicit enforcement of cache size lru_cache[LRU_CACHE_KEY_B] = LRU_CACHE_VALUE_B assert len(lru_cache) == 1 assert lru_cache[LRU_CACHE_KEY_B] == LRU_CACHE_VALUE_B del lru_cache[LRU_CACHE_KEY_B] assert len(lru_cache) == 0 def test_lrucachestrong_two_pass() -> None: """ Test successful usage of the :func:`beartype._util.cache.map.utilmaplru.CacheLruStrong` class against an LRU cache caching at most two key-value pairs. """ # Defer test-specific imports. from beartype._util.cache.map.utilmaplru import CacheLruStrong # Arbitrary key-value pair. LRU_CACHE_KEY_A = 'KEY_A' LRU_CACHE_VALUE_A = 'VALUE_A' LRU_CACHE_ITEM_A = (LRU_CACHE_KEY_A, LRU_CACHE_VALUE_A) # Another arbitrary key-value pair. LRU_CACHE_KEY_B = 'KEY_B' LRU_CACHE_VALUE_B = 'VALUE_B' LRU_CACHE_ITEM_B = (LRU_CACHE_KEY_B, LRU_CACHE_VALUE_B) # Another arbitrary key-value pair. LRU_CACHE_KEY_C = 'KEY_C' LRU_CACHE_VALUE_C = 'VALUE_C' LRU_CACHE_ITEM_C = (LRU_CACHE_KEY_C, LRU_CACHE_VALUE_C) lru_cache = CacheLruStrong(size=2) # Cache two arbitrary key-value pairs and confirm they've been cached in # insertion order. lru_cache[LRU_CACHE_KEY_A] = LRU_CACHE_VALUE_A lru_cache[LRU_CACHE_KEY_B] = LRU_CACHE_VALUE_B lru_cache_items = iter(lru_cache.items()) assert len(lru_cache) == 2 assert next(lru_cache_items) == LRU_CACHE_ITEM_A assert next(lru_cache_items) == LRU_CACHE_ITEM_B # Confirm __getitem__ resets the key in the cache. assert lru_cache[LRU_CACHE_KEY_A] == LRU_CACHE_VALUE_A lru_cache_items = iter(lru_cache.items()) assert len(lru_cache) == 2 assert next(lru_cache_items) == LRU_CACHE_ITEM_B assert next(lru_cache_items) == LRU_CACHE_ITEM_A # Confirm __contains__ resets the key in the cache. assert LRU_CACHE_KEY_B in lru_cache lru_cache_items = iter(lru_cache.items()) assert next(lru_cache_items) == LRU_CACHE_ITEM_A assert next(lru_cache_items) == LRU_CACHE_ITEM_B # Confirm the implicit enforcement of cache size lru_cache[LRU_CACHE_KEY_C] = LRU_CACHE_VALUE_C assert len(lru_cache) == 2 assert LRU_CACHE_KEY_A not in lru_cache # Confirm the remaining key-value pairs have been cached in insertion # order. lru_cache_items = iter(lru_cache.items()) assert next(lru_cache_items) == LRU_CACHE_ITEM_B assert next(lru_cache_items) == LRU_CACHE_ITEM_C # Confirm __setitem__ resets a cached key. lru_cache[LRU_CACHE_KEY_B] = LRU_CACHE_VALUE_B lru_cache_items = iter(lru_cache.items()) assert next(lru_cache_items) == LRU_CACHE_ITEM_C assert next(lru_cache_items) == LRU_CACHE_ITEM_B def test_lrucachestrong_fail() -> None: """ Test unsuccessful usage of the :func:`beartype._util.cache.map.utilmaplru.CacheLruStrong` class. """ # Defer test-specific imports. from beartype._util.cache.map.utilmaplru import CacheLruStrong # Confirm behaviour for a non-integer size. with raises(_BeartypeUtilCacheLruException): CacheLruStrong(size="Wait a minute, I'm not an int!") # Confirm behaviour for a non-positive size. with raises(_BeartypeUtilCacheLruException): CacheLruStrong(size=0) beartype-0.18.5/beartype_test/a00_unit/a20_util/a00_cache/pool/000077500000000000000000000000001461113517100240045ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/a00_cache/pool/__init__.py000066400000000000000000000000001461113517100261030ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/a00_cache/pool/test_utilcachepool.py000066400000000000000000000134461461113517100302600ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype key pool unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.cache.pool.utilcachepool` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_key_pool_pass() -> None: ''' Test successful usage of the :class:`beartype._util.cache.pool.utilcachepool.KeyPool` type. ''' # Defer test-specific imports. from beartype._util.cache.pool.utilcachepool import KeyPool from beartype.roar._roarexc import _BeartypeUtilCachedKeyPoolException from io import StringIO from pytest import raises # Key pool to be tested, seeding empty pools keyed on the "newline" # parameter passed to the StringIO.__init__() method with a new "StringIO" # instance initialized to that parameter. # # Note that the "initial_value" parameter is currently unavailable under # "pypy3" and *MUST* thus be omitted here: # $ pypy3 # >>> from io import StringIO # >>> StringIO(initial_value='', newline=newline) # TypeError: __init__() got an unexpected keyword argument 'initial_value' key_pool = KeyPool(item_maker=lambda newline: StringIO(newline=newline)) # Acquire a new "StringIO" buffer writing Windows-style newlines. windows_stringio = key_pool.acquire(key='\r\n', is_debug=True) # Stanzas delimited by Windows-style newlines to be tested below. THE_DEAD_SHALL_BE_RAISED_INCORRUPTIBLE = '\r\n'.join(( 'My stomach, which has digested', 'four hundred treaties giving the Indians', 'eternal right to their land, I give to the Indians,', 'I throw in my lungs which have spent four hundred years', 'sucking in good faith on peace pipes.', )) BOOK_OF_NIGHTMARES = '\r\n'.join(( 'To the last man surviving on earth', 'I give my eyelids worn out by fear, to wear', 'in his long nights of radiation and silence,', 'so that his eyes can’t close, for regret', 'is like tears seeping through closed eyelids.', )) # Write a series of POSIX-style lines to this buffer. windows_stringio.write('My stomach, which has digested\n') windows_stringio.write('four hundred treaties giving the Indians\n') windows_stringio.write('eternal right to their land, I give to the Indians,\n') windows_stringio.write('I throw in my lungs which have spent four hundred years\n') windows_stringio.write('sucking in good faith on peace pipes.') # Verify the buffer implicitly converted all POSIX- to Windows-style # newlines in the resulting string. assert ( windows_stringio.getvalue() == THE_DEAD_SHALL_BE_RAISED_INCORRUPTIBLE) # Release this buffer back to its parent pool. key_pool.release(key='\r\n', item=windows_stringio, is_debug=True) # Reacquire the same buffer. windows_stringio_too = key_pool.acquire(key='\r\n', is_debug=True) # Confirm the release-require cycle returns the same object assert windows_stringio is windows_stringio_too # Acquire another new "StringIO" buffer writing Windows-style newlines. windows_stringio_new = key_pool.acquire(key='\r\n', is_debug=True) # Assert this to *NOT* be the same buffer. assert windows_stringio is not windows_stringio_new # Write a series of POSIX-style lines to this new buffer. windows_stringio_new.write('To the last man surviving on earth\n') windows_stringio_new.write('I give my eyelids worn out by fear, to wear\n') windows_stringio_new.write('in his long nights of radiation and silence,\n') windows_stringio_new.write('so that his eyes can’t close, for regret\n') windows_stringio_new.write('is like tears seeping through closed eyelids.') # Confirm the new buffer also implicitly converted all POSIX- to # Windows-style newlines in the resulting string. assert windows_stringio_new.getvalue() == BOOK_OF_NIGHTMARES # Release these buffers back to their parent pools (in acquisition order). key_pool.release(key='\r\n', item=windows_stringio, is_debug=True) key_pool.release(key='\r\n', item=windows_stringio_new, is_debug=True) # Confirm the above object is released AND that releasing an already # released object with debugging logic enabled elicits a roar. with raises(_BeartypeUtilCachedKeyPoolException): key_pool.release(key='\r\n', item=windows_stringio_new, is_debug=True) def test_key_pool_fail() -> None: ''' Test unsuccessful usage of the :class:`beartype._util.cache.pool.utilcachepool.KeyPool` type. ''' # Defer test-specific imports. from beartype._util.cache.pool.utilcachepool import KeyPool from beartype.roar._roarexc import _BeartypeUtilCachedKeyPoolException from pytest import raises # Key pool to be tested, seeding empty pools with the identity function. key_pool = KeyPool(item_maker=lambda key: key) # Verify using an unhashable key to acquire a new object throws a TypeError with raises(TypeError): key_pool.acquire( ['Lieutenant!', 'This corpse will not stop burning!'], is_debug=True, ) # Verify releasing a non-existent object elicits a roar with raises(_BeartypeUtilCachedKeyPoolException): key_pool.release(key='I should roar', item=object(), is_debug=True) beartype-0.18.5/beartype_test/a00_unit/a20_util/a00_cache/pool/test_utilcachepoollistfixed.py000066400000000000000000000200671461113517100321710ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype utility fixed list pool unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.cache.pool.utilcachepoollistfixed` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from pytest import raises # ....................{ TESTS ~ pool }.................... def test_listfixed_pool_pass() -> None: ''' Test successful usage of the :mod:`beartype._util.cache.pool.utilcachepoollistfixed` submodule. ''' # Defer test-specific imports. from beartype._util.cache.pool.utilcachepoollistfixed import ( acquire_fixed_list, release_fixed_list) # Acquire a fixed list of some length. moloch_solitude_filth = acquire_fixed_list(size=3) # Initialize this list. moloch_solitude_filth[:] = ( 'Moloch! Solitude! Filth! Ugliness! Ashcans and unobtainable dollars!', 'Children screaming under the stairways! Boys sobbing in armies! Old', 'men weeping in the parks!', ) # Acquire another fixed list of the same length. moloch_whose = acquire_fixed_list(size=3) # Initialize this list. moloch_whose[:] = ( 'Moloch whose mind is pure machinery! Moloch whose blood is running', 'money! Moloch whose fingers are ten armies! Moloch whose breast is a', 'cannibal dynamo! Moloch whose ear is a smoking tomb!', ) # Assert the contents of these lists to still be as expected. assert moloch_solitude_filth[0] == ( 'Moloch! Solitude! Filth! Ugliness! Ashcans and unobtainable dollars!') assert moloch_whose[-1] == ( 'cannibal dynamo! Moloch whose ear is a smoking tomb!') # Release these lists. release_fixed_list(moloch_solitude_filth) release_fixed_list(moloch_whose) def test_listfixed_pool_fail() -> None: ''' Test unsuccessful usage of the :mod:`beartype._util.cache.pool.utilcachepoollistfixed` submodule. ''' # Defer test-specific imports. from beartype._util.cache.pool.utilcachepoollistfixed import ( acquire_fixed_list) from beartype.roar._roarexc import _BeartypeUtilCachedFixedListException # Assert that fixed lists may only be acquired with positive integers. with raises(_BeartypeUtilCachedFixedListException): acquire_fixed_list(( 'Moloch the incomprehensible prison! Moloch the crossbone soulless', 'jailhouse and Congress of sorrows! Moloch whose buildings are', 'judgment! Moloch the vast stone of war! Moloch the stunned', 'governments!', )) with raises(_BeartypeUtilCachedFixedListException): acquire_fixed_list(-67) # ....................{ TESTS ~ type }.................... def test_listfixed_type_pass() -> None: ''' Test successful usage of the :mod:`beartype._util.cache.pool.utilcachepoollistfixed.FixedList` type. ''' # Defer test-specific imports. from beartype._util.cache.pool.utilcachepoollistfixed import FixedList # Fixed list to be tested. fixed_list = FixedList(size=11) # Assert this list to be of the expected size. assert len(fixed_list) == 11 # Assert that list slicing to an iterable of the same length succeeds. fixed_list[:] = ( 'Love the quick profit, the annual raise,', 'vacation with pay. Want more', 'of everything ready-made. Be afraid', 'to know your neighbors and to die.', 'And you will have a window in your head.', 'Not even your future will be a mystery', 'any more. Your mind will be punched in a card', 'and shut away in a little drawer.', 'When they want you to buy something', 'they will call you. When they want you', 'to die for profit they will let you know.', ) assert fixed_list[ 0] == 'Love the quick profit, the annual raise,' assert fixed_list[10] == 'to die for profit they will let you know.' # Assert that list copying succeeds. fixed_list_copy = fixed_list.copy() assert isinstance(fixed_list_copy, FixedList) assert fixed_list_copy == fixed_list def test_listfixed_type_fail() -> None: ''' Test unsuccessful usage of the :mod:`beartype._util.cache.pool.utilcachepoollistfixed.FixedList` type. ''' # Defer test-specific imports. from beartype._util.cache.pool.utilcachepoollistfixed import FixedList from beartype.roar._roarexc import _BeartypeUtilCachedFixedListException # Fixed list to be tested. fixed_list = FixedList(size=8) fixed_list[:] = ( 'Ask the questions that have no answers.', 'Invest in the millennium. Plant sequoias.', 'Say that your main crop is the forest', 'that you did not plant,', 'that you will not live to harvest.', 'Say that the leaves are harvested', 'when they have rotted into the mold.', 'Call that profit. Prophesy such returns.', ) # Assert that fixed lists refuse to support item deletion. with raises(_BeartypeUtilCachedFixedListException): del fixed_list[0] # Assert that fixed lists refuse to support in-place addition. with raises(_BeartypeUtilCachedFixedListException): fixed_list += ( 'Put your faith in the two inches of humus', 'that will build under the trees', 'every thousand years.', 'Listen to carrion – put your ear', 'close, and hear the faint chattering', 'of the songs that are to come.', 'Expect the end of the world. Laugh.', 'Laughter is immeasurable. Be joyful', 'though you have considered all the facts.', ) # Assert that fixed lists refuse to support in-place multiplication. with raises(_BeartypeUtilCachedFixedListException): fixed_list *= 0xDEADBEEF #FIXME: Disabled. For efficiency, fixed lists currently permissively #support slicing to an iterable of differing length. It is what it is. # # Assert that fixed lists refuse to support slicing to an iterable of # # differing length. # with raises(_BeartypeUtilCachedFixedListException): # fixed_list[0:2] = ( # 'Go with your love to the fields.', # 'Lie down in the shade. Rest your head', # 'in her lap. Swear allegiance', # 'to what is nighest your thoughts.', # 'As soon as the generals and the politicos', # 'can predict the motions of your mind,', # 'lose it. Leave it as a sign', # 'to mark the false trail, the way', # 'you didn’t go. Be like the fox', # 'who makes more tracks than necessary,', # 'some in the wrong direction.', # 'Practice resurrection.', # ) # Assert that fixed lists refuse to support appending. with raises(_BeartypeUtilCachedFixedListException): fixed_list.append('Love the world. Work for nothing.') # Assert that fixed lists refuse to support extending. with raises(_BeartypeUtilCachedFixedListException): fixed_list.extend(( 'Take all that you have and be poor.', 'Love someone who does not deserve it.', )) # Assert that fixed lists refuse to support clearing. with raises(_BeartypeUtilCachedFixedListException): fixed_list.clear() # Assert that fixed lists refuse to support popping. with raises(_BeartypeUtilCachedFixedListException): fixed_list.pop() # Assert that fixed lists refuse to support removal. with raises(_BeartypeUtilCachedFixedListException): fixed_list.remove('Invest in the millennium. Plant sequoias.') beartype-0.18.5/beartype_test/a00_unit/a20_util/a00_cache/pool/test_utilcachepoolobjecttyped.py000066400000000000000000000110711461113517100325050ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype utility fixed list pool unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.cache.pool.utilcachepoolobjecttyped` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from io import StringIO from pytest import raises # ....................{ TESTS ~ pool }.................... def test_objecttyped_pool_pass() -> None: ''' Test successful usage of the :mod:`beartype._util.cache.pool.utilcachepoolobjecttyped` submodule. ''' # Defer test-specific imports. from beartype._util.cache.pool.utilcachepoolobjecttyped import ( acquire_object_typed, release_object_typed) # Culturally relevant Clash lyrics to be tested below. PUBLIC_SERVICE_ANNOUNCEMENT = '\n'.join(( 'You have the right not to be killed.', 'Murder is a crime,', 'Unless it was done', 'By a policeman', 'Or an aristocrat.', )) KNOW_YOUR_RIGHTS = '\n'.join(( 'You have the right to food money --', 'Providing, of course, you', "Don't mind a little", 'Investigation, humiliation,', 'And (if you cross your fingers)', 'Rehabilitation.', )) # Acquire an arbitrary string buffer. public_service_announcement = acquire_object_typed(cls=StringIO) # Clear this buffer and reset its position to the start. public_service_announcement.truncate(0) public_service_announcement.seek(0) # Write a series of culturally relevant Clash lyrics to this buffer. public_service_announcement.write('You have the right not to be killed.\n') public_service_announcement.write('Murder is a crime,\n') public_service_announcement.write('Unless it was done\n') public_service_announcement.write('By a policeman\n') public_service_announcement.write('Or an aristocrat.') # Acquire another arbitrary string buffer. know_your_rights = acquire_object_typed(cls=StringIO) # Clear this buffer and reset its position to the start. know_your_rights.truncate(0) know_your_rights.seek(0) # Write another series of culturally relevant Clash lyrics to this buffer. know_your_rights.write('You have the right to food money --\n') know_your_rights.write('Providing, of course, you\n') know_your_rights.write("Don't mind a little\n") know_your_rights.write('Investigation, humiliation,\n') know_your_rights.write('And (if you cross your fingers)\n') know_your_rights.write('Rehabilitation.') # Assert the contents of these buffers to still be as expected. assert ( public_service_announcement.getvalue() == PUBLIC_SERVICE_ANNOUNCEMENT) assert know_your_rights.getvalue() == KNOW_YOUR_RIGHTS # Release the first buffer back to its parent pool. release_object_typed(public_service_announcement) # Reacquire the same buffer again. public_service_announcement_too = acquire_object_typed(cls=StringIO) # Assert this to be the same buffer. assert public_service_announcement is public_service_announcement_too # Assert the second buffer to *NOT* be the same buffer. assert public_service_announcement is not know_your_rights # Release these buffers back to their parent pools (in acquisition order). release_object_typed(public_service_announcement) release_object_typed(know_your_rights) def test_objecttyped_pool_fail() -> None: ''' Test unsuccessful usage of the :mod:`beartype._util.cache.pool.utilcachepoolobjecttyped` submodule. ''' # Defer test-specific imports. from beartype._util.cache.pool.utilcachepoolobjecttyped import ( acquire_object_typed) from beartype.roar._roarexc import _BeartypeUtilCachedObjectTypedException # Assert that typed objects may only be acquired with types. with raises(_BeartypeUtilCachedObjectTypedException): acquire_object_typed(( 'You have the right to free speech', 'As long as', "You're not dumb enough to actually try it.", )) with raises(_BeartypeUtilCachedObjectTypedException): acquire_object_typed(1977) beartype-0.18.5/beartype_test/a00_unit/a20_util/a00_cache/test_utilcachecall.py000066400000000000000000000256171461113517100272540ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype callable caching utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.cache.utilcachecall` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_callable_cached() -> None: ''' Test the :func:`beartype._util.cache.utilcachecall.callable_cached` decorator. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilCallableCachedException from beartype._util.cache.utilcachecall import callable_cached from pytest import raises # ..................{ CALLABLES }.................. @callable_cached def still_i_rise(bitter, twisted, lies): ''' Arbitrary non-variadic callable memoized by this decorator. ''' # If an arbitrary condition, raise an exception whose value depends on # these parameters to exercise this decorator's conditional caching of # exceptions. if len(lies) == 6: raise ValueError(lies) # Else, return a value depending on these parameters to exercise this # decorator's conditional caching of return values. return bitter + twisted + lies @callable_cached def from_savage_men(with_his, sweet_voice = ('and, eyes',), *args): ''' Arbitrary variadic callable memoized by this decorator. ''' # If an arbitrary condition, raise an exception whose value depends on # these parameters to exercise this decorator's conditional caching of # exceptions. if len(args) == 6: raise ValueError(lies) # Else, return a value depending on these parameters to exercise this # decorator's conditional caching of return values. return with_his + sweet_voice + args # ..................{ LOCALS }.................. # Hashable objects to be passed as parameters below. bitter = ('You', 'may', 'write', 'me', 'down', 'in', 'history',) twisted = ('With', 'your', 'bitter,', 'twisted,', 'lies.',) lies = ('You', 'may', 'trod,', 'me,', 'in', 'the', 'very', 'dirt',) dust = ('But', 'still,', 'like', 'dust,', "I'll", 'rise',) # ..................{ PASS ~ non-variadic }.................. # Test the non-variadic function defined above. # Assert that memoizing two calls passed the same positional arguments # caches and returns the same value. assert ( still_i_rise(bitter, twisted, lies) is still_i_rise(bitter, twisted, lies)) # Assert that memoizing a call expected to raise an exception does so. with raises(ValueError) as exception_first_info: still_i_rise(bitter, twisted, dust) # Assert that repeating that call reraises the same exception. with raises(ValueError) as exception_next_info: still_i_rise(bitter, twisted, dust) assert exception_first_info.value is exception_next_info.value # Assert that passing one or more unhashable parameters to this callable # succeeds with the expected return value. assert still_i_rise( ['Just', 'like', 'moons',], ['and', 'like', 'suns',], ['With the certainty of tides',], ) == [ 'Just', 'like', 'moons', 'and', 'like', 'suns', 'With the certainty of tides', ] # ..................{ PASS ~ non-variadic }.................. # Test the variadic function defined above. # Assert that memoizing two calls passed *NO* optional or variadic # positional arguments caches and returns the same value. assert from_savage_men(bitter) is from_savage_men(bitter) # Assert that memoizing two calls passed optional positional arguments but # *NO* variadic positional arguments caches and returns the same value. assert from_savage_men(bitter, twisted) is from_savage_men(bitter, twisted) # Assert that memoizing two calls passed the same positional arguments # caches and returns the same value. assert ( from_savage_men(bitter, twisted, *lies) is from_savage_men(bitter, twisted, *lies)) def test_method_cached_arg_by_id() -> None: ''' Test the :func:`beartype._util.cache.utilcachecall.method_cached_arg_by_id` decorator. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilCallableCachedException from beartype._util.cache.utilcachecall import method_cached_arg_by_id from pytest import raises # ..................{ CLASSES }.................. class LikeDust(object): ''' Arbitrary class containing an arbitrary callable memoized by this decorator. ''' @method_cached_arg_by_id def just_like_moons(self, and_like_suns): ''' Arbitrary callable memoized by this decorator. ''' # If an arbitrary condition, raise an exception whose value depends # on these parameters to exercise this decorator's conditional # caching of exceptions. if len(and_like_suns) == 6: raise ValueError(and_like_suns) # Else, return a value depending on these parameters to exercise # this decorator's conditional caching of return values. return [id(self), and_like_suns] # ..................{ LOCALS }.................. # Instance of this class. like_air = LikeDust() # Hashable objects to be passed as parameters below. moons = ('Just', 'like', 'moons', 'and', 'like', 'suns,') tides = ('With', 'the', 'certainty,', 'of,', 'tides,',) # Shallow copies of hashable objects defined above. # # Note that copying a list in a manner guaranteeing even a shallow copy is # surprisingly non-trivial. See this relevant StackOverflow answer: # https://stackoverflow.com/a/15214661/2809027 tides_copy = tuple(list(tides)) # Unhashable objects to be passed as parameters below. hopes = ['Just', 'like', 'hopes,', 'springing,', 'high',] # ..................{ PASS }.................. # Assert that memoizing two calls passed the same positional arguments # caches and returns the same value. assert ( like_air.just_like_moons(tides) is like_air.just_like_moons(tides)) # Assert that memoizing two calls passed a copy of the above arguments # caches and returns a different value. assert ( like_air.just_like_moons(tides_copy) is not like_air.just_like_moons(tides)) # Assert that memoizing a call expected to raise an exception does so. with raises(ValueError) as exception_first_info: like_air.just_like_moons(moons) # Assert that repeating that call reraises the same exception. with raises(ValueError) as exception_next_info: like_air.just_like_moons(moons) assert exception_first_info.value is exception_next_info.value # Assert that passing one or more unhashable parameters to this callable # succeeds with the expected return value. assert like_air.just_like_moons(hopes) == [id(like_air), hopes] # ..................{ FAIL }.................. # Assert that attempting to memoize a callable accepting *NO* parameters # fails with the expected exception. with raises(_BeartypeUtilCallableCachedException): @method_cached_arg_by_id def into_a_daybreak_thats_wondrously_clear(): return 0 # Assert that attempting to memoize a callable accepting one or more # variadic positional parameters fails with the expected exception. with raises(_BeartypeUtilCallableCachedException): @method_cached_arg_by_id def leaving_behind_nights_of_terror_and_fear(*args): return args def test_property_cached() -> None: ''' Test the :func:`beartype._util.cache.utilcachecall.property_cached` decorator. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype._util.cache.utilcachecall import property_cached # ..................{ CLASSES }.................. class Keeper(object): ''' Arbitrary class defining the property to be cached. ''' def __init__(self, keys: int) -> None: self.keys = keys @property @property_cached def keys_cached(self) -> int: # Property value to be both cached and returned. keys_cached = self.keys * 2 # To detect erroneous attempts to call this property method multiple # times for the same object, modify the object variable from which # this property value derived in a predictable way on each call of # this property method. self.keys /= 2 # Return this property value. return keys_cached # ..................{ LOCALS }.................. # Value of the "Keeper.keys" attribute *BEFORE* invoking keys_cached(). KEY_COUNT_PRECACHED = 7 # Value of the "Keeper.keys" attribute *AFTER* invoking keys_cached(). KEY_COUNT_POSTCACHED = KEY_COUNT_PRECACHED / 2 # Value of the "Keeper.keys_cached" property. KEY_COUNT_CACHED = KEY_COUNT_PRECACHED * 2 # Instance of this class initialized with this value. i_want_out = Keeper(keys=KEY_COUNT_PRECACHED) # ..................{ PASS }.................. # Assert this attribute to be as initialized. assert i_want_out.keys == KEY_COUNT_PRECACHED # Assert this property to be as cached. assert i_want_out.keys_cached == KEY_COUNT_CACHED # Assert this attribute to have been modified by this property call. assert i_want_out.keys == KEY_COUNT_POSTCACHED # Assert this property to still be as cached. assert i_want_out.keys_cached == KEY_COUNT_CACHED # Assert this attribute to *NOT* have been modified again, validating that # the prior property access returned the previously cached value rather than # recalling this property method. assert i_want_out.keys == KEY_COUNT_POSTCACHED beartype-0.18.5/beartype_test/a00_unit/a20_util/a00_cache/test_utilcachemeta.py000066400000000000000000000074651461113517100272700ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype caching metaclass unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.cache.utilcachemeta` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_caching_metaclass() -> None: ''' Test the :func:`beartype._util.cache.utilcachemeta.BeartypeCachingMeta` metaclass. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilCallableCachedException from beartype._util.cache.utilcachemeta import BeartypeCachingMeta from pytest import raises # ..................{ CALLABLES }.................. class FledNotHisThirstingLips(object, metaclass=BeartypeCachingMeta): ''' Arbitrary class whose metaclass is this metaclass. ''' def __init__(self, and_all_of_great, or_good, or_lovely): ''' Arbitrary callable memoized by this decorator. ''' # If an arbitrary condition, raise an exception whose value depends # on these parameters to exercise this metaclass' exception caching. if len(or_lovely) == 6: raise ValueError(or_lovely) # Else, set an instance variable depending on these parameters to # exercise this metaclass' instance caching. self.which_the_sacred_past = and_all_of_great + or_good + or_lovely # ..................{ LOCALS }.................. # Hashable objects to be passed as parameters below. and_all_of_great = ('In', 'truth', 'or', 'fable', 'consecrates,', 'he', 'felt',) or_good = ('And', 'knew.', 'When', 'early', 'youth', 'had', 'past,', 'he', 'left',) or_lovely = ('To', 'seek', 'strange', 'truths', 'in', 'undiscovered', 'lands.',) he_left = ('His', 'cold', 'fireside', 'and', 'alienated', 'home',) # ..................{ ASSERTS ~ pass }.................. # Assert that memoizing two instances passed the same positional arguments # caches and returns the same instance. assert ( FledNotHisThirstingLips(and_all_of_great, or_good, or_lovely) is FledNotHisThirstingLips(and_all_of_great, or_good, or_lovely)) # Assert that memoizing an instance expected to raise an exception does so. with raises(ValueError) as exception_first_info: FledNotHisThirstingLips(and_all_of_great, or_good, he_left) # Assert that repeating that instantiation reraises the same exception. with raises(ValueError) as exception_next_info: FledNotHisThirstingLips(and_all_of_great, or_good, he_left) assert exception_first_info.value is exception_next_info.value # Assert that initializing this class with one or more unhashable parameters # succeeds with the expected instance. and_he_has_bought = FledNotHisThirstingLips( ['Many', 'a', 'wide', 'waste',], ['and', 'tangled', 'wilderness',], ['Has lured his fearless steps;',], ) assert and_he_has_bought.which_the_sacred_past == [ 'Many', 'a', 'wide', 'waste', 'and', 'tangled', 'wilderness', 'Has lured his fearless steps;', ] beartype-0.18.5/beartype_test/a00_unit/a20_util/api/000077500000000000000000000000001461113517100221015ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/api/__init__.py000066400000000000000000000000001461113517100242000ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/api/test_utilapibeartype.py000066400000000000000000000060311461113517100267150ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype-generated wrapper function utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.api.utilapibeartype` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ testers }.................... def test_is_func_beartyped() -> None: ''' Test the :func:`beartype._util.api.utilapibeartype.is_func_beartyped` tester. ''' # Defer test-specific imports. from beartype import beartype from beartype._util.api.utilapibeartype import is_func_beartyped @beartype def where_that_or() -> str: ''' Arbitrary callable decorated by the :func:`beartype.beartype` decorator intentionally annotated by one or more arbitrary unignorable type hints to prevent that decorator from silently reducing to a noop. ''' return 'In the still cave of the witch Poesy,' def thou_art_no_unbidden_guest() -> str: ''' Arbitrary callable *not* decorated by the :func:`beartype.beartype` decorator intentionally annotated by one or more arbitrary unignorable type hints for parity with the prior callable. ''' return 'Seeking among the shadows that pass by' # Assert this tester returns the expected results for these callables. assert is_func_beartyped(where_that_or) is True assert is_func_beartyped(thou_art_no_unbidden_guest) is False # ....................{ TESTS ~ setters }.................... def test_set_func_beartyped() -> None: ''' Test the :func:`beartype._util.api.utilapibeartype.set_func_beartyped` tester. ''' # Defer test-specific imports. from beartype._util.api.utilapibeartype import ( is_func_beartyped, set_func_beartyped, ) def ghosts_of_all_things_that_are() -> str: ''' Arbitrary callable *not* decorated by the :func:`beartype.beartype` decorator intentionally annotated by one or more arbitrary unignorable type hints to prevent that decorator from silently reducing to a noop. ''' return 'some shade of thee,' # Assert this callable to *NOT* be a beartype-decorated wrapper function. assert is_func_beartyped(ghosts_of_all_things_that_are) is False # Declare this callable to be a beartype-decorated wrapper function. set_func_beartyped(ghosts_of_all_things_that_are) # Assert this callable to now be a beartype-decorated wrapper function. assert is_func_beartyped(ghosts_of_all_things_that_are) beartype-0.18.5/beartype_test/a00_unit/a20_util/api/test_utilapicontextlib.py000066400000000000000000000045161461113517100272630ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :mod:`contextlib` utility unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.api.utilapicontextlib` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_is_func_contextlib_contextmanager() -> None: ''' Test the :func:`beartype._util.api.utilapicontextlib.is_func_contextlib_contextmanager` tester. ''' # Defer test-specific imports. from beartype._util.api.utilapicontextlib import ( is_func_contextlib_contextmanager) from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_11 from beartype_test.a00_unit.data.data_type import ( context_manager_factory, decorator_isomorphic, decorator_nonisomorphic, function, ) # Assert this tester either accepts or rejects isomorphic closures created # and returned by the @contextlib.contextmanager decorator conditionally # depending on whether the active Python interpreter targets Python >= 3.11 # or not. assert is_func_contextlib_contextmanager(context_manager_factory) is ( IS_PYTHON_AT_LEAST_3_11) # Assert this tester rejects isomorphic closures *NOT* created and returned # by the @contextlib.contextmanager decorator. assert is_func_contextlib_contextmanager( decorator_isomorphic(function)) is False # Assert this tester rejects non-isomorphic closures. assert is_func_contextlib_contextmanager( decorator_nonisomorphic(function)) is False # Assert this tester rejects non-closure callables. assert is_func_contextlib_contextmanager(function) is False # Assert this tester rejects non-callables. assert is_func_contextlib_contextmanager( 'A lovely youth,—no mourning maiden decked') is False beartype-0.18.5/beartype_test/a00_unit/a20_util/api/test_utilapifunctools.py000066400000000000000000000111501461113517100271140ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :mod:`functools` utility unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.api.utilapifunctools` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ tester }.................... def test_is_func_functools_lru_cache() -> None: ''' Test the :func:`beartype._util.api.utilapifunctools.is_func_functools_lru_cache` tester. ''' # Defer test-specific imports. from beartype._util.api.utilapifunctools import is_func_functools_lru_cache from beartype_test.a00_unit.data.data_type import ( function_lru_cached, function, ) # Assert this tester accepts a @functools.lru_cache-memoized callable. assert is_func_functools_lru_cache(function_lru_cached) is True # Assert this tester rejects a callable *NOT* decorated by that decorator. assert is_func_functools_lru_cache(function) is False # Assert this tester rejects a non-callable *WITHOUT* raising exceptions. assert is_func_functools_lru_cache( 'With weeping flowers, or votive cypress wreath,') is False def test_is_func_functools_partial() -> None: ''' Test the :func:`beartype._util.api.utilapifunctools.is_func_functools_partial` tester. ''' # Defer test-specific imports. from beartype._util.api.utilapifunctools import is_func_functools_partial from beartype_test.a00_unit.data.data_type import ( function, function_partial, ) # Assert this tester accepts a "functools.partial"-wrapped callable. assert is_func_functools_partial(function_partial) is True # Assert this tester rejects a callable *NOT* decorated by that decorator. assert is_func_functools_partial(function) is False # Assert this tester rejects a non-callable *WITHOUT* raising exceptions. assert is_func_functools_partial( 'For well he knew that mighty Shadow loves') is False # ....................{ TESTS ~ getter }.................... def test_get_func_functools_partial_args() -> None: ''' Test the :func:`beartype._util.api.utilapifunctools.get_func_functools_partial_args` getter. ''' # Defer test-specific imports. from beartype._util.api.utilapifunctools import ( get_func_functools_partial_args) from beartype_test.a00_unit.data.data_type import ( FUNCTION_PARTIALIZED_ARG_VALUE, FUNCTION_PARTIALIZED_KWARG_NAME, FUNCTION_PARTIALIZED_KWARG_VALUE, builtin_partial, function_partial, ) # Assert this unwrapper returns the expected 2-tuple providing the # positional and keyword parameters passed to the C-based callable wrapped # by the "functools.partial" class. assert get_func_functools_partial_args(builtin_partial) == ((2,), {}) # Assert this unwrapper returns the expected 2-tuple providing the # positional and keyword parameters passed to the pure-Python callable # wrapped by the "functools.partial" class. assert get_func_functools_partial_args(function_partial) == ( (FUNCTION_PARTIALIZED_ARG_VALUE,), {FUNCTION_PARTIALIZED_KWARG_NAME: FUNCTION_PARTIALIZED_KWARG_VALUE}, ) # ....................{ TESTS ~ tester }.................... def test_unwrap_func_functools_partial_once() -> None: ''' Test the :func:`beartype._util.api.utilapifunctools.unwrap_func_functools_partial_once` unwrapper. ''' # Defer test-specific imports. from beartype._util.api.utilapifunctools import ( unwrap_func_functools_partial_once) from beartype_test.a00_unit.data.data_type import ( builtin_partial, function_partialized, function_partial, ) # Assert this unwrapper returns the expected C-based callable wrapped by # the "functools.partial" class. assert unwrap_func_functools_partial_once(builtin_partial) is divmod # Assert this unwrapper returns the expected pure-Python callable wrapped by # the "functools.partial" class. assert unwrap_func_functools_partial_once(function_partial) is ( function_partialized) beartype-0.18.5/beartype_test/a00_unit/a20_util/api/test_utilapityping.py000066400000000000000000000074261461113517100264250ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **typing module** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.api.utilapityping` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ attr : typing }.................... def test_import_typing_attr() -> None: ''' Test the :func:`beartype._util.api.utilapityping.import_typing_attr` importer. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilModuleException from beartype._util.api.utilapityping import import_typing_attr from beartype._util.module.utilmodimport import import_module_attr_or_sentinel from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_9 from beartype._util.utilobject import SENTINEL from pytest import raises from typing import Union # ....................{ PASS }.................... # Assert that dynamically importing "typing.Union" attribute (guaranteed to # be importable under all supported Python versions) is that attribute. assert import_typing_attr('Union') is Union # ....................{ FAIL }.................... # Assert that attempting to dynamically import a ridiculous attribute # (guaranteed to be unimportable under all supported Python versions, we # swear) raises the expected exception. with raises(_BeartypeUtilModuleException): import_typing_attr('FromMyWingsAreShakenTheDewsThatWaken') # ....................{ VERSION }.................... #FIXME: On retiring Python 3.8, refactor everything below to import a newer #"typing" attribute introduced with a bleeding-edge Python version. # If the active Python interpreter targets Python >= 3.9, the # "typing.Annotated" attribute is guaranteed to exist. In this case... if IS_PYTHON_AT_LEAST_3_9: # Defer Python version-specific imports. from typing import Annotated # Assert that dynamically importing the "typing.Annotated" attribute # (guaranteed to be importable under this Python version) is that # attribute. assert import_typing_attr('Annotated') is Annotated # Else, the active Python interpreter targets Python < 3.9 and the # "typing.Annotated" attribute is guaranteed to *NOT* exist. In this # case... else: # The "typing_extensions.Annotated" attribute if the third-party # "typing_extensions" module is both installed and declares that # attribute *OR* "None" otherwise. typing_extensions_annotated = import_module_attr_or_sentinel( 'typing_extensions.Annotated') # If that attribute exists... if typing_extensions_annotated is not SENTINEL: # Assert that dynamically importing the # "typing_extensions.Annotated" attribute (guaranteed to be # importable by this condition) is that attribute. assert import_typing_attr('Annotated') is ( typing_extensions_annotated) # Else, safely reduce to a noop. This should typically *NEVER* happen, # as all sane versions of that module declare that attribute. *shrug* beartype-0.18.5/beartype_test/a00_unit/a20_util/ast/000077500000000000000000000000001461113517100221175ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/ast/__init__.py000066400000000000000000000000001461113517100242160ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/ast/test_utilastmake.py000066400000000000000000000052661461113517100260640ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **abstract syntax tree (AST) factory utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.ast.utilastmake` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from pytest import fixture # ....................{ FIXTURES }.................... @fixture(scope='session') def node_sibling() -> 'ast.AST': ''' Session-scoped fixture yielding an arbitrary **abstract syntax tree (AST) sibling node** (i.e., node intended to be passed as the ``node_sibling`` keyword parameter to various AST-centric callables defined throughout the :mod:`beartype` codebase). ''' # Defer fixture-specific imports. from ast import Constant # Yield an arbitrary node sibling, defined purely for simplicity as a string # literal node whose string is an arbitrary Python identifier. yield Constant('In_folds_of_the_green_serpent') # ....................{ TESTS ~ factory }.................... def test_make_node_object_attr_load(node_sibling: 'ast.AST') -> None: ''' Test the :func:`beartype._util.ast.utilastmake.make_node_object_attr_load` factory. Parameters ---------- node_sibling : 'ast.AST' Arbitrary sibling node to be passed as the ``node_sibling`` parameter. ''' # Defer test-specific imports. from beartype.roar import BeartypeClawImportAstException from beartype._util.ast.utilastmake import make_node_object_attr_load from pytest import raises # Assert that this factory raises the expected exception when passed neither # the "node_obj" nor "obj_name" parameters. with raises(BeartypeClawImportAstException): make_node_object_attr_load( attr_name='Burn_with_the_poison', node_sibling=node_sibling, ) # Assert that this factory raises the expected exception when passed both # the "node_obj" and "obj_name" parameters. with raises(BeartypeClawImportAstException): make_node_object_attr_load( attr_name='Burn_with_the_poison', node_sibling=node_sibling, node_obj=node_sibling, obj_name='and_precipitates', ) beartype-0.18.5/beartype_test/a00_unit/a20_util/ast/test_utilasttest.py000066400000000000000000000100141461113517100261110ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **abstract syntax tree (AST) test utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.ast.utilasttest` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_is_node_callable_typed() -> None: ''' Test the :func:`beartype._util.ast.utilasttest.is_node_callable_typed` factory. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype._util.ast.utilastget import get_code_child_node from beartype._util.ast.utilasttest import is_node_callable_typed # ....................{ LOCALS }.................... # Tuple of 2-tuples "(is_node_callable_typed_bool, func_code)" describing # example callables to be tested below, where: # * "is_node_callable_typed_bool" is the boolean value expected to be # returned by the is_node_callable_typed() tester when passed the # "ast.Callable" AST tree parsed from the "func_code" item. # * "func_code" is a triple-quoted string defining an arbitrary callable to # be parsed into the "ast.Callable" AST tree to be passed to the # is_node_callable_typed() tester. CALLABLE_DATAS = ( # Untyped function accepting arbitrary parameters. (False, '''def _(a, b, /, c, d, *args, e, f, **kwargs): ...''',), # Typed function with an annotated return and unannotated parameters. (True, '''def _(a, b, /, c, d, *args, e, f, **kwargs) -> None: ...''',), # Typed function with an annotated non-variadic flexible parameter, # other unannotated parameters, and an unannotated return. (True, '''def _(a, b: int, /, c, d, *args, e, f, **kwargs): ...''',), # Typed function with an annotated non-variadic keyword-only parameter # defined implicitly, other unannotated parameters, and an unannotated # return. (True, '''def _(a, b, /, c, d, *args, e, f: bool, **kwargs): ...''',), # Typed function with an annotated non-variadic keyword-only parameter # defined explicitly, other unannotated parameters, and an unannotated # return. (True, '''def _(a, b, /, c, d, *, e, f: bool, **kwargs): ...''',), # Typed function with an annotated non-variadic positional-only # parameter defined explicitly, other unannotated parameters, and an # unannotated return. (True, '''def _(a, b, /, c, d: float, *, d, e, **kwargs): ...''',), # Typed function with an annotated positional variadic argument # parameter, other unannotated parameters, and an unannotated return. (True, '''def _(a, b, /, c, d, *args: str, e, f, **kwargs): ...''',), # Typed function with an annotated keyword variadic argument # parameter, other unannotated parameters, and an unannotated return. (True, '''def _(a, b, /, c, d, *args, e, f, **kwargs: bytes): ...''',), ) # ....................{ ASSERTS }.................... # For each example callable to be tested... for is_node_callable_typed_bool, func_code in CALLABLE_DATAS: # "ast.Callable" AST tree parsed from the string defining this callable. func_node = get_code_child_node(func_code) # Assert that this tester reports the expected boolean when passed this # "ast.Callable" AST tree. assert is_node_callable_typed(func_node) is is_node_callable_typed_bool beartype-0.18.5/beartype_test/a00_unit/a20_util/cls/000077500000000000000000000000001461113517100221115ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/cls/__init__.py000066400000000000000000000000001461113517100242100ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/cls/pep/000077500000000000000000000000001461113517100226755ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/cls/pep/__init__.py000066400000000000000000000000001461113517100247740ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/cls/pep/test_utilpep3119.py000066400000000000000000000200111461113517100263000ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`3119`-compliant **class-specific utility function** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.cls.pep.utilpep3119` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ raiser : isinstanceable }.................... def test_die_unless_object_isinstanceable() -> None: ''' Test the :func:`beartype._util.cls.pep.utilpep3119.die_unless_object_isinstanceable` validator. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar import BeartypeDecorHintPep3119Exception from beartype._util.cls.pep.utilpep3119 import ( die_unless_object_isinstanceable) from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_10 from beartype_test.a00_unit.data.data_type import ( Class, NonIsinstanceableClass, ) from pytest import raises # ....................{ PASS }.................... # Assert this raiser accepts an isinstanceable type. die_unless_object_isinstanceable(Class) # Assert this raiser accepts a tuple of isinstanceable types. die_unless_object_isinstanceable((Class, str)) # ....................{ FAIL }.................... # Assert this raiser raises the expected exception when passed a # non-isinstanceable object. with raises(BeartypeDecorHintPep3119Exception): die_unless_object_isinstanceable('Moab.') # Assert this raiser raises the expected exception when passed a # non-isinstanceable type. with raises(BeartypeDecorHintPep3119Exception): die_unless_object_isinstanceable(NonIsinstanceableClass) # Assert this raiser raises the expected exception when passed a tuple of # one isinstanceable type and one non-isinstanceable type. with raises(BeartypeDecorHintPep3119Exception): die_unless_object_isinstanceable((Class, NonIsinstanceableClass)) # ....................{ VERSION }.................... # If the active Python interpreter targets Python >= 3.10 and thus supports # PEP 604-compliant new unions... if IS_PYTHON_AT_LEAST_3_10: # Assert this raiser accepts a new union of isinstanceable types. die_unless_object_isinstanceable(Class | str) # Assert this raiser raises the expected exception when passed a new # union of one isinstanceable type and one non-isinstanceable type. with raises(BeartypeDecorHintPep3119Exception): die_unless_object_isinstanceable(Class | NonIsinstanceableClass) def test_die_unless_type_isinstanceable() -> None: ''' Test the :func:`beartype._util.cls.pep.utilpep3119.die_unless_type_isinstanceable` validator. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar import BeartypeDecorHintPep3119Exception from beartype._util.cls.pep.utilpep3119 import ( die_unless_type_isinstanceable) from beartype_test.a00_unit.data.data_type import ( Class, NonIsinstanceableClass, ) from pytest import raises # ....................{ PASS }.................... # Assert this validator accepts an isinstanceable type. die_unless_type_isinstanceable(Class) # ....................{ FAIL }.................... # Assert this validator raises the expected exception when passed a # non-isinstanceable type. with raises(BeartypeDecorHintPep3119Exception): die_unless_type_isinstanceable(NonIsinstanceableClass) # Assert this validator raises the expected exception when passed a # non-type. with raises(BeartypeDecorHintPep3119Exception): die_unless_type_isinstanceable('Moab.') # ....................{ TESTS ~ raiser : issubclassable }.................... def test_die_unless_object_issubclassable() -> None: ''' Test the :func:`beartype._util.cls.pep.utilpep3119.die_unless_object_issubclassable` validator. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar import BeartypeDecorHintPep3119Exception from beartype._util.cls.pep.utilpep3119 import ( die_unless_object_issubclassable) from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_10 from beartype_test.a00_unit.data.data_type import ( Class, NonIssubclassableClass, ) from pytest import raises # ....................{ PASS }.................... # Assert this raiser accepts an issubclassable type. die_unless_object_issubclassable(Class) # Assert this raiser accepts a tuple of issubclassable types. die_unless_object_issubclassable((Class, str)) # ....................{ FAIL }.................... # Assert this raiser raises the expected exception when passed a # non-issubclassable object. with raises(BeartypeDecorHintPep3119Exception): die_unless_object_issubclassable('Moab.') # Assert this raiser raises the expected exception when passed a # non-issubclassable type. with raises(BeartypeDecorHintPep3119Exception): die_unless_object_issubclassable(NonIssubclassableClass) # Assert this raiser raises the expected exception when passed a tuple of # one issubclassable type and one non-issubclassable type. with raises(BeartypeDecorHintPep3119Exception): die_unless_object_issubclassable((Class, NonIssubclassableClass)) # ....................{ VERSION }.................... # If the active Python interpreter targets Python >= 3.10 and thus supports # PEP 604-compliant new unions... if IS_PYTHON_AT_LEAST_3_10: # Assert this raiser accepts a new union of issubclassable types. die_unless_object_issubclassable(Class | str) # Assert this raiser raises the expected exception when passed a new # union of one issubclassable type and one non-issubclassable type. with raises(BeartypeDecorHintPep3119Exception): die_unless_object_issubclassable(Class | NonIssubclassableClass) def test_die_unless_type_issubclassable() -> None: ''' Test the :func:`beartype._util.cls.pep.utilpep3119.die_unless_type_issubclassable` validator. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar import BeartypeDecorHintPep3119Exception from beartype._util.cls.pep.utilpep3119 import ( die_unless_type_issubclassable) from beartype_test.a00_unit.data.data_type import ( Class, NonIssubclassableClass, ) from pytest import raises # ....................{ PASS }.................... # Assert this validator accepts an issubclassable type. die_unless_type_issubclassable(Class) # ....................{ FAIL }.................... # Assert this validator raises the expected exception when passed a # non-issubclassable type. with raises(BeartypeDecorHintPep3119Exception): die_unless_type_issubclassable(NonIssubclassableClass) # Assert this validator raises the expected exception when passed a # non-type. with raises(BeartypeDecorHintPep3119Exception): die_unless_type_issubclassable('Moab.') beartype-0.18.5/beartype_test/a00_unit/a20_util/cls/pep/test_utilpep557.py000066400000000000000000000040651461113517100262360ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`557`-compliant **class-specific utility function** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.cls.pep.utilpep557` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_is_type_pep557() -> None: ''' Test the :func:`beartype._util.cls.pep.utilpep557.is_type_pep557` tester. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilTypeException from beartype._util.cls.pep.utilpep557 import is_type_pep557 from dataclasses import dataclass from pytest import raises # ....................{ CLASSES }.................... @dataclass class WhichTeachesAwfulDoubtOrFaithSoMild(object): ''' Arbitrary dataclass. ''' pass # ....................{ PASS }.................... # Assert this tester returns true when passed a dataclass type. assert is_type_pep557(WhichTeachesAwfulDoubtOrFaithSoMild) is True # Assert this tester returns false when passed a non-dataclass type. assert is_type_pep557(str) is False # ....................{ FAIL }.................... # Assert this tester raises the expected exception when passed a non-type. with raises(_BeartypeUtilTypeException): is_type_pep557('The wilderness has a mysterious tongue') beartype-0.18.5/beartype_test/a00_unit/a20_util/cls/test_utilclsget.py000066400000000000000000000031761461113517100257100ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **class getter** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.cls.utilclsget` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ getter }.................... def test_get_type_filename_or_none() -> None: ''' Test the :func:`beartype._util.cls.utilclsget.get_type_filename_or_none` getter. ''' # Defer test-specific imports. from beartype._util.cls.utilclsget import get_type_filename_or_none from beartype_test.a00_unit.data.data_type import ( Class, ClassModuleNameNone, ClassModuleNameFake, ) # Filename of a class declared on-disk. type_filename = get_type_filename_or_none(Class) # Assert this filename is that of the expected submodule. assert isinstance(type_filename, str) assert 'data_type' in type_filename # Assert this getter returns "None" for classes with either missing or # non-existent module names. assert get_type_filename_or_none(ClassModuleNameNone) is None assert get_type_filename_or_none(ClassModuleNameFake) is None beartype-0.18.5/beartype_test/a00_unit/a20_util/cls/test_utilclsmake.py000066400000000000000000000105171461113517100260430ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **class factory** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.cls.utilclsmake` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_make_type() -> None: ''' Test the :func:`beartype._util.cls.utilclsmake.make_type` tester. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilTypeException from beartype._util.cls.utilclsmake import make_type from beartype._util.cls.utilclstest import is_type_subclass from pytest import raises # ....................{ LOCALS }.................... class TooEnamouredOfThatVoice(object): ''' Arbitrary base class from which to subclass classes defined below. ''' # ....................{ PASS }.................... # Assert this factory creates and returns the expected class when the passed # name is a valid unqualified Python identifier. AndSilence = make_type(type_name='AndSilence') assert isinstance(AndSilence, type) assert AndSilence.__name__ == 'AndSilence' # Assert this factory creates and returns the expected class when passed a # tuple of one or more base classes. BySolemnVision = make_type( type_name='BySolemnVision', type_bases=(TooEnamouredOfThatVoice,)) assert is_type_subclass(BySolemnVision, TooEnamouredOfThatVoice) # Assert this factory creates and returns the expected class when passed a # dictionary of one or more class attributes. BrightSilverDream = make_type(type_name='BrightSilverDream', type_scope={ 'every_sight': 'His infancy was nurtured.', 'and_sound': lambda self: self.every_sight, }) assert ( BrightSilverDream.every_sight == BrightSilverDream().and_sound() == 'His infancy was nurtured.' ) # Assert this factory creates and returns the expected class when the passed # module name is a valid Python identifier. SentToHisHeart = make_type( type_name='SentToHisHeart', type_module_name='its_choicest.impulses') assert SentToHisHeart.__module__ == 'its_choicest.impulses' # Assert this factory creates and returns the expected class when passed a # docstring. TheFountains = make_type( type_name='TheFountains', type_doc='of divine philosophy') assert TheFountains.__doc__ == 'of divine philosophy' # ....................{ FAIL }.................... # Assert this factory raises the expected exception when the passed name is # the empty string. with raises(_BeartypeUtilTypeException): make_type('') # Assert this factory raises the expected exception when the passed name is # *NOT* a valid Python identifier. with raises(_BeartypeUtilTypeException): make_type('And wasted for fond love of his wild eyes.') # Assert this factory raises the expected exception when the passed name is # a valid Python identifier that is fully-qualified (i.e., contains one or # more "." characters). with raises(_BeartypeUtilTypeException): make_type('The_fire.of.those_soft_orbs.has_ceased.to_burn') # Assert this factory raises the expected exception when the passed module # name is the empty string. with raises(_BeartypeUtilTypeException): make_type(type_name='And_Silence', type_module_name='') # Assert this factory raises the expected exception when the passed module # name is *NOT* a valid Python identifier. with raises(_BeartypeUtilTypeException): make_type( type_name='And_Silence', type_module_name='Locks its mute music in her rugged cell.', ) beartype-0.18.5/beartype_test/a00_unit/a20_util/cls/test_utilclstest.py000066400000000000000000000104571461113517100261100ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **class tester** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.cls.utilclstest` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ tester }.................... def test_is_type_or_types() -> None: ''' Test the :func:`beartype._util.cls.utilclstest.is_type_or_types` tester. ''' # Defer test-specific imports. from beartype._util.cls.utilclstest import is_type_or_types # Assert this tester accepts an arbitrary type. assert is_type_or_types(str) is True # Assert this tester accepts an arbitrary non-empty tuple of types. assert is_type_or_types((bool, int)) is True # Assert this tester rejects an arbitrary object that is neither a type # nor a non-empty tuple of types. assert is_type_or_types( 'To drink their odours, and their mighty swinging') is False # Assert this tester rejects the empty tuple. assert is_type_or_types(()) is False # Assert this tester rejects an arbitrary non-empty tuple containing one or # more non-types. assert is_type_or_types( (dict, 'To hear—an old and solemn harmony;', float)) is False def test_is_type_subclass() -> None: ''' Test the :func:`beartype._util.cls.utilclstest.is_type_subclass` tester. ''' # Defer test-specific imports. from beartype._util.cls.utilclstest import is_type_subclass from beartype_test.a00_unit.data.data_type import Class, Subclass # Assert this tester accepts objects that are subclasses of superclasses. assert is_type_subclass(Subclass, Class) is True assert is_type_subclass(Class, object) is True assert is_type_subclass(Class, (Class, str)) is True # Assert this tester rejects objects that are superclasses of subclasses. assert is_type_subclass(object, Class) is False assert is_type_subclass(Class, Subclass) is False assert is_type_subclass(Class, (Subclass, str)) is False # Assert this tester rejects objects that are unrelated classes. assert is_type_subclass(str, bool) is False # Assert this tester rejects objects that are non-classes *WITHOUT* raising # an exception. assert is_type_subclass( "Thou many-colour'd, many-voiced vale,", str) is False # ....................{ TESTS ~ tester : builtin }.................... def test_is_type_builtin() -> None: ''' Test the :func:`beartype._util.cls.utilclstest.is_type_builtin` tester. ''' # Defer test-specific imports. from beartype._util.cls.utilclstest import is_type_builtin from beartype_test.a00_unit.data.data_type import ( TYPES_BUILTIN, TYPES_NONBUILTIN, ) # Assert this tester accepts all builtin types. for type_builtin in TYPES_BUILTIN: assert is_type_builtin(type_builtin) is True # Assert this tester rejects non-builtin types. for type_nonbuiltin in TYPES_NONBUILTIN: assert is_type_builtin(type_nonbuiltin) is False def test_is_type_builtin_or_fake() -> None: ''' Test the :func:`beartype._util.cls.utilclstest.is_type_builtin_or_fake` tester. ''' # Defer test-specific imports. from beartype._util.cls.utilclstest import is_type_builtin_or_fake from beartype_test.a00_unit.data.data_type import ( TYPES_BUILTIN, TYPES_BUILTIN_FAKE, Class, ) # Assert this tester accepts all non-fake builtin types. for type_builtin in TYPES_BUILTIN: assert is_type_builtin_or_fake(type_builtin) is True # Assert this tester accepts all fake builtin types, too. for type_builtin_fake in TYPES_BUILTIN_FAKE: assert is_type_builtin_or_fake(type_builtin_fake) is True # Assert this tester rejects an arbitrary non-builtin type. assert is_type_builtin_or_fake(Class) is False beartype-0.18.5/beartype_test/a00_unit/a20_util/error/000077500000000000000000000000001461113517100224615ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/error/__init__.py000066400000000000000000000000001461113517100245600ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/error/test_utilerrget.py000066400000000000000000000046111461113517100262620ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype exception getter utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.error.utilerrget` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To get human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_get_name_error_attr_name() -> None: ''' Test the :func:`beartype._util.error.utilerrget.get_name_error_attr_name` getter. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype._util.error.utilerrget import get_name_error_attr_name # ....................{ ASSERT }.................... # Attempt to access an attribute *NEVER* defined in either the global or # local scopes of this unit test. try: undefined_attr # When doing so necessarily raises the standard "NameError" exception... except NameError as exception: # Assert that the unqualified basename of this undefined attribute # returned by this getter is the expected basename. assert get_name_error_attr_name(exception) == 'undefined_attr' # Attempt to access a currently undefined free attribute (i.e., local # attribute subsequently defined in the local scope of this unit test). try: FreeAttr # When doing so necessarily raises the standard "NameError" exception... except NameError as exception: # Assert that the unqualified basename of this currently undefined free # attribute returned by this getter is the expected basename. assert get_name_error_attr_name(exception) == 'FreeAttr' # ....................{ ATTRIBUTES }.................... class FreeAttr(object): ''' Arbitrary class whose name is that of a previously undefined free attribute referenced above. ''' pass beartype-0.18.5/beartype_test/a00_unit/a20_util/error/test_utilerrraise.py000066400000000000000000000121631461113517100266070ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype exception raiser utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.error.utilerrraise` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_reraise_exception_placeholder() -> None: ''' Test the :func:`beartype._util.error.utilerrraise.reraise_exception_placeholder` function. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype._util.cache.utilcachecall import callable_cached from beartype._util.error.utilerrraise import reraise_exception_placeholder from pytest import raises from random import getrandbits # ....................{ CLASSES }.................... class CachedException(ValueError): ''' Test-specific exception intended to be raised below with messages containing placeholder substrings replaced by the :func:`.reraise_exception_placeholder` re-raiser. ''' pass # ....................{ LOCALS }.................... # Source substring to be hard-coded into the messages of all exceptions # raised by the low-level memoized callable defined below. TEST_SOURCE_STR = '{its_got_bro}' # ....................{ CALLABLES }.................... @callable_cached def portend_low_level_winter(is_winter_coming: bool) -> str: ''' Low-level memoized callable raising unreadable exceptions conditionally depending on the value of the passed parameter. ''' if is_winter_coming: raise CachedException( f'{TEST_SOURCE_STR} intimates that winter is coming.') else: return 'PRAISE THE SUN' def portend_high_level_winter() -> None: ''' High-level non-memoized callable calling the low-level memoized callable and reraising unreadable exceptions raised by the latter with equivalent readable exceptions. ''' try: # Call the low-level memoized callable without raising exceptions. print(portend_low_level_winter(False)) # Call the low-level memoized callable with raising exceptions. print(portend_low_level_winter(True)) except CachedException as exception: # print('exception.args: {!r} ({!r})'.format(exception.args, type(exception.args))) reraise_exception_placeholder( exception=exception, source_str=TEST_SOURCE_STR, target_str=( 'Random "Song of Fire and Ice" spoiler' if getrandbits(1) else 'Random "Dark Souls" plaintext meme' ), ) # ....................{ ASSERT }.................... # Assert this high-level non-memoized callable raises the same type of # exception raised by this low-level memoized callable and preserve this # exception for subsequent assertion. with raises(CachedException) as exception_info: portend_high_level_winter() # Assert that this exception message does *NOT* contain the unreadable # source substring hard-coded into the messages of all exceptions raised by # this low-level memoized callable. assert TEST_SOURCE_STR not in str(exception_info.value) # Assert that exception messages may also contain *NO* source substrings. try: raise CachedException( "What's bravery without a dash of recklessness?") except Exception as exception: with raises(CachedException): reraise_exception_placeholder( exception=exception, source_str=TEST_SOURCE_STR, target_str='and man sees not light,', ) # Assert that exception messages may be empty. try: raise CachedException() except Exception as exception: with raises(CachedException): reraise_exception_placeholder( exception=exception, source_str=TEST_SOURCE_STR, target_str='but only endless nights.', ) # Assert that exception messages need *NOT* be strings. try: raise CachedException(0xDEADBEEF) except Exception as exception: with raises(CachedException): reraise_exception_placeholder( exception=exception, source_str=TEST_SOURCE_STR, target_str='Rise if you would, for that is our curse.', ) beartype-0.18.5/beartype_test/a00_unit/a20_util/error/test_utilerrwarn.py000066400000000000000000000131201461113517100264450ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype exception raiser utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.error.utilerrwarn` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_reissue_warnings_placeholder() -> None: ''' Test the :func:`beartype._util.error.utilerrwarn.reissue_warnings_placeholder` function. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype._util.error.utilerrwarn import ( issue_warning, reissue_warnings_placeholder, ) from pytest import warns from warnings import catch_warnings # ....................{ CLASSES }.................... class TheSkyWarning(UserWarning): ''' Arbitrary warning issued below with messages containing placeholder substrings replaced by :func:`.reissue_warnings_placeholder`. ''' pass class ThePoetWarning(UserWarning): ''' Arbitrary warning issued below with messages containing placeholder substrings replaced by :func:`.reissue_warnings_placeholder`. ''' pass # ....................{ LOCALS }.................... # Source substring to be hard-coded into the messages of all warnings # issued by the low-level callable defined below. TEST_SOURCE_STR = '{its_got_bro}' # Target substring to globally replace this source substring with. TEST_TARGET_STR = 'With his still soul. ' # ....................{ CALLABLES }.................... def portend_his_brain() -> None: ''' Low-level callable issuing two or more possibly unreadable warnings. ''' # Issue an arbitrary warning message containing one source substring. issue_warning( cls=TheSkyWarning, message=f'{TEST_SOURCE_STR}The sky', ) # Issue an arbitrary warning message containing *NO* source substring. issue_warning( cls=ThePoetWarning, message='the Poet kept mute conference', ) # Issue an empty warning message. issue_warning(cls=TheSkyWarning, message='') # Issue a non-string warning message. While an unlikely edge case, the # standard low-level warnings.warn() function permissively permits this. # Moreover, this behaviour coincides with similar permissiveness in # exception messages (which are also allowed to be non-strings): e.g., # >>> from warnings import warn # >>> warn(0xCAFE, UserWarning) # UserWarning: 51966 # warn(0xCAFE, UserWarning) issue_warning(cls=ThePoetWarning, message=0xBEEFDEAD) def portend_while_daylight() -> None: ''' High-level callable calling the low-level callable and reissuing unreadable warnings issued by the latter with equivalent readable warnings. ''' # With a context manager "catching" *ALL* non-fatal warnings emitted # during this logic for subsequent "playrback" below... with catch_warnings(record=True) as warnings_issued: # Call this low-level callable to emit multiple warnings. portend_his_brain() # If one or more warnings were issued, reissue these warnings with each # placeholder substring replaced by an arbitrary substring. if warnings_issued: reissue_warnings_placeholder( warnings=warnings_issued, source_str=TEST_SOURCE_STR, target_str=TEST_TARGET_STR, ) # Else, *NO* warnings were issued. # ....................{ ASSERT }.................... # Assert this high-level non-memoized callable raises the same type of # exception raised by this low-level memoized callable and preserve this # exception for subsequent assertion. # # Note that passing nothing to warns() enables undocumented edge-case # behaviour in which warns() captures warnings issued by this block # *WITHOUT* validating the types of these warnings. See also: # https://docs.pytest.org/en/4.6.x/warnings.html#recwarn with warns() as warnings_info: portend_while_daylight() # Assert that the expected number of warnings were issued. assert len(warnings_info) == 4 # Assert that the expected types of warnings were issued. assert issubclass(warnings_info[0].category, TheSkyWarning) assert issubclass(warnings_info[1].category, ThePoetWarning) assert issubclass(warnings_info[2].category, TheSkyWarning) assert issubclass(warnings_info[3].category, ThePoetWarning) # Assert that the expected warning messages were issued. assert str(warnings_info[0].message) == f'{TEST_TARGET_STR}The sky' assert str(warnings_info[1].message) == 'the Poet kept mute conference' assert str(warnings_info[2].message) == '' assert warnings_info[3].message.args == (0xBEEFDEAD,) beartype-0.18.5/beartype_test/a00_unit/a20_util/func/000077500000000000000000000000001461113517100222635ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/func/__init__.py000066400000000000000000000000001461113517100243620ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/func/arg/000077500000000000000000000000001461113517100230345ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/func/arg/__init__.py000066400000000000000000000000001461113517100251330ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/func/arg/test_utilfuncargget.py000066400000000000000000000134651461113517100275010ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Callable parameter getter utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.func.arg.utilfuncargget` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ arg }.................... def test_get_func_arg_first_name_or_none() -> None: ''' Test the :func:`beartype._util.func.arg.utilfuncargget.get_func_arg_first_name_or_none` getter. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilCallableException from beartype._util.func.arg.utilfuncargget import ( get_func_arg_first_name_or_none) from beartype_test.a00_unit.data.func.data_func import ( func_args_0_wrapped, func_args_1_varpos_wrapped, func_args_3_flex_mandatory_optional_varkw_wrapped, ) from pytest import raises # ....................{ PASS }.................... # Assert this getter returns "None" for argumentless callables. assert get_func_arg_first_name_or_none(func_args_0_wrapped) is None # Assert this getter returns the expected names of the first parameters # accepted by argumentative callables. assert get_func_arg_first_name_or_none(func_args_1_varpos_wrapped) == ( 'and_in_her_his_one_delight') assert get_func_arg_first_name_or_none( func_args_3_flex_mandatory_optional_varkw_wrapped) == ( 'and_the_wolf_tracks_her_there') # ....................{ FAIL }.................... # Assert this getter raises the expected exception when passed a C-based # callable. with raises(_BeartypeUtilCallableException): get_func_arg_first_name_or_none(iter) # ....................{ TESTS ~ args }.................... def test_get_func_args_len_flexible() -> None: ''' Test the :func:`beartype._util.func.arg.utilfuncargget.get_func_args_flexible_len` getter. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilCallableException from beartype._util.func.arg.utilfuncargget import ( get_func_args_flexible_len) from beartype_test.a00_unit.data.data_type import ( CallableClass, function_partial, function_partial_bad, ) from beartype_test.a00_unit.data.func.data_func import ( func_args_0, func_args_1_flex_mandatory, func_args_1_varpos, func_args_2_flex_mandatory, func_args_0_wrapped, func_args_1_flex_mandatory_wrapped, func_args_1_varpos_wrapped, func_args_2_flex_mandatory_wrapped, ) from pytest import raises # ....................{ LOCALS }.................... # Callable object whose class defines the __call__() dunder method. callable_object = CallableClass() # ....................{ PASS }.................... # Assert this getter returns the expected lengths of unwrapped callables. assert get_func_args_flexible_len(func_args_0) == 0 assert get_func_args_flexible_len(func_args_1_varpos) == 0 assert get_func_args_flexible_len(func_args_1_flex_mandatory) == 1 assert get_func_args_flexible_len(func_args_2_flex_mandatory) == 2 # Assert this getter returns the expected lengths of wrapped callables. assert get_func_args_flexible_len(func_args_0_wrapped) == 0 assert get_func_args_flexible_len(func_args_1_varpos_wrapped) == 0 assert get_func_args_flexible_len(func_args_1_flex_mandatory_wrapped) == 1 assert get_func_args_flexible_len(func_args_2_flex_mandatory_wrapped) == 2 # Assert this getter returns the expected length of a partial callable. assert get_func_args_flexible_len(function_partial) == 0 # Assert this getter returns the expected length of a callable object. assert get_func_args_flexible_len(callable_object) == 0 # Assert this getter returns 0 when passed a wrapped callable and an option # disabling callable unwrapping. assert get_func_args_flexible_len( func_args_0_wrapped, is_unwrap=False) == 0 assert get_func_args_flexible_len( func_args_1_varpos_wrapped, is_unwrap=False) == 0 assert get_func_args_flexible_len( func_args_1_flex_mandatory_wrapped, is_unwrap=False) == 0 assert get_func_args_flexible_len( func_args_2_flex_mandatory_wrapped, is_unwrap=False) == 0 # ....................{ FAIL }.................... # Assert this getter raises the expected exception when passed an uncallable # object. with raises(_BeartypeUtilCallableException): get_func_args_flexible_len('Following his eager soul, the wanderer') # Assert this getter raises the expected exception when passed a C-based # callable. with raises(_BeartypeUtilCallableException): get_func_args_flexible_len(iter) # Assert this getter raises the expected exception when passed an invalid # partial callable passing more parameters than the underlying function # actually accepts. with raises(_BeartypeUtilCallableException): get_func_args_flexible_len(function_partial_bad) beartype-0.18.5/beartype_test/a00_unit/a20_util/func/arg/test_utilfuncargiter.py000066400000000000000000000131311461113517100276530ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **callable argument iterator utility** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.utilfunc.arg.utilfuncargiter` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ iterator }.................... def test_iter_func_args() -> None: ''' Test the :func:`beartype._util.func.arg.utilfuncargtest.iter_func_args` generator. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilCallableException from beartype._util.func.arg.utilfuncargiter import ( ArgKind, ArgMandatory, iter_func_args, ) from beartype_test.a00_unit.data.func.data_func import ( func_args_0, func_args_1_flex_mandatory, func_args_1_varpos, func_args_1_kwonly_mandatory, func_args_2_flex_mandatory, func_args_2_kwonly_mixed, func_args_3_flex_mandatory_optional_varkw, func_args_5_flex_mandatory_varpos_kwonly_varkw, ) from beartype_test.a00_unit.data.func.data_pep570 import ( func_args_2_posonly_mixed, func_args_10_all_except_flex_mandatory, ) from pytest import raises # ....................{ PASS }.................... # Assert this iterator returns the empty generator for an argumentless # callable, explicitly coerced into a tuple to trivialize testing. assert len(tuple(iter_func_args(func_args_0))) == 0 # Assert this iterator returns the expected generator for argumentative # callables accepting multiple kinds of parameters, explicitly coerced into # tuples to trivialize testing. assert tuple(iter_func_args(func_args_1_flex_mandatory)) == ( (ArgKind.POSITIONAL_OR_KEYWORD, 'had_one_fair_daughter', ArgMandatory,), ) assert tuple(iter_func_args(func_args_1_varpos)) == ( (ArgKind.VAR_POSITIONAL, 'and_in_her_his_one_delight', ArgMandatory,), ) assert tuple(iter_func_args(func_args_1_kwonly_mandatory)) == ( (ArgKind.KEYWORD_ONLY, 'when_can_I_take_you_from_this_place', ArgMandatory,), ) assert tuple(iter_func_args(func_args_2_flex_mandatory)) == ( (ArgKind.POSITIONAL_OR_KEYWORD, 'thick_with_wet_woods', ArgMandatory,), (ArgKind.POSITIONAL_OR_KEYWORD, 'and_many_a_beast_therein', ArgMandatory,), ) assert tuple(iter_func_args(func_args_2_kwonly_mixed)) == ( (ArgKind.KEYWORD_ONLY, 'white_summer', 'So far I have gone to see you again.',), (ArgKind.KEYWORD_ONLY, 'hiding_your_face_in_the_palm_of_your_hands', ArgMandatory,), ) assert tuple(iter_func_args(func_args_3_flex_mandatory_optional_varkw)) == ( (ArgKind.POSITIONAL_OR_KEYWORD, 'and_the_wolf_tracks_her_there', ArgMandatory,), (ArgKind.POSITIONAL_OR_KEYWORD, 'how_hideously', "Its shapes are heap'd around!",), (ArgKind.VAR_KEYWORD, 'rude_bare_and_high', ArgMandatory,), ) assert tuple(iter_func_args(func_args_5_flex_mandatory_varpos_kwonly_varkw)) == ( (ArgKind.POSITIONAL_OR_KEYWORD, 'we_are_selfish_men', ArgMandatory,), (ArgKind.POSITIONAL_OR_KEYWORD, 'oh_raise_us_up', ArgMandatory,), (ArgKind.VAR_POSITIONAL, 'and_give_us', ArgMandatory,), (ArgKind.KEYWORD_ONLY, 'return_to_us_again', 'Of inward happiness.',), (ArgKind.VAR_KEYWORD, 'manners_virtue_freedom_power', ArgMandatory,), ) # ....................{ PASS ~ positional-only }.................... # Assert this iterator returns the expected generator for argumentative # callables accepting multiple kinds of parameters -- including # positional-only parameters. assert tuple(iter_func_args(func_args_2_posonly_mixed)) == ( (ArgKind.POSITIONAL_ONLY, 'before_spreading_his_black_wings', ArgMandatory,), (ArgKind.POSITIONAL_ONLY, 'reaching_for_the_skies', 'in this forest',), ) assert tuple(iter_func_args(func_args_10_all_except_flex_mandatory)) == ( (ArgKind.POSITIONAL_ONLY, 'in_solitude_i_wander', ArgMandatory,), (ArgKind.POSITIONAL_ONLY, 'through_the_vast_enchanted_forest', ArgMandatory,), (ArgKind.POSITIONAL_ONLY, 'the_surrounding_skies', 'are one',), (ArgKind.POSITIONAL_OR_KEYWORD, 'torn_apart_by', 'the phenomenon of lightning',), (ArgKind.POSITIONAL_OR_KEYWORD, 'rain_is_pouring_down', 'my now shivering shoulders',), (ArgKind.VAR_POSITIONAL, 'in_the_rain_my_tears_are_forever_lost', ArgMandatory,), (ArgKind.KEYWORD_ONLY, 'the_darkened_oaks_are_my_only_shelter', ArgMandatory,), (ArgKind.KEYWORD_ONLY, 'red_leaves_are_blown_by', 'the wind',), (ArgKind.KEYWORD_ONLY, 'an_ebony_raven_now_catches', 'my eye.',), (ArgKind.VAR_KEYWORD, 'sitting_in_calmness', ArgMandatory,), ) # ....................{ FAIL }.................... # Assert this iterator returns a generator raising the expected exception # when passed a C-based callable. with raises(_BeartypeUtilCallableException): next(iter_func_args(iter)) beartype-0.18.5/beartype_test/a00_unit/a20_util/func/arg/test_utilfuncargtest.py000066400000000000000000000104451461113517100276740ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Callable argument tester utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.func.arg.utilfuncargtest` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ tester : kind }.................... def test_is_func_argless() -> None: ''' Test the :func:`beartype._util.func.arg.utilfuncargtest.is_func_argless` tester. ''' # Defer test-specific imports. from beartype._util.func.arg.utilfuncargtest import is_func_argless from beartype_test.a00_unit.data.func.data_func import ( func_args_0, func_args_1_flex_mandatory, func_args_1_varpos, func_args_1_kwonly_mandatory, ) # Assert this tester accepts an argumentless callable. assert is_func_argless(func_args_0) is True # Assert this tester rejects all other callables. assert is_func_argless(func_args_1_flex_mandatory) is False assert is_func_argless(func_args_1_varpos) is False assert is_func_argless(func_args_1_kwonly_mandatory) is False def test_is_func_arg_variadic() -> None: ''' Test the :func:`beartype._util.func.arg.utilfuncargtest.is_func_arg_variadic` tester. ''' # Defer test-specific imports. from beartype._util.func.arg.utilfuncargtest import is_func_arg_variadic #FIXME: Generalize these callables into the #"beartype_test.a00_unit.data.func.data_func" submodule, please. # Arbitrary callable accepting *NO* variadic arguments. def by_day_or_starlight(thus_from: int, my_first_dawn: int) -> int: return thus_from + my_first_dawn # Arbitrary callable accepting a variadic positional argument. def of_childhood( didst_thou: str, *args: str, intertwine_for_me: str) -> str: return didst_thou + ''.join(args) + intertwine_for_me # Arbitrary callable accepting a variadic keyword argument. def the_passions( that_build_up: tuple, our_human_soul: tuple, **kwargs: tuple) -> tuple: return that_build_up + our_human_soul + ( kwarg_tuple_item for kwarg_tuple in kwargs for kwarg_tuple_item in kwarg_tuple ) # Assert this tester accepts callables accepting variadic arguments. assert is_func_arg_variadic(of_childhood) is True assert is_func_arg_variadic(the_passions) is True # Assert this tester rejects callables accepting *NO* variadic arguments. assert is_func_arg_variadic(by_day_or_starlight) is False # ....................{ TESTS ~ tester : name }.................... def test_is_func_arg_name() -> None: ''' Test the :func:`beartype._util.func.arg.utilfuncargtest.is_func_arg_name` tester. ''' # Defer test-specific imports. from beartype._util.func.arg.utilfuncargtest import is_func_arg_name from beartype_test.a00_unit.data.func.data_func import ( func_args_5_flex_mandatory_varpos_kwonly_varkw) # Assert this tester accepts all argument names accepted by this callable. assert is_func_arg_name( func_args_5_flex_mandatory_varpos_kwonly_varkw, 'we_are_selfish_men') is True assert is_func_arg_name( func_args_5_flex_mandatory_varpos_kwonly_varkw, 'oh_raise_us_up') is True assert is_func_arg_name( func_args_5_flex_mandatory_varpos_kwonly_varkw, 'return_to_us_again') is True assert is_func_arg_name( func_args_5_flex_mandatory_varpos_kwonly_varkw, 'and_give_us') is True assert is_func_arg_name( func_args_5_flex_mandatory_varpos_kwonly_varkw, 'manners_virtue_freedom_power', ) is True # Assert this tester rejects all local variable names declared in the body # of this callable. assert is_func_arg_name( func_args_5_flex_mandatory_varpos_kwonly_varkw, 'thy_soul_was_like_a_star', ) is False beartype-0.18.5/beartype_test/a00_unit/a20_util/func/coro/000077500000000000000000000000001461113517100232255ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/func/coro/__init__.py000066400000000000000000000000001461113517100253240ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/func/pep/000077500000000000000000000000001461113517100230475ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/func/pep/__init__.py000066400000000000000000000000001461113517100251460ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/func/pep/test_utilpep484func.py000066400000000000000000000033711461113517100272620ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' :pep:`484`-compliant **callable utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.utilfunc.pep.utilpep484func` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_is_func_pep484_notypechecked() -> None: ''' Test the :func:`beartype._util.func.pep.utilpep484func.is_func_pep484_notypechecked` tester. ''' # Defer test-specific imports. from beartype._util.func.pep.utilpep484func import ( is_func_pep484_notypechecked) from typing import no_type_check @no_type_check def now_float_above_thy_darkness() -> None: ''' Arbitrary callable decorated by the :pep:`484`-compliant :func:`typing.no_type_check` decorator. ''' pass def and_now_rest() -> None: ''' Arbitrary callable *not* decorated by the :pep:`484`-compliant :func:`typing.no_type_check` decorator. ''' pass # Assert this tester returns the expected results for these callables. assert is_func_pep484_notypechecked( now_float_above_thy_darkness) is True assert is_func_pep484_notypechecked( and_now_rest) is False beartype-0.18.5/beartype_test/a00_unit/a20_util/func/test_utilfunccode.py000066400000000000000000000163011461113517100263610ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype callable source code utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.func.utilfunccode` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ code }.................... def test_get_func_code_or_none() -> None: ''' Test usage of the :func:`beartype._util.func.utilfunccode.get_func_code_or_none` function. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilCallableException from beartype.roar._roarwarn import _BeartypeUtilCallableWarning from beartype._util.func.utilfunccode import get_func_code_or_none from beartype._util.py.utilpyversion import ( IS_PYTHON_AT_LEAST_3_9, IS_PYTHON_AT_LEAST_3_11, ) from beartype_test.a00_unit.data.util.func.data_utilfunccode import ( of_vapours, will_be_the_dome, thou_dirge, yellow, ) from collections.abc import Callable from pytest import warns from re import escape, search # ..................{ NON-LAMBDA }.................. # Assert this getter accepts C-based callables with "None" assert get_func_code_or_none(iter) is None # Assert this getter accepts a dynamically declared callable with "None". assert get_func_code_or_none(of_vapours) is None # Assert this getter accepts a physically declared pure-Python non-lambda # callable with the full definition of that callable. assert get_func_code_or_none(will_be_the_dome) == """def will_be_the_dome(): ''' Arbitrary non-lambda function physically declared by this submodule. ''' return 'of a vast sepulchre' """ # ..................{ LAMBDA }.................. def _assert_lambda_args_0_body_is( func: Callable, func_code_body: str, ) -> None: ''' Test-specific function asserting that the body of the passed **argumentless lambda function** (i.e., lambda function accepting *no* parameters) is exactly the passed string. This function enables the bodies of lambda functions to be portably tested across distinct Python versions. Parameters ---------- func : Callable Lambda function to test this string against. func_code_body : str Expected body of this lambda function to be tested for. ''' # Regular expression matching the leading prefix of the source code # underlying this lambda. LAMBDA_ARGS_0_CODE_PREFIX = ( # Under Python >= 3.11, this code is no longer erroneously prefixed # by an assignment statement globalizing this lambda. r'lambda: ' if IS_PYTHON_AT_LEAST_3_11 else # Under Python 3.9 and 3.10, this code is typically erroneously # prefixed by an assignment statement globalizing this lambda but # nonetheless has an accidental space inserted inappropriately. r'lambda : ' if IS_PYTHON_AT_LEAST_3_9 else # Under Python < 3.9, this code is typically erroneously prefixed # by an assignment statement globalizing this lambda. r'(.*? = )lambda: ' ) # Regular expression matching this prefix followed by this body, # escaped to avoid unsafe interpretation as regular expressions. func_code_regex = ( fr'^{LAMBDA_ARGS_0_CODE_PREFIX}{escape(func_code_body)}$') # Original source code of this lambda. func_code = get_func_code_or_none(func) # Assert this code is prefixed by the expected substring followed by the # passed body of this lambda. assert search(pattern=func_code_regex, string=func_code) is not None # If the active Python interpreter targets Python >= 3.9 and thus defines # requisite AST machinery enabling this getter to return exact rather than # inexact definitions for lambda functions... if IS_PYTHON_AT_LEAST_3_9: # Assert this getter accepts a physically declared pure-Python lambda # function in which only one lambda is declared on its source code line # with the embedded definition of that function. _assert_lambda_args_0_body_is( func=thou_dirge, func_code_body="'Of the dying year, to which this closing night'", ) # Assert this getter accepts a physically declared pure-Python lambda # functions in which multiple lambdas are declared on the same source # code line with the embedded definition of the first such function and # a non-fatal warning disclosing this inconvenience to the caller. with warns(_BeartypeUtilCallableWarning): _assert_lambda_args_0_body_is( func=yellow[0], func_code_body="'and black,'", ) # Else, the active Python interpreter targets only Python < 3.9 and thus # does *NOT* define that machinery. In this case... else: # Assert this getter accepts a physically declared pure-Python lambda # function in which only one lambda is declared on its source code line # with the entire line. _assert_lambda_args_0_body_is( func=thou_dirge, func_code_body="'Of the dying year, to which this closing night'\n", ) # ....................{ TESTS ~ label }.................... #FIXME: This getter no longer has a sane reason to exist. Consider excising. # def test_get_func_code_label() -> None: # ''' # Test usage of the # :func:`beartype._util.func.utilfunccode.get_func_code_label` function. # ''' # # # Defer test-specific imports. # from beartype.roar._roarexc import _BeartypeUtilCallableException # from beartype._util.func.utilfunccode import get_func_code_label # from beartype_test.a00_unit.data.data_type import ( # CALLABLES_C, # CALLABLES_PYTHON, # MODULE_FILENAME, # NON_CALLABLES, # ) # # # Assert this getter returns the expected label for pure-Python callables. # for callable_python in CALLABLES_PYTHON: # assert get_func_code_label(callable_python) == MODULE_FILENAME # # # Assert this getter returns the expected label for C-based callables. # for callable_c in CALLABLES_C: # assert get_func_code_label(callable_c) == '' # # # Assert this getter raises the expected exception for non-callables. # for non_callable in NON_CALLABLES: # with raises(_BeartypeUtilCallableException): # get_func_code_label(non_callable) beartype-0.18.5/beartype_test/a00_unit/a20_util/func/test_utilfunccodeobj.py000066400000000000000000000154101461113517100270540ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype callable code object utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.utilfunc.utilfunccodeobj` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from functools import wraps # ....................{ DATA }.................... #FIXME: Refactor as follows for sanity, please #* Shift into the "beartype_test.a00_unit.data.data_type" submodule. #* Rename this class to something suitably generic -- say, "ClassWithWrappers". # Or perhaps just shift these methods into an existing class of that submodule? #* Import this class into tests below. # Arbitrary pure-Python class. class OfStagnantWaters(object): # Arbitrary pure-Python wrappee method. def altar(self, sword: str, and_pen: str) -> str: return sword + and_pen # Arbitrary pure-Python wrappee method. @wraps(altar) def and_give_us_manners_virtue_freedom_power( self, *args, **kwargs) -> str: return self.altar(*args, **kwargs) # ....................{ TESTS }.................... def test_get_func_codeobj() -> None: ''' Test the :func:`beartype._util.func.utilfunccodeobj.get_func_codeobj` function. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilCallableException from beartype._cave._cavefast import CallableCodeObjectType from beartype._util.func.utilfunccodeobj import get_func_codeobj from beartype_test.a00_unit.data.data_type import ( function, sync_generator_factory, wrapper_isomorphic, ) from pytest import raises # ..................{ UNWRAPPING ~ false }.................. # Instance of that class. fireside = OfStagnantWaters() # Assert this tester accepts pure-Python callables. function_codeobj = get_func_codeobj(function) assert isinstance(function_codeobj, CallableCodeObjectType) assert isinstance(get_func_codeobj(fireside.altar), CallableCodeObjectType) # Assert this tester accepts pure-Python generators. assert isinstance( get_func_codeobj(sync_generator_factory), CallableCodeObjectType) # Assert this tester also accepts code objects. assert get_func_codeobj(function_codeobj) is function_codeobj # Assert this tester rejects C-based callables. with raises(_BeartypeUtilCallableException): get_func_codeobj(iter) # ..................{ UNWRAPPING ~ true }.................. # Assert this tester accepts pure-Python wrappee callables. function_codeobj = get_func_codeobj( func=function, is_unwrap=True) have_forfeited_their_ancient_english_dower = get_func_codeobj( func=fireside.altar, is_unwrap=True) assert isinstance( function_codeobj, CallableCodeObjectType) assert isinstance( have_forfeited_their_ancient_english_dower, CallableCodeObjectType) # Assert this tester accepts pure-Python wrapper callables. assert ( get_func_codeobj(func=wrapper_isomorphic, is_unwrap=True) is get_func_codeobj(func=function, is_unwrap=False) ) assert get_func_codeobj( func=fireside.and_give_us_manners_virtue_freedom_power, is_unwrap=True ) is get_func_codeobj(func=fireside.altar, is_unwrap=False) # Assert this tester also accepts code objects. assert get_func_codeobj( function_codeobj, is_unwrap=True) is function_codeobj # Assert this tester rejects C-based callables. with raises(_BeartypeUtilCallableException): get_func_codeobj(iter, is_unwrap=True) def test_get_func_codeobj_or_none() -> None: ''' Test the :func:`beartype._util.func.utilfunccodeobj.get_func_codeobj_or_none` function. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilCallableException from beartype._cave._cavefast import CallableCodeObjectType from beartype._util.func.utilfunccodeobj import get_func_codeobj_or_none from beartype_test.a00_unit.data.data_type import ( function, sync_generator_factory, wrapper_isomorphic, ) # ..................{ UNWRAPPING ~ false }.................. # Instance of that class. fireside = OfStagnantWaters() # Assert this tester accepts pure-Python non-generator callables. function_codeobj = get_func_codeobj_or_none(function) assert isinstance(function_codeobj, CallableCodeObjectType) assert isinstance( get_func_codeobj_or_none(fireside.altar), CallableCodeObjectType) # Assert this tester accepts pure-Python generators. assert isinstance( get_func_codeobj_or_none(sync_generator_factory), CallableCodeObjectType, ) # Assert this tester also accepts code objects. assert get_func_codeobj_or_none(function_codeobj) is function_codeobj # Assert this tester rejects C-based builtins. assert get_func_codeobj_or_none(iter) is None # ..................{ UNWRAPPING ~ true }.................. # Assert this tester accepts pure-Python wrappee callables. function_codeobj = get_func_codeobj_or_none( func=function, is_unwrap=True) have_forfeited_their_ancient_english_dower = get_func_codeobj_or_none( func=fireside.altar, is_unwrap=True) assert isinstance(function_codeobj, CallableCodeObjectType) assert isinstance( have_forfeited_their_ancient_english_dower, CallableCodeObjectType) # Assert this tester accepts pure-Python wrapper callables. assert ( get_func_codeobj_or_none(wrapper_isomorphic, is_unwrap=True) is get_func_codeobj_or_none(function, is_unwrap=False) ) assert get_func_codeobj_or_none( func=fireside.and_give_us_manners_virtue_freedom_power, is_unwrap=True, ) is get_func_codeobj_or_none(fireside.altar, is_unwrap=False) # Assert this tester also accepts code objects. assert get_func_codeobj_or_none( func=function_codeobj, is_unwrap=True) is function_codeobj # Assert this tester rejects C-based builtins. assert get_func_codeobj_or_none(iter, is_unwrap=True) is None beartype-0.18.5/beartype_test/a00_unit/a20_util/func/test_utilfuncfile.py000066400000000000000000000100171461113517100263640ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype callable source code file utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.func.utilfuncfile` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ tester }.................... def test_is_func_file() -> None: ''' Test the :func:`beartype._util.func.utilfuncfile.is_func_file` tester. ''' # Defer test-specific imports. from beartype._util.func.utilfuncfile import is_func_file # Arbitrary pure-Python callable declared on-disk. def but_now_thy_youngest_dearest_one_has_perished(): return 'The nursling of thy widowhood, who grew,' # Arbitrary pure-Python callable declared in-memory *WITHOUT* being cached # by the standard "linecache" module. like_a_pale_flower = eval('lambda: "by some sad maiden cherished,"') # Assert this tester accepts pure-Python callables declared on-disk. assert is_func_file(but_now_thy_youngest_dearest_one_has_perished) is True # Assert this tester rejects pure-Python callables declared in-memory. assert is_func_file(like_a_pale_flower) is False # Assert this tester rejects C-based callables. assert is_func_file(iter) is False # ....................{ TESTS ~ getter }.................... def test_get_func_filename_or_none() -> None: ''' Test the :func:`beartype._util.func.utilfuncfile.get_func_filename_or_none` getter. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype._util.func.utilfuncfile import get_func_filename_or_none from beartype._util.func.utilfuncmake import make_func # ....................{ CALLABLES }.................... # Arbitrary pure-Python callable declared on-disk. def and_this_the_naked_countenance_of_earth(): return 'On which I gaze, even these primeval mountains' # Arbitrary pure-Python callable declared in-memory cached by the standard # "linecache" module. like_snakes_that_watch_their_prey = make_func( func_name='like_snakes_that_watch_their_prey', func_code=( """ like_snakes_that_watch_their_prey = lambda: ( 'from their far fountains,') """), is_debug=True, ) # Arbitrary pure-Python callable declared in-memory *WITHOUT* being cached # by the standard "linecache" module. teach_the_adverting_mind = eval('lambda: "The glaciers creep"') # ....................{ ASSERTS }.................... # Assert this getter returns "None" when passed a C-based callable. assert get_func_filename_or_none(iter) is None # Assert this getter returns the absolute filename of this submodule when # passed an on-disk pure-Python callable defined by this submodule. assert get_func_filename_or_none( and_this_the_naked_countenance_of_earth) == __file__ # Assert this getter returns the placeholder filename associated with the # code object of a pure-Python callable defined in-memory cached by the # standard "linecache" module. assert get_func_filename_or_none( like_snakes_that_watch_their_prey) == ( like_snakes_that_watch_their_prey.__code__.co_filename) # Assert this getter returns "None" when passed a pure-Python callable # defined in-memory *NOT* cached by the standard "linecache" module. assert get_func_filename_or_none(teach_the_adverting_mind) is None beartype-0.18.5/beartype_test/a00_unit/a20_util/func/test_utilfuncframe.py000066400000000000000000000054551461113517100265510ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Call stack utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.utilfunc.utilfuncframe` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ getters }.................... def test_get_frame() -> None: ''' Test the :func:`beartype._util.func.utilfuncframe.get_frame` getter. ''' # Defer test-specific imports. from beartype._util.func.utilfuncframe import get_frame # Assert this attribute is a callable under both CPython and PyPy. assert callable(get_frame) is True # ....................{ TESTS ~ iterators }.................... def test_iter_frames() -> None: ''' Test the :func:`beartype._util.func.utilfuncframe.iter_frames` iterator. ''' # Defer test-specific imports. from beartype._util.func.utilfunccodeobj import get_func_codeobj from beartype._util.func.utilfuncframe import iter_frames # For each stack frame on the current call stack iterated by this # iterator... for frame in iter_frames(): # Code object underlying the pure-Python function encapsulated by this # stack frame. frame_codeobj = get_func_codeobj(frame) # Unqualified name of this function. frame_name = frame_codeobj.co_name # Assert that this function is the current unit test function. assert frame_name == 'test_iter_frames' # Halt iteration. For safety, avoid attempting to assert *ANYTHING* # about pytest-specific stack frames deeper in the call stack. break # If the prior "break" statement was *NOT* run, the prior iteration did # *NOT* run at all. In this case, raise an unconditional test failure. else: assert False, 'iter_frames() empty.' # Assert that this iterator reduces to the empty generator when asked to # ignore more stack frames than currently reside on the call stack. # # Note that an excessively large number of stack frames is intentionally # chosen to avoid conflicts with "pytest" itself, which is implemented in # pure-Python and thus runs tests with Python functions that themselves add # an arbitrary number of stack frames to the call stack. assert tuple(iter_frames(func_stack_frames_ignore=999999)) == () beartype-0.18.5/beartype_test/a00_unit/a20_util/func/test_utilfuncget.py000066400000000000000000000013201461113517100262210ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **callable tester utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.utilfunc.utilfunctest` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! beartype-0.18.5/beartype_test/a00_unit/a20_util/func/test_utilfuncmake.py000066400000000000000000000240511461113517100263650ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Callable creation utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.utilfunc.utilfuncmake` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ GLOBALS }.................... # Arbitrary global referenced in functions created below. AND_SEE_THE_GREAT_ACHILLES = 'whom we knew' # ....................{ TESTS ~ make }.................... def test_make_func(capsys) -> None: ''' Test the :func:`beartype._util.func.utilfuncmake.make_func` function. Parameters ---------- capsys :mod:`pytest` fixture enabling standard output and error to be reliably captured and tested against from within unit tests and fixtures. Parameters ---------- https://docs.pytest.org/en/latest/how-to/capture-stdout-stderr.html#accessing-captured-output-from-a-test-function Official ``capsys`` reference documentation. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar import BeartypeDecorWrapperException from beartype.roar._roarexc import _BeartypeUtilCallableException from beartype.typing import Optional from beartype._util.func.utilfuncmake import make_func from linecache import cache as linecache_cache from pytest import raises # ....................{ LOCALS }.................... # Arbitrary local referenced in functions created below. THO_MUCH_IS_TAKEN = 'much abides; and tho’' # ....................{ FUNCS }.................... # Arbitrary callable wrapped by wrappers created below. def we_are_not_now_that_strength_which_in_old_days() -> str: ''' One equal temper of heroic hearts, ''' return 'Moved earth and heaven, that which we are, we are;' # Arbitrary wrapper accessing both globally and locally scoped attributes, # exercising most optional parameters. ulysses = make_func( func_name='it_may_be_that_the_gulfs_will_wash_us_down', func_code=''' def it_may_be_that_the_gulfs_will_wash_us_down( it_may_be_we_shall_touch_the_happy_isles: Optional[str]) -> str: return ( AND_SEE_THE_GREAT_ACHILLES + THO_MUCH_IS_TAKEN + we_are_not_now_that_strength_which_in_old_days() + ( it_may_be_we_shall_touch_the_happy_isles or 'Made weak by time and fate, but strong in will' ) ) ''', func_globals={ 'AND_SEE_THE_GREAT_ACHILLES': AND_SEE_THE_GREAT_ACHILLES, 'THO_MUCH_IS_TAKEN': THO_MUCH_IS_TAKEN, 'we_are_not_now_that_strength_which_in_old_days': ( we_are_not_now_that_strength_which_in_old_days), }, func_locals={ 'Optional': Optional, }, func_wrapped=we_are_not_now_that_strength_which_in_old_days, ) # ....................{ PASS }.................... # Assert this wrapper wrapped this wrappee. assert ulysses.__doc__ == ( we_are_not_now_that_strength_which_in_old_days.__doc__) # Assert this wrapper returns an expected value. odyssey = ulysses('Made weak by time and fate, but strong in will') assert 'Made weak by time and fate, but strong in will' in odyssey # Arbitrary debuggable callable accessing no scoped attributes. to_strive_to_seek_to_find = make_func( func_name='to_strive_to_seek_to_find', func_code=''' def to_strive_to_seek_to_find(and_not_to_yield: str) -> str: return and_not_to_yield ''', # Print the definition of this callable to standard output, captured by # the "capsys" fixture passed above for testing against below. is_debug=True, ) # Assert this callable returns an expected value. assert ( to_strive_to_seek_to_find('Tis not too late to seek a newer world.') == 'Tis not too late to seek a newer world.' ) # Pytest object freezing the current state of standard output and error as # uniquely written to by this unit test up to this statement. standard_captured = capsys.readouterr() # Assert the prior make_func() call printed the expected definition. assert standard_captured.out.count('\n') == 2 assert 'line' in standard_captured.out assert 'def to_strive_to_seek_to_find(' in standard_captured.out assert 'return and_not_to_yield' in standard_captured.out # Assert the prior make_func() call cached the expected definition. func_filename = to_strive_to_seek_to_find.__code__.co_filename func_cache = linecache_cache.get(func_filename) assert isinstance(func_cache, tuple) assert len(func_cache) == 4 assert isinstance(func_cache[0], int) assert func_cache[1] is None assert func_cache[3] == func_filename func_cache_code = ''.join(func_cache[2]) assert 'def to_strive_to_seek_to_find(' in func_cache_code assert 'return and_not_to_yield' in func_cache_code # ....................{ FAIL }.................... # Assert that attempting to create a function whose name collides with that # of a caller-defined local variable raises the expected exception. with raises(_BeartypeUtilCallableException): make_func( func_name='come_my_friends', func_code=''' def come_my_friends(T: str) -> str: return T + 'is not too late to seek a newer world' ''', func_label='Magnanimous come_my_friends() function', func_locals={ 'come_my_friends': 'Push off, and sitting well in order smite', }, ) # Assert that attempting to execute a syntactically invalid snippet raises # the expected exception. with raises(BeartypeDecorWrapperException): make_func( func_name='to_sail_beyond_the_sunset', func_code=''' def to_sail_beyond_the_sunset(and_the_baths: str) -> str: Of all the western stars, until I die. ''', func_label='Heroic to_sail_beyond_the_sunset() function', exception_cls=BeartypeDecorWrapperException, ) # Assert that attempting to execute a syntactically valid snippet failing # to declare this function raises the expected exception. with raises(BeartypeDecorWrapperException): make_func( func_name='you_and_i_are_old', func_code=''' def old_age_hath_yet_his_honour_and_his_toil() -> str: return 'Death closes all: but something ere the end' ''', func_label='Geriatric you_and_i_are_old() function', exception_cls=BeartypeDecorWrapperException, ) # ....................{ TESTS ~ make }.................... #FIXME: Consider excising. Although awesome, this is no longer needed. # def test_copy_func_shallow_pass() -> None: # ''' # Test successful usage of the # :func:`beartype._util.func.utilfuncmake.copy_func_shallow` function. # ''' # # # Defer test-specific imports. # from beartype.roar import BeartypeDecorWrapperException # from beartype._util.func.utilfuncmake import copy_func_shallow # # # Tuple of the names of all attributes expected to be shallowly copied. # ATTRS_NAME_COPIED = ( # '__annotations__', # '__closure__', # '__code__', # '__defaults__', # '__doc__', # '__globals__', # # '__kwdefaults__', # '__module__', # '__name__', # '__qualname__', # ) # # # String returned by the in_memoriam() function declared below when passed # # an even integer. # IN_MEMORIAM_RETURN_IF_PARAM_EVEN = 'And all we met was fair and good,' # # # String returned by the in_memoriam() function declared below when passed # # an even integer. # IN_MEMORIAM_RETURN_IF_PARAM_ODD = ' And all was good that Time could bring,' # # # String suffixing the string returned by that function. # IN_MEMORIAM_RETURN_SUFFIX = 'I sing to him that rests below,' # # # Arbitrary closure to be shallowly copied. # def in_memoriam( # # Mandatory parameter. # the_shadow: int, # # # Optional parameter. # the_shroud: str = IN_MEMORIAM_RETURN_SUFFIX, # ) -> str: # ''' # The Shadow sits and waits for me. # ''' # # return ( # IN_MEMORIAM_RETURN_IF_PARAM_EVEN + the_shroud # if the_shadow % 2 == 0 else # IN_MEMORIAM_RETURN_IF_PARAM_ODD + the_shroud # ) # # # Set a custom attribute on this callable to be shallowly copied. # in_memoriam.the_clock = ''' # And in the dusk of thee, the clock # Beats out the little lives of men.''' # # # Function shallowly copied from this callable. # captive_void = copy_func_shallow(func=in_memoriam) # # # Assert this copy returns the expected value. # assert captive_void(27) == ( # f'{IN_MEMORIAM_RETURN_IF_PARAM_ODD}{IN_MEMORIAM_RETURN_SUFFIX}') # # # Assert this copy shares the same custom attribute as the original. # assert captive_void.the_clock == in_memoriam.the_clock # # # Assert this copy contains the same dunder attributes. # for attr_name_copied in ATTRS_NAME_COPIED: # assert ( # getattr(captive_void, attr_name_copied) == # getattr(in_memoriam, attr_name_copied) # ) # # # Assert this function rejects C-based functions. # with raises(BeartypeDecorWrapperException): # copy_func_shallow( # func=iter, exception_cls=BeartypeDecorWrapperException) beartype-0.18.5/beartype_test/a00_unit/a20_util/func/test_utilfuncscope.py000066400000000000000000000306451461113517100265670ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Callable scope utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.utilfunc.utilfuncscope` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ CLASSES }.................... class WhenOwlsCallTheBreathlessMoon(object): ''' Arbitrary class declaring an arbitrary method. ''' def in_the_blue_veil_of_the_night(self) -> None: ''' Arbitrary method. ''' pass # ....................{ DECORATORS }.................... def attach_func_locals(**kwargs) -> 'collections.abc.Callable': ''' Decorator attaching the local scope of the parent callable declaring the passed callable to a new ``func_locals`` attribute of the passed callable. Parameters ---------- This decorator forwards all passed keyword parameters as is to the :func:`beartype._util.func.utilfuncscope.get_func_locals` getter. ''' # Defer scope-specific imports for sanity. from beartype._util.func.utilfuncscope import get_func_locals def _attach_func_locals(func) -> 'collections.abc.Callable': ''' Inner decorator satisfying Python's abstruse decorator design pattern. ''' # Attach the local scope of that parent callable to the passed callable. func.func_locals = get_func_locals(func=func, **kwargs) # Reduce to the identity decorator. return func # Return this inner decorator. return _attach_func_locals # ....................{ CALLABLES }.................... def when_in_the_springtime_of_the_year(): ''' Arbitrary callable declaring an arbitrary nested callable. ''' # Defer scope-specific imports for sanity. from beartype.typing import Union # Arbitrary PEP-compliant type hint localized to this parent callable. type_hint = Union[int, str] @attach_func_locals(func_stack_frames_ignore=1) def when_the_trees_are_crowned_with_leaves() -> type_hint: ''' Arbitrary nested callable annotated by a type hint localized to the parent callable and decorated by a decorator attaching the local scope of that parent callable to a new ``func_locals`` attribute of this nested callable. ''' return 'When the ash and oak, and the birch and yew' # Return this nested callable. return when_the_trees_are_crowned_with_leaves def long_past_their_woodland_days(): ''' Arbitrary callable declaring an arbitrary deeply nested class hierarchy declaring an arbitrary deeply nested callable. ''' # Defer scope-specific imports for sanity. from typing import Union # Arbitrary PEP-compliant type hint localized to this parent callable. type_hint = Union[bool, str] class TheShadowsOfTheTreesAppear(object): class AmidstTheLanternLight(object): @attach_func_locals( # Ignore both this nested "AmidstTheLanternLight" class and its # parent "TheShadowsOfTheTreesAppear" class when searching for # the closure lexical scope declaring the "type_hint" local. func_scope_names_ignore=2, func_stack_frames_ignore=1, ) def weve_been_rambling_all_the_night(self) -> type_hint: ''' Arbitrary nested callable annotated by a PEP-compliant type hint localized to the parent callable and decorated by a decorator attaching the local scope of that parent callable to a new ``func_locals`` attribute of this nested callable. ''' return 'And some time of this day' # Return the root class of this class hierarchy. return TheShadowsOfTheTreesAppear # ....................{ TESTS ~ tester }.................... def test_is_func_nested() -> None: ''' Test the :func:`beartype._util.func.utilfuncscope.is_func_nested` tester. ''' # Defer test-specific imports. from beartype._util.func.utilfunctest import is_func_nested # Nested callable returned by the above callable. when_the_ash_and_oak_and_the_birch_and_yew = ( when_in_the_springtime_of_the_year()) # Assert this tester accepts methods. assert is_func_nested( WhenOwlsCallTheBreathlessMoon.in_the_blue_veil_of_the_night) is True # Assert this tester accepts nested callables. # print(f'__nested__: {repr(when_the_ash_and_oak_and_the_birch_and_yew.__nested__)}') assert is_func_nested(when_the_ash_and_oak_and_the_birch_and_yew) is True # Assert this tester rejects non-nested parent callables declaring nested # callables. # print(f'__nested__: {repr(when_in_the_springtime_of_the_year.__nested__)}') assert is_func_nested(when_in_the_springtime_of_the_year) is False # Assert this tester rejects C-based builtins. assert is_func_nested(iter) is False #FIXME: Unclear whether we'll ever require this, but preserved as is for now. # def test_get_func_wrappee() -> None: # ''' # Test the # :func:`beartype._util.func.utilfuncget.get_func_wrappee` function. # ''' # # # Defer test-specific imports. # from beartype.roar._roarexc import _BeartypeUtilCallableException # from beartype._util.func.utilfuncget import get_func_wrappee # from functools import wraps # # # Arbitrary callable *NOT* decorated by @wraps. # def the_journey_begins_with_curiosity() -> str: # return 'And envolves into soul-felt questions' # # # Arbitrary callable decorated by @wraps. # @wraps(the_journey_begins_with_curiosity) # def on_the_stones_that_we_walk() -> str: # return ( # the_journey_begins_with_curiosity() + # 'And choose to make our path' # ) # # # Assert this getter raises the expected exception when passed a callable # # *NOT* decorated by @wraps. # with raises(_BeartypeUtilCallableException): # get_func_wrappee(the_journey_begins_with_curiosity) # # # Assert this getter returns the wrapped callable when passed a callable # # decorated by @wraps. # assert get_func_wrappee(on_the_stones_that_we_walk) is ( # the_journey_begins_with_curiosity) # ....................{ TESTS ~ getter }.................... def test_get_func_locals() -> None: ''' Test the :func:`beartype._util.func.utilfuncscope.get_func_locals` getter. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilCallableException from beartype.typing import Union from beartype._util.func.utilfuncmake import make_func from beartype._util.func.utilfuncscope import get_func_locals from pytest import raises # ..................{ CALLABLES }.................. def and_summon_the_shadows_there(): ''' Arbitrary nested callable. ''' return 'And tie a ribbon on those sheltering arms' # Arbitrary nested callable dynamically declared in-memory. when_the_ash_and_oak_and_the_birch_and_yew = make_func( func_name='when_the_ash_and_oak_and_the_birch_and_yew', func_code='''def when_the_ash_and_oak_and_the_birch_and_yew(): pass''', ) # Arbitrary nested callables whose unqualified and fully-qualified names # are maliciously desynchronized below, exercising edge cases. def are_dressed_in_ribbons_fair(): pass def who_will_go_down_to_the_shady_groves(): pass are_dressed_in_ribbons_fair.__qualname__ = ( 'when_owls_call.the_breathless_moon') who_will_go_down_to_the_shady_groves.__qualname__ = ( '.who_will_go_down_to_the_shady_groves') # ..................{ PASS ~ noop }.................. # Assert this getter returns the empty dictionary for callables dynamically # declared in-memory. assert get_func_locals(when_the_ash_and_oak_and_the_birch_and_yew) == {} # Assert this getter returns the empty dictionary for unnested callables. assert get_func_locals(when_in_the_springtime_of_the_year) == {} # Assert this getter returns the empty dictionary for nested callables # ignoring *ALL* of their parent lexical scopes. assert get_func_locals( func=and_summon_the_shadows_there, func_scope_names_ignore=1) == {} # ..................{ PASS ~ callable }.................. # Arbitrary nested callable declared by a module-scoped callable. when_the_trees_are_crowned_with_leaves = ( when_in_the_springtime_of_the_year()) # Dictionary mapping from the name to value of all local attributes # accessible to that nested callable. func_locals = when_the_trees_are_crowned_with_leaves.func_locals # Assert this dictionary contains the name of the local attribute annotating # that nested callable's return. func_locals_return = func_locals.get('type_hint') assert func_locals_return == Union[int, str] # ..................{ PASS ~ class }.................. # Arbitrary root nested class declared by a module-scoped callable. TheShadowsOfTheTreesAppear = long_past_their_woodland_days() # Method lexically nested inside that root nested class. weve_been_rambling_all_the_night = ( TheShadowsOfTheTreesAppear.AmidstTheLanternLight.weve_been_rambling_all_the_night) # Dictionary mapping from the name to value of all local attributes # accessible to that method. func_locals = weve_been_rambling_all_the_night.func_locals # Assert this dictionary contains the name of the local attribute annotating # that nested callable's return. func_locals_return = func_locals.get('type_hint') assert func_locals_return == Union[bool, str] # ..................{ FAIL }.................. # Assert this getter raises the expected exception for nested callables # whose unqualified and fully-qualified names are desynchronized. with raises(_BeartypeUtilCallableException): get_func_locals(are_dressed_in_ribbons_fair) # Assert this getter raises the expected exception for nested callables # whose fully-qualified name is prefixed by "". with raises(_BeartypeUtilCallableException): get_func_locals(who_will_go_down_to_the_shady_groves) # Assert this getter raises the expected exception for nested callables # erroneously ignoring more parent lexical scopes than actually exist. with raises(_BeartypeUtilCallableException): get_func_locals( func=and_summon_the_shadows_there, func_scope_names_ignore=2, ) # ....................{ TESTS ~ adder }.................... def test_add_func_scope_attr() -> None: ''' Test the :func:`beartype._util.func.utilfuncscope.add_func_scope_attr` adder. ''' # Defer test-specific imports. from beartype._util.func.utilfuncscope import add_func_scope_attr # Arbitrary scope to add attributes to. func_scope = {} # Arbitrary object to be added to this scope. attr = 'Pestilence-stricken multitudes: O thou,' # Named of this attribute in this scope. attr_name = add_func_scope_attr(attr=attr, func_scope=func_scope) # Assert the prior call added this attribute to this scope as expected. assert isinstance(attr_name, str) assert str(id(attr)) in attr_name assert func_scope[attr_name] is attr # Assert this getter returns the same name when repassed an attribute # previously added to this scope. assert add_func_scope_attr(attr=attr, func_scope=func_scope) == attr_name assert func_scope[attr_name] is attr # Note that testing this getter's error condition is effectively # infeasible, as doing so would require deterministically creating a # different object with the same object identifier. *sigh* beartype-0.18.5/beartype_test/a00_unit/a20_util/func/test_utilfunctest.py000066400000000000000000000432631461113517100264350ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **callable tester utility** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.utilfunc.utilfunctest` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... # Note that the related test_is_func_nested() unit test is intentionally defined # in the companion "test_utilfuncscope" submodule rather than this submodule. # Why? Convenience, mostly. That submodule defines various callables of interest # that dramatically simplify the implementation of that unit test. def test_is_func_lambda() -> None: ''' Test the :func:`beartype._util.func.utilfunctest.is_func_lambda` tester. ''' # Defer test-specific imports. from beartype._util.func.utilfunctest import is_func_lambda def intimations_of_immortality(): 'from Recollections of Early Childhood' # Assert this tester accepts pure-Python lambda functions. assert is_func_lambda(lambda: True) is True # Assert this tester rejects pure-Python non-lambda callables. assert is_func_lambda(intimations_of_immortality) is False # Assert this tester rejects C-based callables. assert is_func_lambda(iter) is False # ....................{ TESTS ~ async }.................... def test_is_func_async() -> None: ''' Test the :func:`beartype._util.func.utilfunctest.is_func_async` tester. ''' # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilCallableException from beartype._util.func.utilfunctest import is_func_async from beartype_test.a00_unit.data.data_type import ( async_generator, async_generator_factory, async_coroutine, async_coroutine_factory, sync_generator, sync_generator_factory, function, ) # Assert this tester accepts pure-Python coroutine callables. assert is_func_async(async_coroutine_factory) is True # Assert this tester rejects pure-Python coroutine objects. assert is_func_async(async_coroutine) is False # Assert this tester accepts pure-Python asynchronous generator callables. assert is_func_async(async_generator_factory) is True # Assert this tester rejects pure-Python asynchronous generator objects. assert is_func_async(async_generator) is False # Assert this tester rejects pure-Python synchronous generator callables. assert is_func_async(sync_generator_factory) is False # Assert this tester rejects pure-Python synchronous generator objects. assert is_func_async(sync_generator) is False # Assert this tester rejects pure-Python non-asynchronous callables. assert is_func_async(function) is False # Assert this tester rejects arbitrary non-asynchronous objects. assert is_func_async('To hear—an old and solemn harmony;') is False def test_is_func_coro() -> None: ''' Test the :func:`beartype._util.func.utilfunctest.is_func_coro` function. ''' # Defer test-specific imports. from beartype._util.func.utilfunctest import is_func_coro from beartype.roar._roarexc import _BeartypeUtilCallableException from beartype_test.a00_unit.data.data_type import ( async_generator, async_generator_factory, async_coroutine, async_coroutine_factory, function, ) # Assert this tester accepts pure-Python coroutine callables. assert is_func_coro(async_coroutine_factory) is True # Assert this tester rejects pure-Python coroutine objects. assert is_func_coro(async_coroutine) is False # Assert this tester rejects pure-Python asynchronous generator callables. assert is_func_coro(async_generator_factory) is False # Assert this tester rejects pure-Python asynchronous generator objects. assert is_func_coro(async_generator) is False # Assert this tester rejects pure-Python non-asynchronous callables. assert is_func_coro(function) is False # Assert this tester rejects arbitrary non-asynchronous objects. assert is_func_coro('To hear—an old and solemn harmony;') is ( False) def test_is_func_async_generator() -> None: ''' Test the :func:`beartype._util.func.utilfunctest.is_func_async_generator` function. ''' # Defer test-specific imports. from beartype._util.func.utilfunctest import is_func_async_generator from beartype.roar._roarexc import _BeartypeUtilCallableException from beartype_test.a00_unit.data.data_type import ( async_coroutine, async_coroutine_factory, async_generator, async_generator_factory, sync_generator, sync_generator_factory, function, ) # Assert this tester accepts pure-Python asynchronous generator callables. assert is_func_async_generator(async_generator_factory) is True # Assert this tester rejects pure-Python asynchronous generator objects. assert is_func_async_generator(async_generator) is False # Assert this tester rejects pure-Python coroutine callables. assert is_func_async_generator(async_coroutine_factory) is False # Assert this tester rejects pure-Python coroutine objects. assert is_func_async_generator(async_coroutine) is False # Assert this tester rejects pure-Python synchronous generator callables. assert is_func_async_generator(sync_generator_factory) is False # Assert this tester rejects pure-Python synchronous generator objects. assert is_func_async_generator(sync_generator) is False # Assert this tester rejects pure-Python non-asynchronous callables. assert is_func_async_generator(function) is False # Assert this tester rejects arbitrary non-asynchronous objects. assert is_func_async_generator('To hear—an old and solemn harmony;') is ( False) # ....................{ TESTS ~ sync }.................... def test_is_func_sync_generator() -> None: ''' Test the :func:`beartype._util.func.utilfunctest.is_func_sync_generator` function. ''' # Defer test-specific imports. from beartype._util.func.utilfunctest import is_func_sync_generator from beartype.roar._roarexc import _BeartypeUtilCallableException from beartype_test.a00_unit.data.data_type import ( async_coroutine, async_coroutine_factory, async_generator, async_generator_factory, sync_generator, sync_generator_factory, function, ) # Assert this tester rejects pure-Python asynchronous generator callables. assert is_func_sync_generator(async_generator_factory) is False # Assert this tester rejects pure-Python asynchronous generator objects. assert is_func_sync_generator(async_generator) is False # Assert this tester rejects pure-Python coroutine callables. assert is_func_sync_generator(async_coroutine_factory) is False # Assert this tester rejects pure-Python coroutine objects. assert is_func_sync_generator(async_coroutine) is False # Assert this tester accepts pure-Python synchronous generator callables. assert is_func_sync_generator(sync_generator_factory) is True # Assert this tester accepts pure-Python synchronous generator objects. assert is_func_sync_generator(sync_generator) is False # Assert this tester rejects pure-Python non-asynchronous callables. assert is_func_sync_generator(function) is False # Assert this tester rejects arbitrary non-asynchronous objects. assert is_func_sync_generator('To hear—an old and solemn harmony;') is ( False) # ....................{ TESTS ~ descriptor : classmethod }.................... def test_die_unless_func_classmethod() -> None: ''' Test the :func:`beartype._util.func.utilfunctest.die_unless_func_classmethod` validator. ''' # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilCallableException from beartype._util.func.utilfunctest import die_unless_func_classmethod from beartype_test.a00_unit.data.data_type import ( CALLABLES, Class, ) from pytest import raises # Assert this validator accepts a class method descriptor. # # Note that class method descriptors are *ONLY* directly accessible via the # low-level "object.__dict__" dictionary. When accessed as class or instance # attributes, class methods reduce to instances of the standard # "beartype.cave.MethodBoundInstanceOrClassType" type. die_unless_func_classmethod(Class.__dict__['class_method']) # Assert this validator rejects *ALL* other callables. for some_callable in CALLABLES: with raises(_BeartypeUtilCallableException): die_unless_func_classmethod(some_callable) def test_is_func_classmethod() -> None: ''' Test the :func:`beartype._util.func.utilfunctest.is_func_classmethod` tester. ''' # Defer test-specific imports. from beartype._util.func.utilfunctest import is_func_classmethod from beartype_test.a00_unit.data.data_type import ( CALLABLES, Class, ) # Assert this tester accepts a class method descriptor. # # Note that class method descriptors are *ONLY* directly accessible via the # low-level "object.__dict__" dictionary. When accessed as class or instance # attributes, class methods reduce to instances of the standard # "beartype.cave.MethodBoundInstanceOrClassType" type. assert is_func_classmethod(Class.__dict__['class_method']) is True # Assert this tester rejects *ALL* other callables. for some_callable in CALLABLES: assert is_func_classmethod(some_callable) is False # ....................{ TESTS ~ descriptor : property }.................... def test_die_unless_func_property() -> None: ''' Test the :func:`beartype._util.func.utilfunctest.die_unless_func_property` validator. ''' # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilCallableException from beartype._util.func.utilfunctest import die_unless_func_property from beartype_test.a00_unit.data.data_type import ( CALLABLES, Class, ) from pytest import raises # Assert this validator accepts a property descriptor. # # Note that property descriptors are directly accessible both as class # attributes *AND* via the low-level "object.__dict__" dictionary. Property # objects are *NOT* accessible as instance attributes, for obvious reasons. die_unless_func_property(Class.instance_property) # Assert this validator rejects *ALL* other callables. for some_callable in CALLABLES: with raises(_BeartypeUtilCallableException): die_unless_func_property(some_callable) def test_is_func_property() -> None: ''' Test the :func:`beartype._util.func.utilfunctest.is_func_property` tester. ''' # Defer test-specific imports. from beartype._util.func.utilfunctest import is_func_property from beartype_test.a00_unit.data.data_type import ( CALLABLES, Class, ) # Assert this tester accepts a property descriptor. # # Note that property descriptors are directly accessible both as class # attributes *AND* via the low-level "object.__dict__" dictionary. Property # objects are *NOT* accessible as instance attributes, for obvious reasons. assert is_func_property(Class.instance_property) is True # Assert this tester rejects *ALL* other callables. for some_callable in CALLABLES: assert is_func_property(some_callable) is False # ....................{ TESTS ~ descriptor : staticmethod }.................... def test_die_unless_func_staticmethod() -> None: ''' Test the :func:`beartype._util.func.utilfunctest.die_unless_func_staticmethod` validator. ''' # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilCallableException from beartype._util.func.utilfunctest import die_unless_func_staticmethod from beartype_test.a00_unit.data.data_type import ( CALLABLES, Class, ) from pytest import raises # Assert this validator accepts a static method descriptor. # # Note that static method descriptors are *ONLY* directly accessible via the # low-level "object.__dict__" dictionary. When accessed as class or instance # attributes, static methods reduce to instances of the standard # "beartype.cave.FunctionType" type. die_unless_func_staticmethod(Class.__dict__['static_method']) # Assert this validator rejects *ALL* other callables. for some_callable in CALLABLES: with raises(_BeartypeUtilCallableException): die_unless_func_staticmethod(some_callable) def test_is_func_staticmethod() -> None: ''' Test the :func:`beartype._util.func.utilfunctest.is_func_staticmethod` tester. ''' # Defer test-specific imports. from beartype._util.func.utilfunctest import is_func_staticmethod from beartype_test.a00_unit.data.data_type import ( CALLABLES, Class, ) # Assert this tester accepts a static method descriptor. # # Note that static method descriptors are *ONLY* directly accessible via the # low-level "object.__dict__" dictionary. When accessed as class or instance # attributes, static methods reduce to instances of the standard # "beartype.cave.FunctionType" type. assert is_func_staticmethod(Class.__dict__['static_method']) is True # Assert this tester rejects *ALL* other callables. for some_callable in CALLABLES: assert is_func_staticmethod(some_callable) is False # ....................{ TESTS ~ python }.................... def test_die_unless_func_python() -> None: ''' Test the :func:`beartype._util.func.utilfunctest.die_unless_func_python` tester. ''' # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilCallableException from beartype._util.func.utilfunctest import die_unless_func_python from beartype_test.a00_unit.data.data_type import CALLABLES_C from pytest import raises # Assert this validator accepts pure-Python callables. die_unless_func_python(lambda: True) # Assert this validator rejects *ALL* C-based callables. for callable_c in CALLABLES_C: with raises(_BeartypeUtilCallableException): die_unless_func_python(callable_c) def test_is_func_python() -> None: ''' Test the :func:`beartype._util.func.utilfunctest.is_func_python` tester. ''' # Defer test-specific imports. from beartype._util.func.utilfunctest import is_func_python # Assert this tester accepts pure-Python callables. assert is_func_python(lambda: True) is True # Assert this tester rejects C-based callables. assert is_func_python(iter) is False # ....................{ TESTS ~ nested : closure }.................... def test_is_func_closure() -> None: ''' Test the :func:`beartype._util.func.utilfunctest.is_func_closure` tester. ''' # Defer test-specific imports. from beartype._util.func.utilfunctest import is_func_closure from beartype_test.a00_unit.data.data_type import ( closure_factory, function, ) # Assert this tester accepts closures. assert is_func_closure(closure_factory()) is True # Assert this tester rejects non-closure callables. assert is_func_closure(function) is False # ....................{ TESTS ~ nested : wrapper }.................... def test_is_func_wrapper() -> None: ''' Test the :func:`beartype._util.func.utilfunctest.is_func_wrapper` tester. ''' # Defer test-specific imports. from beartype._util.func.utilfunctest import is_func_wrapper from beartype_test.a00_unit.data.data_type import ( decorator_isomorphic, function, ) # Assert this tester accepts wrappers generated by standard decorators. assert is_func_wrapper(decorator_isomorphic(function)) is True # Assert this tester rejects non-wrapper callables. assert is_func_wrapper(function) is False # Assert this tester rejects non-wrapper non-callables. assert is_func_wrapper( 'Of mouldering leaves in the waste wilderness:—') is False def test_is_func_wrapper_isomorphic() -> None: ''' Test the :func:`beartype._util.func.utilfunctest.is_func_wrapper_isomorphic` tester. ''' # Defer test-specific imports. from beartype._util.func.utilfunctest import is_func_wrapper_isomorphic from beartype_test.a00_unit.data.data_type import ( context_manager_factory, decorator_isomorphic, decorator_nonisomorphic, function, wrapper_isomorphic, ) # Assert this tester accepts isomorphic closure wrappers. assert is_func_wrapper_isomorphic(context_manager_factory) is True assert is_func_wrapper_isomorphic(decorator_isomorphic(function)) is True # Assert this tester accepts isomorphic non-closure wrappers. assert is_func_wrapper_isomorphic(wrapper_isomorphic) is True # Assert this tester rejects non-isomorphic wrappers. assert is_func_wrapper_isomorphic( decorator_nonisomorphic(function)) is False # Assert this tester rejects non-wrapper callables. assert is_func_wrapper_isomorphic(function) is False beartype-0.18.5/beartype_test/a00_unit/a20_util/func/test_utilfuncwrap.py000066400000000000000000000143761461113517100264320ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Callable wrapper utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.utilfunc.utilfuncwrap` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ all }.................... def test_unwrap_func_all() -> None: ''' Test the :func:`beartype._util.func.utilfuncwrap.unwrap_func_all` function. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype._util.func.utilfuncwrap import unwrap_func_all from functools import wraps # ....................{ CALLABLES }.................... def in_a_station_of_the_metro_line_two(): ''' Arbitrary wrappee callable. ''' return 'Petals on a wet, black bough.' @wraps(in_a_station_of_the_metro_line_two) def in_a_station_of_the_metro(): ''' Arbitrary wrapper callable. ''' return ( 'THE apparition of these faces in the crowd;\n' + in_a_station_of_the_metro_line_two() ) # ....................{ ASSERTS }.................... # Assert this function returns unwrapped callables unmodified. assert unwrap_func_all(in_a_station_of_the_metro_line_two) is ( in_a_station_of_the_metro_line_two) assert unwrap_func_all(iter) is iter # Assert this function returns wrapper callables unwrapped. assert unwrap_func_all(in_a_station_of_the_metro) is ( in_a_station_of_the_metro_line_two) # ....................{ TESTS ~ descriptor }.................... def test_unwrap_func_classmethod_once() -> None: ''' Test the :func:`beartype._util.func.utilfuncwrap.unwrap_func_classmethod_once` getter. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilCallableWrapperException from beartype._util.func.utilfuncwrap import unwrap_func_classmethod_once from beartype_test.a00_unit.data.data_type import CALLABLES from pytest import raises # ....................{ LOCALS }.................... def never_to_be_reclaimed() -> str: ''' Arbitrary pure-Python function. ''' return 'The dwelling-place' class TheLimitsOfTheDeadAndLivingWorld(object): ''' Arbitrary pure-Python class defining a class method wrapping the pure-Python function defined above. ''' of_insects_beasts_and_birds = classmethod(never_to_be_reclaimed) # ....................{ PASS }.................... # Assert this getter returns the expected wrappee when passed a class method # descriptor. # # Note that class method descriptors are *ONLY* directly accessible via the # low-level "object.__dict__" dictionary. When accessed as class or instance # attributes, class methods reduce to instances of the standard # "beartype.cave.MethodBoundInstanceOrClassType" type. class_method = TheLimitsOfTheDeadAndLivingWorld.__dict__[ 'of_insects_beasts_and_birds'] class_method_wrappee = unwrap_func_classmethod_once(class_method) assert class_method_wrappee is never_to_be_reclaimed # ....................{ FAIL }.................... # Assert this getter raises the expected exception when passed an object # that is *NOT* a class method descriptor. for some_callable in CALLABLES: with raises(_BeartypeUtilCallableWrapperException): unwrap_func_classmethod_once(some_callable) def test_unwrap_func_staticmethod_once() -> None: ''' Test the :func:`beartype._util.func.utilfuncwrap.unwrap_func_staticmethod_once` getter. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilCallableWrapperException from beartype._util.func.utilfuncwrap import unwrap_func_staticmethod_once from beartype_test.a00_unit.data.data_type import CALLABLES from pytest import raises # ....................{ LOCALS }.................... def becomes_its_spoil() -> str: ''' Arbitrary pure-Python function. ''' return 'The dwelling-place' class TheirFoodAndTheirRetreatForEverGone(object): ''' Arbitrary pure-Python static defining a static method wrapping the pure-Python function defined above. ''' so_much_of_life_and_joy_is_lost = staticmethod(becomes_its_spoil) # ....................{ PASS }.................... # Assert this getter returns the expected wrappee when passed a static method # descriptor. # # Note that static method descriptors are *ONLY* directly accessible via the # low-level "object.__dict__" dictionary. When accessed as static or instance # attributes, static methods reduce to instances of the standard # "beartype.cave.MethodBoundInstanceOrClassType" type. static_method = TheirFoodAndTheirRetreatForEverGone.__dict__[ 'so_much_of_life_and_joy_is_lost'] static_method_wrappee = unwrap_func_staticmethod_once(static_method) assert static_method_wrappee is becomes_its_spoil # ....................{ FAIL }.................... # Assert this getter raises the expected exception when passed an object # that is *NOT* a static method descriptor. for some_callable in CALLABLES: with raises(_BeartypeUtilCallableWrapperException): unwrap_func_staticmethod_once(some_callable) beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/000077500000000000000000000000001461113517100222725ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/__init__.py000066400000000000000000000000001461113517100243710ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a00_pep/000077500000000000000000000000001461113517100235165ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a00_pep/__init__.py000066400000000000000000000000001461113517100256150ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/000077500000000000000000000000001461113517100253555ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/__init__.py000066400000000000000000000000001461113517100274540ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/pep484/000077500000000000000000000000001461113517100264015ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/pep484/__init__.py000066400000000000000000000000001461113517100305000ustar00rootroot00000000000000test_utilpep484typevar.py000066400000000000000000000043751461113517100333010ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/pep484#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype** :pep:`484`-compliant **type variable utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.hint.pep.proposal.pep484.utilpep484typevar` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ kind : typevar }.................... def test_get_hint_pep484_typevar_bound_or_none() -> None: ''' Test the :func:`beartype._util.hint.pep.proposal.pep484.utilpep484typevar.get_hint_pep484_typevar_bound_or_none` tester. ''' # Defer test-specific imports. from beartype.roar import BeartypeDecorHintPep484Exception from beartype._util.hint.pep.proposal.pep484.utilpep484typevar import ( get_hint_pep484_typevar_bound_or_none) from beartype_test.a00_unit.data.hint.pep.proposal.data_pep484 import ( T, T_BOUNDED, T_CONSTRAINED, ) from pytest import raises # Assert this getter returns "None" for unbounded type variables. assert get_hint_pep484_typevar_bound_or_none(T) is None # Assert this getter reduces bounded type variables to their upper bound. assert get_hint_pep484_typevar_bound_or_none(T_BOUNDED) is int # Union of all constraints parametrizing a constrained type variable, # reduced from that type variable. typevar_constraints_union = get_hint_pep484_typevar_bound_or_none( T_CONSTRAINED) # Assert this union contains all constraints parametrizing this variable. assert str in typevar_constraints_union.__args__ assert bytes in typevar_constraints_union.__args__ # Assert this getter raises the expected exception when passed a non-type # variable. with raises(BeartypeDecorHintPep484Exception): get_hint_pep484_typevar_bound_or_none(str) beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/pep484585/000077500000000000000000000000001461113517100266435ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/pep484585/__init__.py000066400000000000000000000000001461113517100307420ustar00rootroot00000000000000test_utilpep484585.py000066400000000000000000000111671461113517100323670ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/pep484585#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`484` and :pep:`585` **type hint utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.hint.pep.proposal.pep484585.utilpep484585` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ tester }.................... def test_is_hint_pep484585_tuple_empty() -> None: ''' Test the :func:`beartype._util.hint.pep.proposal.pep484585.utilpep484585.is_hint_pep484585_tuple_empty` tester. ''' # Defer test-specific imports. from beartype._util.hint.pep.proposal.pep484585.utilpep484585 import ( is_hint_pep484585_tuple_empty) from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_9 from typing import Tuple # <-- intentionally import the PEP 484 variant # Assert this tester returns true for PEP 484-compliant empty tuple type # hints. assert is_hint_pep484585_tuple_empty(Tuple[()]) is True # Assert this tester returns false for PEP 484-compliant non-empty tuple # type hints. assert is_hint_pep484585_tuple_empty(Tuple[int, ...]) is False # If the active Python interpreter targets Python >= 3.9 and thus supports # PEP 585... if IS_PYTHON_AT_LEAST_3_9: # Assert this tester returns true for PEP 585-compliant empty tuple type # hints. assert is_hint_pep484585_tuple_empty(tuple[()]) is True # Assert this tester returns false for PEP 585-compliant non-empty tuple # type hints. assert is_hint_pep484585_tuple_empty(tuple[int, ...]) is False # Else, the active Python interpreter targets Python 3.8 and thus fails to # support PEP 585. # ....................{ TESTS ~ getter }.................... def test_get_hint_pep484585_args() -> None: ''' Test the :func:`beartype._util.hint.pep.proposal.pep484585.utilpep484585.get_hint_pep484585_args` getter. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar import BeartypeDecorHintPep585Exception from beartype.typing import ( Dict, List, ) from beartype._util.hint.pep.proposal.pep484585.utilpep484585 import ( get_hint_pep484585_args) from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_9 from pytest import raises # ....................{ PASS }.................... # Assert this getter returns the expected child type hint for a PEP 484- # and 585-compliant type hint properly subscripted by one child type hint. assert get_hint_pep484585_args( hint=List[bool], args_len=1, exception_prefix='') == bool # Assert this getter returns the expected child type hints for a PEP 484- # and 585-compliant type hint properly subscripted by two or more child type # hints. assert get_hint_pep484585_args( hint=Dict[str, int], args_len=2, exception_prefix='') == (str, int) # ....................{ FAIL }.................... # Assert this tester raises the expected exception for an arbitrary type # hint passed invalid argument lengths. with raises(AssertionError): get_hint_pep484585_args( hint='He fled. Red morning dawned upon his flight,', args_len=0.01, exception_prefix='', ) with raises(AssertionError): get_hint_pep484585_args( hint='Shedding the mockery of its vital hues', args_len=0, exception_prefix='', ) # If the active Python interpreter targets Python >= 3.9 and thus supports # PEP 585... if IS_PYTHON_AT_LEAST_3_9: # Assert this tester raises the expected exception for a PEP # 585-compliant dictionary type hint improperly subscripted by an # unexpected number of child type hints. with raises(BeartypeDecorHintPep585Exception): get_hint_pep484585_args( hint=Dict[str], args_len=2, exception_prefix='') # Else, the active Python interpreter targets Python 3.8 and thus fails to # support PEP 585. test_utilpep484585callable.py000066400000000000000000000170001461113517100340370ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/pep484585#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`484` and :pep:`585` **callable type hint utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.hint.pep.proposal.pep484585.utilpep484585callable` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_get_hint_pep484585_callable_params_and_return(hints_pep_meta) -> None: ''' Test both the ``get_hint_pep484585_callable_params`` and ``get_hint_pep484585_callable_return`` getters declared by the :mod:`beartype._util.hint.pep.proposal.pep484585.utilpep484585callable` submodule. Since these getters are inextricably interrelated, this unit test exercises both within the same test to satisfy Don't Repeat Yourself (DRY). Parameters ---------- hints_pep_meta : List[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] List of PEP-compliant type hint metadata describing sample PEP-compliant type hints exercising edge cases in the :mod:`beartype` codebase. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype.roar import BeartypeDecorHintPep484585Exception from beartype.typing import Any from beartype._data.hint.pep.sign.datapepsigns import HintSignCallable from beartype._util.hint.pep.proposal.pep484585.utilpep484585callable import ( get_hint_pep484585_callable_params, get_hint_pep484585_callable_return, ) from beartype._util.py.utilpyversion import ( IS_PYTHON_AT_LEAST_3_10, IS_PYTHON_AT_LEAST_3_9, ) from beartype_test.a00_unit.data.hint.data_hint import NOT_HINTS_PEP from pytest import raises # ..................{ GENERIC }.................. # General-purpose logic generically exercising these getters against all # globally defined type hints. # Assert these getters... for hint_pep_meta in hints_pep_meta: # Return zero or more arguments for PEP-compliant callable type hints. if hint_pep_meta.pep_sign is HintSignCallable: hint_callable_params = get_hint_pep484585_callable_params( hint_pep_meta.hint) hint_callable_return = get_hint_pep484585_callable_return( hint_pep_meta.hint) assert isinstance(hint_callable_params, tuple) assert hint_callable_return is not None # Raise an exception for concrete PEP-compliant type hints *NOT* # defined by the "typing" module. else: with raises(BeartypeDecorHintPep484585Exception): get_hint_pep484585_callable_params(hint_pep_meta.hint) with raises(BeartypeDecorHintPep484585Exception): get_hint_pep484585_callable_return(hint_pep_meta.hint) # Assert these getters raise the expected exception for non-PEP-compliant # type hints. for not_hint_pep in NOT_HINTS_PEP: with raises(BeartypeDecorHintPep484585Exception): get_hint_pep484585_callable_params(not_hint_pep) with raises(BeartypeDecorHintPep484585Exception): get_hint_pep484585_callable_return(not_hint_pep) # ..................{ PEP ~ 484 }.................. # Intentionally import the callable type hint factory from "typing" rather # than "beartype.typing" to guarantee PEP 484-compliance. from typing import Callable # List of 3-tuples "(callable_hint, callable_hint_params, # callable_hint_return)", where: # * "callable_hint" is a PEP-compliant callable type hint to be tested. # * "callable_hint_params" is the parameters type hint subscripting that # callable type hint. # * "callable_hint_return" is the return type hint subscripting that # callable type hint. CALLABLE_HINT_PARAMS_RETURN_CASES = [ # PEP 484-compliant callable type hints. (Callable[[], Any], (), Any), (Callable[[int], bool], (int,), bool), (Callable[[int, bool], float], (int, bool), float), (Callable[..., bytes], Ellipsis, bytes), ] # ..................{ PEP ~ 585 }.................. # If the active Python interpreter targets Python >= 3.9 and thus supports # PEP 585... if IS_PYTHON_AT_LEAST_3_9: # Intentionally import the callable type hint factory from # "collections.abc" rather than "beartype.typing" to guarantee PEP # 585-compliance. from collections.abc import Callable # Extend this list with PEP 585-compliant callable type hints. CALLABLE_HINT_PARAMS_RETURN_CASES.extend(( (Callable[[], Any], (), Any), (Callable[[int], bool], (int,), bool), (Callable[[int, bool], float], (int, bool), float), (Callable[..., bytes], Ellipsis, bytes), # Note this edge case is intentionally *NOT* tested above as a # PEP 484-compliant callable type hint, as the "typing.Callable" # factory refuses to accept the empty tuple here: e.g., # >>> from typing import Callable # >>> Callable[[()], str] # TypeError: Callable[[arg, ...], result]: each arg must be a # type. Got (). (Callable[[()], str], (), str), )) # ..................{ PEP ~ 612 }.................. # If the active Python interpreter targets Python >= 3.10 and thus supports # PEP 612... if IS_PYTHON_AT_LEAST_3_10: # Defer version-specific imports. from beartype.typing import ( Concatenate, ParamSpec, ) # Arbitrary PEP 612-compliant child type hints. P = ParamSpec('P') str_plus_P = Concatenate[str, P] # Extend this list with PEP 585-compliant callable type hints. CALLABLE_HINT_PARAMS_RETURN_CASES.extend(( (Callable[P, Any], P, Any), (Callable[str_plus_P, int], str_plus_P, int), )) # ..................{ PEP }.................. # For each callable type hint defined above... for hint, hint_params, hint_return in CALLABLE_HINT_PARAMS_RETURN_CASES: # Parameters type hint returned by this getter for this hint. hint_params_actual = get_hint_pep484585_callable_params(hint) # If the parameters type hint subscripting this callable type hint is a # tuple, assert this tuple to be equal but *NOT* identical to this # actual tuple. if isinstance(hint_params, tuple): assert hint_params_actual == hint_params # Else, the parameters type hint subscripting this callable type hint is # a non-tuple. In this case, assert this non-tuple to be identical to # this actual non-tuple. else: assert hint_params_actual is hint_params # Assert this getter returns the expected return. assert get_hint_pep484585_callable_return(hint) is hint_return test_utilpep484585generic.py000066400000000000000000000223331461113517100337210ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/pep484585#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`484` and :pep:`585` **generic type hint utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.hint.pep.proposal.pep484585.utilpep484585generic` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ testers }.................... def test_is_hint_pep484585_generic(hints_pep_meta) -> None: ''' Test the :func:`beartype._util.hint.pep.proposal.pep484585.utilpep484585generic.is_hint_pep484585_generic` tester. Parameters ---------- hints_pep_meta : List[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] List of PEP-compliant type hint metadata describing sample PEP-compliant type hints exercising edge cases in the :mod:`beartype` codebase. ''' # Defer test-specific imports. from beartype._data.hint.pep.sign.datapepsigns import HintSignGeneric from beartype._util.hint.pep.proposal.pep484585.utilpep484585generic import ( is_hint_pep484585_generic) from beartype_test.a00_unit.data.hint.data_hint import NOT_HINTS_PEP # Assert this tester: # * Accepts generic PEP 484-compliant generics. # * Rejects concrete PEP-compliant type hints. for hint_pep_meta in hints_pep_meta: assert is_hint_pep484585_generic(hint_pep_meta.hint) is ( hint_pep_meta.pep_sign is HintSignGeneric) # Assert this tester rejects non-PEP-compliant type hints. for not_hint_pep in NOT_HINTS_PEP: assert is_hint_pep484585_generic(not_hint_pep) is False # ....................{ TESTS ~ getters }.................... def test_get_hint_pep484585_generic_type_or_none(hints_pep_meta) -> None: ''' Test the :func:`beartype._util.hint.pep.proposal.pep484585.utilpep484585generic.get_hint_pep484585_generic_type_or_none` getter. Parameters ---------- hints_pep_meta : List[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] List of PEP-compliant type hint metadata describing sample PEP-compliant type hints exercising edge cases in the :mod:`beartype` codebase. ''' # Defer test-specific imports. from beartype._data.hint.pep.sign.datapepsigns import HintSignGeneric from beartype._util.hint.pep.proposal.pep484585.utilpep484585generic import ( get_hint_pep484585_generic_type_or_none) # Assert this getter returns the expected type origin for all # PEP-compliant type hint generics. While we could support non-generics as # well, there's little benefit and significant costs to doing so. Instead, # we assert this getter only returns the expected type origin for a small # subset of type hints. for hint_pep_meta in hints_pep_meta: if hint_pep_meta.pep_sign is HintSignGeneric: assert get_hint_pep484585_generic_type_or_none( hint_pep_meta.hint) is hint_pep_meta.generic_type #FIXME: Uncomment if we ever want to exercise extreme edge cases. *shrug* # from beartype_test.a00_unit.data.hint.data_hint import NOT_HINTS_PEP # # # Assert this getter returns the expected type origin for all # # PEP-compliant type hints. # for hint_pep_meta in HINTS_PEP_META: # assert get_hint_pep484585_generic_type_or_none( # hint_pep_meta.hint) is hint_pep_meta.generic_type # # # Assert this getter raises the expected exception for non-PEP-compliant # # type hints. # for not_hint_pep in NOT_HINTS_PEP: # assert get_hint_pep484585_generic_type_or_none(not_hint_pep) is None def test_get_hint_pep484585_generic_bases_unerased(hints_pep_meta) -> None: ''' Test the :func:`beartype._util.hint.pep.proposal.pep484585.utilpep484585generic.get_hint_pep484585_generic_bases_unerased` getter. Parameters ---------- hints_pep_meta : List[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] List of PEP-compliant type hint metadata describing sample PEP-compliant type hints exercising edge cases in the :mod:`beartype` codebase. ''' # Defer test-specific imports. from beartype.roar import BeartypeDecorHintPepException from beartype._data.hint.pep.sign.datapepsigns import HintSignGeneric from beartype._util.hint.pep.proposal.pep484585.utilpep484585generic import ( get_hint_pep484585_generic_bases_unerased) from beartype._util.hint.pep.utilpeptest import is_hint_pep_type_typing from beartype_test.a00_unit.data.hint.data_hint import NOT_HINTS_PEP from pytest import raises # Assert this getter... for hint_pep_meta in hints_pep_meta: # Returns one or more unerased pseudo-superclasses for PEP-compliant # generics. if hint_pep_meta.pep_sign is HintSignGeneric: hint_pep_bases = get_hint_pep484585_generic_bases_unerased( hint_pep_meta.hint) assert isinstance(hint_pep_bases, tuple) assert hint_pep_bases # Raises an exception for concrete PEP-compliant type hints *NOT* # defined by the "typing" module. elif not is_hint_pep_type_typing(hint_pep_meta.hint): with raises(BeartypeDecorHintPepException): get_hint_pep484585_generic_bases_unerased(hint_pep_meta.hint) # Else, this hint is defined by the "typing" module. In this case, this # hint may or may not be implemented as a generic conditionally # depending on the current Python version -- especially under the # Python < 3.7.0 implementations of the "typing" module, where # effectively *EVERYTHING* was internally implemented as a generic. # While we could technically correct for this conditionality, doing so # would render the resulting code less maintainable for no useful gain. # Ergo, we quietly ignore this edge case and get on with actual coding. # Assert this getter raises the expected exception for non-PEP-compliant # type hints. for not_hint_pep in NOT_HINTS_PEP: with raises(BeartypeDecorHintPepException): assert get_hint_pep484585_generic_bases_unerased(not_hint_pep) # ....................{ TESTS ~ finders }.................... def test_find_hint_pep484585_generic_module_base_first() -> None: ''' Test the :func:`beartype._util.hint.pep.proposal.pep484585.utilpep484585generic.find_hint_pep484585_generic_module_base_first` finder. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar import BeartypeDecorHintPep484585Exception from beartype.typing import Generic from beartype._data.hint.datahinttyping import T from beartype._util.hint.pep.proposal.pep484585.utilpep484585generic import ( find_hint_pep484585_generic_module_base_first) from pytest import raises # ....................{ CLASSES }.................... class ToWhichThisClosingNight(object): ''' Arbitrary superclass. ''' pass class OfTheDyingYear(Generic[T], str, ToWhichThisClosingNight): ''' Arbitrary generic subclassing: * The generic superclass subscripted by a type variable. * An arbitrary type *not* defined by the :mod:`beartype_test` package. * An arbitrary type defined by the :mod:`beartype_test` package. ''' pass # ....................{ LOCALS }.................... # Tuple of all generic type hints to be tested below, including... TEST_GENERICS = ( # An unsubscripted generic. OfTheDyingYear, # A subscripted generic (subscripted by an arbitrary type). OfTheDyingYear[int], ) # ....................{ ASSERTS }.................... # For each such generic type hint... for test_generic in TEST_GENERICS: # ....................{ PASS }.................... # Assert that this finder passed this generic returns the first unerased # superclass of this generic declared by this package. assert find_hint_pep484585_generic_module_base_first( hint=test_generic, module_name='beartype_test') is ( ToWhichThisClosingNight) # ....................{ FAIL }.................... # Assert that this finder passed this generic and the name of a # hypothetical module guaranteed *NOT* to exist raises the expected # exception. with raises(BeartypeDecorHintPep484585Exception): find_hint_pep484585_generic_module_base_first( hint=test_generic, module_name='will_be.the.dome_of.a_vast_sepulchre', ) test_utilpep484585ref.py000066400000000000000000000317331461113517100330650ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/pep484585#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`484` and :pep:`585` **forward reference type hint utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.hint.pep.proposal.pep484585.utilpep484585ref` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ getter }.................... def test_get_hint_pep484585_ref_names() -> None: ''' Test :func:`beartype._util.hint.pep.proposal.pep484585.utilpep484585ref.get_hint_pep484585_ref_names` getter. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar import BeartypeDecorHintForwardRefException from beartype.typing import ForwardRef from beartype._util.hint.pep.proposal.pep484585.utilpep484585ref import ( get_hint_pep484585_ref_names) from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_10 from pytest import raises # ....................{ LOCALS }.................... # Arbitrary absolute forward references defined as strings. ALAS_ALAS = 'He_overleaps.the.bounds' BREATH_AND = 'Were.limbs_and.breath_and' # Arbitrary absolute forward reference defined as a non-string. BEING_INTERTWINED = ForwardRef(BREATH_AND) # Arbitrary fully-qualified names of a hypothetical module. OF_DIM_SLEEP = 'In_the.wide.pathless_desert' # Arbitrary relative forward reference defined as a string. THUS_TREACHEROUSLY = 'Lost_lost_for_ever_lost' # ....................{ PASS }.................... # Assert this getter preserves the passed absolute forward reference as is # when defined as a string, regardless of whether the second passed object # defines the "__module__" dunder attribute. assert get_hint_pep484585_ref_names(ALAS_ALAS) == (None, ALAS_ALAS) # Assert this getter preserves the passed absolute forward reference as is # when defined as a non-string, regardless of whether the second passed # object defines the "__module__" dunder attribute. assert get_hint_pep484585_ref_names(BEING_INTERTWINED) == (None, BREATH_AND) # If the active Python interpreter targets >= Python 3.10, then this # typing.ForwardRef.__init__() method accepts an additional optional # "module: Optional[str] = None" parameter preserving the fully-qualified # names of the module to which the passed unqualified basenames is relative. # In this case... if IS_PYTHON_AT_LEAST_3_10: # Arbitrary relative forward reference defined as a non-string relative # to the fully-qualified names of a hypothetical module. BEAUTIFUL_SHAPE = ForwardRef(THUS_TREACHEROUSLY, module=OF_DIM_SLEEP) # Arbitrary absolute forward reference defined as a non-string relative # to the fully-qualified names of a hypothetical module. # # Note that this exercises an edge case. Technically, passing both an # absolute forward reference *AND* a module names is a non-sequitur. # Pragmatically, the ForwardRef.__init__() method blindly permits # callers to do just that. Ergo, @beartype *MUST* guard against this. DARK_GATE_OF_DEATH = ForwardRef(ALAS_ALAS, module=OF_DIM_SLEEP) # Assert this getter canonicalizes the passed relative forward reference # against the fully-qualified names of the module with which this # reference was instantiated when defined as a non-string. assert get_hint_pep484585_ref_names(BEAUTIFUL_SHAPE) == ( OF_DIM_SLEEP, THUS_TREACHEROUSLY) # Assert this getter preserves the passed absolute forward reference as # is *WITHOUT* canonicalizing this reference against the fully-qualified # names of the module with which this reference was instantiated when # defined as a non-string. assert get_hint_pep484585_ref_names(DARK_GATE_OF_DEATH) == ( OF_DIM_SLEEP, ALAS_ALAS) # ....................{ FAIL }.................... # Assert this getter raises the expected exception when passed a type hint # that is *NOT* a valid forward reference. with raises(BeartypeDecorHintForwardRefException): get_hint_pep484585_ref_names( b'Beyond the realms of dream that fleeting shade;') def test_get_hint_pep484585_ref_names_relative_to() -> None: ''' Test :func:`beartype._util.hint.pep.proposal.pep484585.utilpep484585ref.get_hint_pep484585_ref_names_relative_to` getter. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar import BeartypeDecorHintForwardRefException from beartype.typing import ForwardRef from beartype._util.hint.pep.proposal.pep484585.utilpep484585ref import ( get_hint_pep484585_ref_names_relative_to) from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_10 from beartype_test.a00_unit.data.data_type import ( ClassModuleNameFake, ClassModuleNameNone, function_module_name_fake, function_module_name_none, ) from pytest import raises # ....................{ LOCALS }.................... # Fully-qualified name of the current module defining this unit test. THIS_MODULE_NAME = __name__ # Arbitrary absolute forward references defined as strings. ALAS_ALAS = 'He_overleaps.the.bounds' THE_MASK_OF_ANARCHY = 'as_I.lay_asleep.in_Italy' WITH_GREAT_POWER_IT = 'And_with.great_power.it_forth.led_me' # Arbitrary absolute forward reference defined as a non-string. WITH_GREAT_POWER = ForwardRef(WITH_GREAT_POWER_IT) # Arbitrary fully-qualified names of hypothetical modules. CASTLEREAGH = 'He_had.a_mask.like_Castlereagh' OF_DIM_SLEEP = 'In_the.wide.pathless_desert' # Arbitrary relative forward references defined as strings. VERY_SMOOTH = 'Very_smooth_he_looked_yet_grim' THUS_TREACHEROUSLY = 'Lost_lost_for_ever_lost' # ....................{ CALLABLES }.................... def and_pendent_mountains(): ''' Arbitrary function whose ``__module__`` dunder attribute is preserved as the fully-qualified name of this currently importable module. ''' pass # ....................{ CLASSES }.................... class OfRainbowClouds(object): ''' Arbitrary class whose ``__module__`` dunder attribute is preserved as the fully-qualified name of this currently importable module. ''' pass # ....................{ PASS }.................... # Assert that this getter preserves an absolute forward reference in string # format as is. assert get_hint_pep484585_ref_names_relative_to(THE_MASK_OF_ANARCHY) == ( None, THE_MASK_OF_ANARCHY) # Assert that this getter preserves an absolute forward reference in # "typing.ForwardRef" format as is. assert get_hint_pep484585_ref_names_relative_to(WITH_GREAT_POWER) == ( None, WITH_GREAT_POWER_IT) # Assert that this getter canonicalizes a relative forward reference against # the "__module__" dunder attribute of a function to the expected string. assert get_hint_pep484585_ref_names_relative_to( hint=VERY_SMOOTH, func=and_pendent_mountains) == ( THIS_MODULE_NAME, VERY_SMOOTH) # Assert that this getter canonicalizes a relative forward reference against # the "__module__" dunder attribute of a type stack to the expected string. assert get_hint_pep484585_ref_names_relative_to( hint=VERY_SMOOTH, cls_stack=[OfRainbowClouds], func=and_pendent_mountains, ) == (THIS_MODULE_NAME, VERY_SMOOTH) # Assert that this getter preserves the passed relative forward reference as # is when this reference is the name of a builtin type, even when passed # neither a class *NOR* callable. assert get_hint_pep484585_ref_names_relative_to('int') == ( 'builtins', 'int') # Assert that this getter preserves the passed relative forward reference as # is when this reference is the name of a builtin type, even when passed # a class and callable both defining the "__module__" dunder attribute to be # the fully-qualified names of imaginary and thus unimportable modules that # do *NOT* physically exist. assert get_hint_pep484585_ref_names_relative_to( hint='int', cls_stack=(ClassModuleNameFake,), func=function_module_name_fake, ) == ('builtins', 'int') # If the active Python interpreter targets >= Python 3.10, then this # typing.ForwardRef.__init__() method accepts an additional optional # "module: Optional[str] = None" parameter preserving the fully-qualified # name of the module to which the passed unqualified basename is relative. # In this case... if IS_PYTHON_AT_LEAST_3_10: # Arbitrary relative forward reference defined as a non-string relative # to the fully-qualified name of a hypothetical module. BEAUTIFUL_SHAPE = ForwardRef(THUS_TREACHEROUSLY, module=OF_DIM_SLEEP) # Arbitrary absolute forward reference defined as a non-string relative # to the fully-qualified name of a hypothetical module. # # Note that this exercises an edge case. Technically, passing both an # absolute forward reference *AND* a module name is a non-sequitur. # Pragmatically, the ForwardRef.__init__() method blindly permits # callers to do just that. Ergo, @beartype *MUST* guard against this. DARK_GATE_OF_DEATH = ForwardRef(ALAS_ALAS, module=OF_DIM_SLEEP) # Assert this getter canonicalizes the passed relative forward reference # against the fully-qualified name of the module with which this # reference was instantiated when defined as a "typing.ForwardRef". assert get_hint_pep484585_ref_names_relative_to(BEAUTIFUL_SHAPE) == ( OF_DIM_SLEEP, THUS_TREACHEROUSLY) # Assert this getter preserves the passed absolute forward reference as # is *WITHOUT* canonicalizing this reference against the fully-qualified # name of the module with which this reference was instantiated when # defined as a "typing.ForwardRef". assert get_hint_pep484585_ref_names_relative_to(DARK_GATE_OF_DEATH) == ( OF_DIM_SLEEP, ALAS_ALAS) # ....................{ FAIL }.................... # Assert that this getter raises the expected exception when passed a # relative forward reference and neither a class *NOR* callable. with raises(BeartypeDecorHintForwardRefException): get_hint_pep484585_ref_names_relative_to(VERY_SMOOTH) # Assert that this getter raises the expected exception when passed a # relative forward reference and a class and callable both defining the # "__module__" dunder attribute to be the fully-qualified names of imaginary # and thus unimportable modules that do *NOT* physically exist. with raises(BeartypeDecorHintForwardRefException): get_hint_pep484585_ref_names_relative_to( hint=VERY_SMOOTH, cls_stack=(ClassModuleNameFake,), func=function_module_name_fake, ) # Assert that this getter raises the expected exception when passed a # relative forward reference and a class and callable both defining the # "__module__" dunder attribute to be "None". with raises(BeartypeDecorHintForwardRefException): get_hint_pep484585_ref_names_relative_to( hint=VERY_SMOOTH, cls_stack=(ClassModuleNameNone,), func=function_module_name_none, ) # Assert that this getter raises the expected exception when passed a # relative forward reference and *ONLY* a callable defining the # "__module__" dunder attribute to be the fully-qualified name of an # imaginary and thus unimportable module that does *NOT* physically exist. with raises(BeartypeDecorHintForwardRefException): get_hint_pep484585_ref_names_relative_to( hint=VERY_SMOOTH, func=function_module_name_fake) # Assert that this getter raises the expected exception when passed a # relative forward reference and *ONLY* a callable defining the # "__module__" dunder attribute to be "None". with raises(BeartypeDecorHintForwardRefException): get_hint_pep484585_ref_names_relative_to( hint=VERY_SMOOTH, func=function_module_name_none) test_utilpep484585type.py000066400000000000000000000143101461113517100332620ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/pep484585#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`484` and :pep:`585` **subclass type hint utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.hint.pep.proposal.pep484585.utilpep484585type` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ kind : subclass }.................... def test_get_hint_pep484585_type_superclass() -> None: ''' Test the ``get_hint_pep484585_type_superclass`` getter defined by the :mod:`beartype._util.hint.pep.proposal.pep484585.utilpep484585type` submodule. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar import ( BeartypeDecorHintPep3119Exception, BeartypeDecorHintPep484585Exception, BeartypeDecorHintPep585Exception, ) from beartype.typing import ( ForwardRef, Type, Union, ) from beartype._util.hint.pep.proposal.pep484585.utilpep484585type import ( get_hint_pep484585_type_superclass) from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_9 from beartype_test.a00_unit.data.data_type import NonIssubclassableClass from pytest import raises # ....................{ PASS }.................... # Assert this getter returns the expected object when passed a PEP # 484-compliant subclass type hint subscripted by a class. assert get_hint_pep484585_type_superclass(Type[str], '') is str # Assert this getter returns the expected object when passed a PEP # 484-compliant subclass type hint subscripted by a union of classes. hint_superclass = get_hint_pep484585_type_superclass( Type[Union[dict, set]], '') assert hint_superclass == (dict, set) or hint_superclass == (set, dict) # Assert this getter returns the expected object when passed a PEP # 484-compliant subclass type hint subscripted by a forward reference to a # class. Sadly, the expected object differs by Python version. *sigh* assert get_hint_pep484585_type_superclass(Type['bytes'], '') == ( 'bytes' if IS_PYTHON_AT_LEAST_3_9 else ForwardRef('bytes') ) # ....................{ FAIL }.................... # Assert this getter raises the expected exception when passed a PEP # 484-compliant subclass type hint subscripted by a non-issubclassable # class. with raises(BeartypeDecorHintPep3119Exception): get_hint_pep484585_type_superclass( Type[NonIssubclassableClass], '') # Assert this getter raises the expected exception when passed an arbitrary # object that is neither a PEP 484- nor 585-compliant subclass type hint. with raises(BeartypeDecorHintPep484585Exception): get_hint_pep484585_type_superclass('Caustically', '') # ....................{ VERSION }.................... # If the active Python interpreter targets Python >= 3.9 and thus supports # PEP 585... if IS_PYTHON_AT_LEAST_3_9: # ....................{ PASS }.................... # Assert this getter returns the expected object when passed a PEP # 585-compliant subclass type hint subscripted by a class. assert get_hint_pep484585_type_superclass(type[bool], '') is bool # Assert this getter returns the expected object when passed a PEP # 585-compliant subclass type hint subscripted by a union of classes. hint_superclass = get_hint_pep484585_type_superclass( type[Union[dict, set]], '') assert hint_superclass == (dict, set) or hint_superclass == (set, dict) # Assert this getter returns the expected object when passed a PEP # 585-compliant subclass type hint subscripted by a forward reference # to a class. assert get_hint_pep484585_type_superclass(type['complex'], '') == ( 'complex') # ....................{ FAIL }.................... # Assert this getter raises the expected exception when passed a PEP # 585-compliant subclass type hint subscripted by a non-issubclassable # class. with raises(BeartypeDecorHintPep3119Exception): get_hint_pep484585_type_superclass( type[NonIssubclassableClass], '') # Assert this getter raises the expected exception when passed a PEP # 585-compliant subclass type hint subscripted by *NO* classes. # # Note there intentionally exists *NO* corresponding PEP 484 test, as # the "typing.Type" factory already validates this to be the case. with raises(BeartypeDecorHintPep484585Exception): get_hint_pep484585_type_superclass(type[()], '') # Assert this getter raises the expected exception when passed a PEP # 585-compliant subclass type hint subscripted by two or more classes. # # Note there intentionally exists *NO* corresponding PEP 484 test, as # the "typing.Type" factory already validates this to be the case. with raises(BeartypeDecorHintPep585Exception): get_hint_pep484585_type_superclass(type[int, float], '') # Assert this getter raises the expected exception when passed a PEP # 585-compliant subclass type hint subscripted by an object that is # neither a class nor a forward reference to a class. # # Note there intentionally exists *NO* corresponding PEP 484 test, as # the "typing.Type" factory already validates this to be the case. with raises(BeartypeDecorHintPep484585Exception): get_hint_pep484585_type_superclass(type[ b'Counterrevolutionary'], '') beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/test_utilpep544.py000066400000000000000000000145701461113517100307140ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype :pep:`544` **type hint utility** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.hint.pep.proposal.utilpep544` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ tester }.................... def test_is_hint_pep544_protocol() -> None: ''' Test the private :mod:`beartype._util.hint.pep.proposal.utilpep544.is_hint_pep544_protocol` tester. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from abc import abstractmethod from beartype._util.hint.pep.proposal.utilpep544 import ( is_hint_pep544_protocol) from beartype_test.a00_unit.data.data_type import ( TYPES_BUILTIN, Class, ) # Intentionally import @beartype-accelerated protocols. from beartype.typing import ( Protocol, runtime_checkable, ) # Intentionally import @beartype-unaccelerated protocols. from typing import ( SupportsAbs, SupportsBytes, SupportsComplex, SupportsFloat, SupportsInt, SupportsRound, Union, ) # ....................{ LOCALS }.................... @runtime_checkable class ProtocolCustomUntypevared(Protocol): ''' User-defined protocol parametrized by *NO* type variables declaring arbitrary concrete and abstract methods. ''' def alpha(self) -> str: return 'Of a Spicily sated' @abstractmethod def omega(self) -> str: pass # Set of all PEP 544-compliant "typing" protocols. TYPING_PROTOCOLS = { SupportsAbs, SupportsBytes, SupportsComplex, SupportsFloat, SupportsInt, SupportsRound, } # ....................{ PASS }.................... # Assert this tester rejects builtin types erroneously presenting themselves # as PEP 544-compliant protocols under Python >= 3.8. assert is_hint_pep544_protocol(int) is False assert is_hint_pep544_protocol(str) is False assert is_hint_pep544_protocol(type(None)) is False # Assert this tester rejects a user-defined type. assert is_hint_pep544_protocol(Class) is False # class Yam(object): pass # from typing import Protocol # assert not issubclass(Yam, Protocol) # assert is_hint_pep544_protocol(Yam) is False # Assert this tester accepts all standard PEP 544-compliant protocols. for typing_protocol in TYPING_PROTOCOLS: assert is_hint_pep544_protocol(typing_protocol) is True # Assert this tester rejects all builtin types. For unknown reasons, some # but *NOT* all builtin types (e.g., "int") erroneously present themselves # to be PEP 544-compliant protocols. *sigh* for class_builtin in TYPES_BUILTIN: assert is_hint_pep544_protocol(class_builtin) is False # Assert this tester rejects standard type hints in either case. assert is_hint_pep544_protocol(Union[int, str]) is False # Assert this tester accepts a standardized protocol. assert is_hint_pep544_protocol(SupportsInt) is True # Assert this tester accepts a user-defined protocol. assert is_hint_pep544_protocol(ProtocolCustomUntypevared) is True def test_is_hint_pep544_io_generic() -> None: ''' Test the :func:`beartype._util.hint.pep.proposal.utilpep544.is_hint_pep484_generic_io` tester. ''' # Defer test-specific imports. from beartype.typing import Union from beartype._util.hint.pep.proposal.utilpep544 import ( is_hint_pep484_generic_io) from beartype_test.a00_unit.data.hint.pep.proposal.data_pep484 import ( PEP484_GENERICS_IO) # Assert this tester accepts all standard PEP 484-compliant IO generics. for pep484_generic_io in PEP484_GENERICS_IO: assert is_hint_pep484_generic_io(pep484_generic_io) is True # Assert this tester rejects standard type hints in either case. assert is_hint_pep484_generic_io(Union[int, str]) is False # ....................{ TESTS ~ reducers }.................... def test_reduce_hint_pep484_generic_io_to_pep544_protocol() -> None: ''' Test the :func:`beartype._util.hint.pep.proposal.utilpep544.reduce_hint_pep484_generic_io_to_pep544_protocol` reducer. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar import BeartypeDecorHintPep544Exception from beartype.typing import Union from beartype._util.hint.pep.proposal.utilpep544 import ( reduce_hint_pep484_generic_io_to_pep544_protocol) from beartype._util.hint.pep.proposal.utilpep593 import is_hint_pep593 from beartype_test.a00_unit.data.hint.pep.proposal.data_pep484 import ( PEP484_GENERICS_IO) from pytest import raises from typing import Protocol # ....................{ PASS }.................... # For each PEP 484-compliant "typing" IO generic base class... for pep484_generic_io in PEP484_GENERICS_IO: # Equivalent protocol reduced from this generic. pep544_protocol_io = ( reduce_hint_pep484_generic_io_to_pep544_protocol( pep484_generic_io, '')) # Assert this protocol is either... assert ( # A PEP 593-compliant type metahint generalizing a protocol # *OR*... is_hint_pep593(pep544_protocol_io) or # A PEP 544-compliant protocol. issubclass(pep544_protocol_io, Protocol) ) # ....................{ FAIL }.................... # Assert this function rejects standard type hints in either case. with raises(BeartypeDecorHintPep544Exception): reduce_hint_pep484_generic_io_to_pep544_protocol(Union[int, str], '') beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/test_utilpep557.py000066400000000000000000000031371461113517100307150ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype :pep:`557` **type hint utility** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.hint.pep.proposal.utilpep557` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ getter }.................... def test_get_hint_pep557_initvar_arg() -> None: ''' Test usage of the private :mod:`beartype._util.hint.pep.proposal.utilpep557.get_hint_pep557_initvar_arg` getter. ''' # Defer test-specific imports. from beartype.roar import BeartypeDecorHintPep557Exception from beartype._util.hint.pep.proposal.utilpep557 import ( get_hint_pep557_initvar_arg) from dataclasses import InitVar from pytest import raises # Assert this getter returns the argument subscripting an InitVar. assert get_hint_pep557_initvar_arg(InitVar[str]) is str # Assert this tester raises the expected exception when passed a # non-InitVar. with raises(BeartypeDecorHintPep557Exception): get_hint_pep557_initvar_arg( 'Large codes of fraud and woe; not understood') beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/test_utilpep561.py000066400000000000000000000047361461113517100307160ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype** :pep:`561` **utility unit tests.** This submodule unit tests :pep:`561` support implemented in the :func:`beartype.beartype` decorator. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_pep561_pytyped() -> None: ''' Test :pep:`561` support implemented in the :mod:`beartype` package by asserting that this package provides the ``py.typed`` file required by :pep:`561`. Note that this unit test exercises a necessary but *not* sufficient condition for this package to comply with :pep:`561`. The comparable :mod:`beartype_test.a90_func.pep.test_pep561` submodule defines a functional test exercising the remaining necessary condition: **the absence of static type-checking errors across this package.** ''' # Defer test-specific imports. import beartype from beartype._util.module.utilmodget import get_module_filename from pathlib import Path # Concrete platform-agnostic path encapsulating the absolute filename of # the "beartype.__init__" submodule. # # Note that we intentionally do *NOT* obtain this file via the # test-specific "beartype_test._util.path.pytpathmain" submodule. Why? # Because we want to test that the "py.typed" file is actually being # installed with the "beartype" package, wherever that package may # currently be installed (e.g., to a "tox"-isolated venv). BEARTYPE_INIT_FILENAME = Path(get_module_filename(beartype)) # Concrete platform-agnostic path encapsulating the absolute dirname of # the "beartype" package. BEARTYPE_DIRNAME = BEARTYPE_INIT_FILENAME.parent # Concrete platform-agnostic path encapsulating the "py.typed" file # bundled with the "beartype" package. # # Note that this path has *NOT* been validated to exist yet. BEARTYPE_INIT_FILE = BEARTYPE_DIRNAME.joinpath('py.typed') # Assert this file exists. assert BEARTYPE_INIT_FILE.is_file() beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/test_utilpep585.py000066400000000000000000000100521461113517100307100ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype** :pep:`585` **utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.hint.pep.proposal.utilpep585` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ kind : builtin }.................... def test_is_hint_pep585_builtin(hints_pep_meta) -> None: ''' Test the :func:`beartype._util.hint.pep.proposal.utilpep585.is_hint_pep585_builtin_subscripted` function. Parameters ---------- hints_pep_meta : List[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] List of type hint metadata describing sample type hints exercising edge cases in the :mod:`beartype` codebase. ''' # Defer test-specific imports. from beartype._util.hint.pep.proposal.utilpep585 import ( is_hint_pep585_builtin_subscripted) # Assert this tester accepts only PEP 585-compliant type hints. for hint_pep_meta in hints_pep_meta: assert is_hint_pep585_builtin_subscripted(hint_pep_meta.hint) is ( hint_pep_meta.is_pep585_builtin_subscripted) # ....................{ TESTS ~ kind : generic }.................... def test_is_hint_pep585_generic(hints_pep_meta) -> None: ''' Test the :func:`beartype._util.hint.pep.proposal.utilpep585.is_hint_pep585_generic` function. Parameters ---------- hints_pep_meta : List[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] List of type hint metadata describing sample type hints exercising edge cases in the :mod:`beartype` codebase. ''' # Defer test-specific imports. from beartype._util.hint.pep.proposal.utilpep585 import ( is_hint_pep585_generic) # Assert this tester accepts only PEP 585-compliant generics. for hint_pep_meta in hints_pep_meta: assert is_hint_pep585_generic(hint_pep_meta.hint) is ( hint_pep_meta.is_pep585_generic) def test_get_hint_pep585_generic_typevars(hints_pep_meta) -> None: ''' Test the :func:`beartype._util.hint.pep.proposal.utilpep585.get_hint_pep585_generic_typevars` function. Parameters ---------- hints_pep_meta : List[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] List of type hint metadata describing sample type hints exercising edge cases in the :mod:`beartype` codebase. ''' # Defer test-specific imports. from beartype.roar import BeartypeDecorHintPep585Exception from beartype._util.hint.pep.proposal.utilpep585 import ( get_hint_pep585_generic_typevars) from pytest import raises # Assert this getter... for hint_pep_meta in hints_pep_meta: # If this hint is a PEP 585-compliant generic... if hint_pep_meta.is_pep585_generic: # Tuple of all tupe variables returned by this function. hint_pep_typevars = get_hint_pep585_generic_typevars( hint_pep_meta.hint) # Returns one or more type variables for typevared PEP # 585-compliant generics. if hint_pep_meta.is_typevars: assert isinstance(hint_pep_typevars, tuple) assert hint_pep_typevars # *NO* type variables for untypevared PEP 585-compliant generics. else: assert hint_pep_typevars == () # Raises an exception for objects *NOT* PEP 585-compliant generics. else: with raises(BeartypeDecorHintPep585Exception): get_hint_pep585_generic_typevars(hint_pep_meta.hint) beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/test_utilpep586.py000066400000000000000000000103051461113517100307120ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype** :pep:`586` **utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.hint.pep.proposal.utilpep586` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from pytest import raises # ....................{ TESTS }.................... def test_is_hint_pep586() -> None: ''' Test the :func:`beartype._util.hint.pep.proposal.utilpep586.die_unless_hint_pep586` raiser. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar import BeartypeDecorHintPep586Exception from beartype.typing import Optional from beartype._util.hint.pep.proposal.utilpep586 import ( die_unless_hint_pep586) from beartype._util.api.utilapityping import get_typing_attrs from enum import Enum # ....................{ LOCALS }.................... class _Color(Enum): ''' Arbitrary enumeration to be tested below. ''' RED = 0 # Tuple of arbitrary objects permissible for use as arguments subscripting # PEP 586-compliant "Literal" type hints. # # For conformance, the items of this tuple are copied verbatim from a # prominent PEP 586 example under the "Legal parameters for Literal at type # check time" subsection. _LITERAL_ARGS = ( 26, 0x1A, -4, "hello world", b"hello world", u"hello world", True, _Color.RED, None, ) # ..................{ FACTORIES }.................. # For each PEP 586-compliant "Literal" factory importable from each # currently importable "typing" module... for Literal in get_typing_attrs('Literal'): # ....................{ PASS }.................... # For each object that is a valid literal argument, assert this raiser # raises no exception when passed that factory subscripted by that # argument. for literal_arg in _LITERAL_ARGS: die_unless_hint_pep586(Literal[literal_arg]) # Assert this raiser raises no exception when passed that factory # subscripted by two or more such arguments. die_unless_hint_pep586(Literal[ 26, "hello world", b"hello world", True, _Color.RED, None]) # ....................{ FAIL }.................... # Assert this validator raises the expected exception when passed an # object that is *NOT* a literal. with raises(BeartypeDecorHintPep586Exception): die_unless_hint_pep586(Optional[str]) # Assert this raiser raises the expected exception when passed that # unsubscripted factory. with raises(BeartypeDecorHintPep586Exception): die_unless_hint_pep586(Literal) # Assert this raiser raises the expected exception when passed that # factory subscripted by the empty tuple. with raises(BeartypeDecorHintPep586Exception): die_unless_hint_pep586(Literal[()]) # Assert this raiser raises the expected exception when passed that # factory subscripted by an invalid literal argument. with raises(BeartypeDecorHintPep586Exception): die_unless_hint_pep586(Literal[object()]) # Assert this raiser raises the expected exception when passed that # factory subscripted by one or more valid literal arguments and an # invalid literal argument. with raises(BeartypeDecorHintPep586Exception): die_unless_hint_pep586(Literal[ 26, "hello world", b"hello world", True, object(), _Color.RED]) beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/test_utilpep589.py000066400000000000000000000073631461113517100307270ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype** :pep:`589` **utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.hint.pep.proposal.utilpep589` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_is_hint_pep589() -> None: ''' Test the :beartype._util.hint.pep.proposal.utilpep589.is_hint_pep589` tester. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype._util.hint.pep.proposal.utilpep589 import is_hint_pep589 from beartype_test._util.module.pytmodtyping import ( import_typing_attr_or_none_safe) # ....................{ CLASSES }.................... class NonTypedDict(dict): ''' :class:`dict` subclass defining only one of the requisite three dunder attributes necessarily defined by the :class:`typing.TypedDict` superclass. ''' # Note that: # * The "__annotations__" dunder attribute is intentionally omitted; # that is the *ONLY* dunder attribute guaranteed to be declared by # Python 3.8. # * The "__required_keys__" dunder attribute is also intentionally # omitted; for unknown reasons, Python >= 3.8 implicitly adds an # unwanted "__annotations__" dunder attribute to *ALL* "dict" # subclasses -- including "dict" subclasses annotating *NO* class or # instance variables. Defining both the "__required_keys__" and # "__optional_keys__" dunder attributes here would thus suffice for # this subclass to be erroneously detected as a typed dictionary under # Python >= 3.8. And we will sleep now. This has spiralled into # insanity, folks. __optional_keys__ = () __total__ = True # "typing.TypedDict" superclass imported from either the "typing" or # "typing_extensions" modules if importable *OR* "None" otherwise. TypedDict = import_typing_attr_or_none_safe('TypedDict') # If this superclass exists... if TypedDict is not None: class ThouArtThePath(TypedDict): ''' Arbitrary non-empty typed dictionary annotated to require arbitrary key-value pairs. ''' of_that: str unresting_sound: int # Assert this tester returns true when passed a typed dictionary. assert is_hint_pep589(ThouArtThePath) is True # ....................{ PASS }.................... # Assert this tester returns false when passed a non-class. assert is_hint_pep589( 'Thou art pervaded with that ceaseless motion,') is False # Assert this tester returns false when passed a class *NOT* subclassing # the builtin "dict" type. assert is_hint_pep589(str) is False # Assert this tester returns false when passed the builtin "dict" type. assert is_hint_pep589(dict) is False # Assert this tester returns false when passed a "dict" subclass defining # only two of the requisite three dunder attributes necessarily defined by # the "typing.TypedDict" superclass. assert is_hint_pep589(NonTypedDict) is False beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/test_utilpep593.py000066400000000000000000000152351461113517100307170ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype** :pep:`593` **utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.hint.pep.proposal.utilpep593` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ validators }.................... def test_die_unless_hint_pep593() -> None: ''' Test the :beartype._util.hint.pep.proposal.utilpep593.die_unless_hint_pep593` validator. ''' # Defer test-specific imports. from beartype.roar import BeartypeDecorHintPep593Exception from beartype._util.hint.pep.proposal.utilpep593 import ( die_unless_hint_pep593) from beartype_test._util.module.pytmodtyping import ( import_typing_attr_or_none_safe) from pytest import raises from typing import Optional # "typing.Annotated" type hint factory imported from either the "typing" or # "typing_extensions" modules if importable *OR* "None" otherwise. Annotated = import_typing_attr_or_none_safe('Annotated') # If this factory exists, assert this validator avoids raising exceptions # for a type hint subscripting this factory. if Annotated is not None: die_unless_hint_pep593(Annotated[Optional[str], int]) # Assert this validator raises the expected exception for an arbitrary # PEP-compliant type hint *NOT* subscripting this factory. with raises(BeartypeDecorHintPep593Exception): die_unless_hint_pep593(Optional[str]) # ....................{ TESTS ~ tester }.................... def test_is_hint_pep593_beartype() -> None: ''' Test usage of the private :mod:`beartype._util.hint.pep.proposal.utilpep593.is_hint_pep593_beartype` tester. ''' # Defer test-specific imports. from beartype.roar import ( BeartypeDecorHintPepException, BeartypeValeLambdaWarning, ) from beartype.vale import Is from beartype._util.hint.pep.proposal.utilpep593 import ( is_hint_pep593_beartype) from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_9 from pytest import raises, warns # If the active Python interpreter targets at least Python >= 3.9 and thus # supports PEP 593... if IS_PYTHON_AT_LEAST_3_9: # Defer version-specific imports. from typing import Annotated # Assert this tester accepts beartype-specific metahints. # # Unfortunately, this test actually induces an error in CPython, which # our codebase emits as a non-fatal warning. Specifically, CPython # reports the "func.__code__.co_firstlineno" attribute of the nested # lambda function defined below to be one less than the expected value. # Since this issue is unlikely to be resolved soon *AND* since we have # no means of monkey-patching CPython itself, we acknowledge the # existence of this warning by simply ignoring it. *sigh* with warns(BeartypeValeLambdaWarning): assert is_hint_pep593_beartype(Annotated[ str, Is[lambda text: bool(text)]]) is True # Assert this tester rejects beartype-agnostic metahints. assert is_hint_pep593_beartype(Annotated[ str, 'And may there be no sadness of farewell']) is False # Assert this tester raises the expected exception when passed a # non-metahint in either case. with raises(BeartypeDecorHintPepException): is_hint_pep593_beartype('When I embark;') # ....................{ TESTS ~ getters }.................... def test_get_hint_pep593_metadata() -> None: ''' Test the :beartype._util.hint.pep.proposal.utilpep593.get_hint_pep593_metadata` getter. ''' # Defer test-specific imports. from beartype.roar import BeartypeDecorHintPep593Exception from beartype._util.hint.pep.proposal.utilpep593 import ( get_hint_pep593_metadata) from beartype_test._util.module.pytmodtyping import ( import_typing_attr_or_none_safe) from pytest import raises from typing import Optional # "typing.Annotated" type hint factory imported from either the "typing" or # "typing_extensions" modules if importable *OR* "None" otherwise. Annotated = import_typing_attr_or_none_safe('Annotated') # If this factory exists, assert this getter returns the expected tuple for # an arbitrary PEP 593-compliant type hint. if Annotated is not None: assert get_hint_pep593_metadata(Annotated[ Optional[str], 'Thy', 'caverns', 'echoing', 'to', 'the', "Arve's", 'commotion,' ]) == ( 'Thy', 'caverns', 'echoing', 'to', 'the', "Arve's", 'commotion,') # Assert this getter raises the expected exception for an arbitrary # PEP-compliant type hint *NOT* subscripting this factory. with raises(BeartypeDecorHintPep593Exception): get_hint_pep593_metadata(Optional[str]) def test_get_hint_pep593_metahint() -> None: ''' Test the :beartype._util.hint.pep.proposal.utilpep593.get_hint_pep593_metahint` getter. ''' # Defer test-specific imports. from beartype.roar import BeartypeDecorHintPep593Exception from beartype._util.hint.pep.proposal.utilpep593 import ( get_hint_pep593_metahint) from beartype_test._util.module.pytmodtyping import ( import_typing_attr_or_none_safe) from pytest import raises from typing import Optional # "typing.Annotated" type hint factory imported from either the "typing" or # "typing_extensions" modules if importable *OR* "None" otherwise. Annotated = import_typing_attr_or_none_safe('Annotated') # If this factory exists, assert this getter returns the expected # PEP-compliant type hint for an arbitrary PEP 593-compliant type hint. if Annotated is not None: metahint = Optional[int] assert get_hint_pep593_metahint(Annotated[ metahint, 'A', 'loud', 'lone', 'sound', 'no', 'other', 'sound', 'can', 'tame' ]) is metahint # Assert this getter raises the expected exception for an arbitrary # PEP-compliant type hint *NOT* subscripting this factory. with raises(BeartypeDecorHintPep593Exception): get_hint_pep593_metahint(Optional[str]) beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/test_utilpep604.py000066400000000000000000000031341461113517100307030ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`604` **type hint utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.hint.pep.proposal.utilpep604` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ tester }.................... def test_is_hint_pep604() -> None: ''' Test the private :mod:`beartype._util.hint.pep.proposal.utilpep604.is_hint_pep604` tester. ''' # Defer test-specific imports. from beartype._util.hint.pep.proposal.utilpep604 import is_hint_pep604 from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_10 # If the active Python interpreter targets Python >= 3.10 and thus supports # PEP 604... if IS_PYTHON_AT_LEAST_3_10: # Assert this tester accepts a PEP 604-compliant union. assert is_hint_pep604(int | str | None) is True # Else, this interpreter targets Python < 3.10 and thus fails to support PEP # 604. # Assert this tester rejects an arbitrary PEP 604-noncompliant object. is_hint_pep604('Meet in the vale, and one majestic River,') beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a00_pep/proposal/test_utilpep695.py000066400000000000000000000060171461113517100307200ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`695` **type hint utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.hint.pep.proposal.utilpep695` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ iterator }.................... def test_iter_hint_pep695_forwardrefs() -> None: ''' Test the private :mod:`beartype._util.hint.pep.proposal.utilpep695.iter_hint_pep695_forwardrefs` iterator. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar import BeartypeDecorHintPep695Exception from beartype._util.hint.pep.proposal.utilpep695 import ( iter_hint_pep695_forwardrefs) from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_12 from pytest import raises # If the active Python interpreter targets Python >= 3.12 and thus supports # PEP 695... if IS_PYTHON_AT_LEAST_3_12: # Defer version-specific imports. from beartype_test.a00_unit.data.pep.pep695.data_pep695_util import ( unit_test_iter_hint_pep695_forwardrefs) # Perform this test. unit_test_iter_hint_pep695_forwardrefs() # Else, this interpreter targets Python < 3.12 and thus fails to support PEP # 695. # ....................{ FAIL }.................... # Assert this iterator raises the expected exception when passed an # arbitrary PEP 695-noncompliant object. with raises(BeartypeDecorHintPep695Exception): next(iter_hint_pep695_forwardrefs( 'Tumultuously accorded with those fits')) # ....................{ TESTS ~ reducer }.................... def test_reduce_hint_pep695() -> None: ''' Test the private :mod:`beartype._util.hint.pep.proposal.utilpep695.reduce_hint_pep695` iterator. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_12 # If the active Python interpreter targets Python >= 3.12 and thus supports # PEP 695... if IS_PYTHON_AT_LEAST_3_12: # Defer version-specific imports. from beartype_test.a00_unit.data.pep.pep695.data_pep695_util import ( unit_test_reduce_hint_pep695) # Perform this test. unit_test_reduce_hint_pep695() # Else, this interpreter targets Python < 3.12 and thus fails to support PEP # 695. beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a00_pep/test_a00_utilpepget.py000066400000000000000000000227041461113517100277560ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **PEP-compliant type hint getter** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.hint.pep.utilpepget` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # from beartype_test._util.mark.pytskip import skip_if_python_version_less_than # ....................{ TESTS ~ attr }.................... def test_get_hint_pep_args(hints_pep_meta) -> None: ''' Test the :func:`beartype._util.hint.pep.utilpepget.get_hint_pep_args` getter. Parameters ---------- hints_pep_meta : List[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] List of PEP-compliant type hint metadata describing sample PEP-compliant type hints exercising edge cases in the :mod:`beartype` codebase. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.typing import Tuple from beartype._util.hint.pep.utilpepget import ( _HINT_ARGS_EMPTY_TUPLE, get_hint_pep_args, ) from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_9 from beartype_test.a00_unit.data.hint.data_hint import NOT_HINTS_PEP # ....................{ PASS }.................... # For each PEP-compliant hint, assert this getter returns... for hint_pep_meta in hints_pep_meta: # Tuple of all arguments subscripting this hint. hint_args = get_hint_pep_args(hint_pep_meta.hint) assert isinstance(hint_args, tuple) # For subscripted hints, one or more arguments. if hint_pep_meta.is_args: assert hint_args # For non-argumentative hints, *NO* arguments. else: assert hint_args == () # ....................{ PASS ~ pep }.................... #FIXME: Explicitly validate that this getter handles both PEP 484- and 585- #compliant empty tuples by returning "_HINT_ARGS_EMPTY_TUPLE" as expected, #please. This is sufficiently critical that we *NEED* to ensure this. # Assert that this getter when passed a PEP 484-compliant empty tuple type # hint returns a tuple containing an empty tuple for disambiguity. assert get_hint_pep_args(Tuple[()]) == _HINT_ARGS_EMPTY_TUPLE # If Python >= 3.9, the active Python interpreter supports PEP 585. In this # case, assert that this getter when passed a PEP 585-compliant empty tuple # type hint returns a tuple containing an empty tuple for disambiguity. if IS_PYTHON_AT_LEAST_3_9: assert get_hint_pep_args(tuple[()]) == _HINT_ARGS_EMPTY_TUPLE # ....................{ FAIL }.................... # Assert this getter returns *NO* type variables for non-"typing" hints. for not_hint_pep in NOT_HINTS_PEP: assert get_hint_pep_args(not_hint_pep) == () def test_get_hint_pep_typevars(hints_pep_meta) -> None: ''' Test the :func:`beartype._util.hint.pep.utilpepget.get_hint_pep_typevars` getter. Parameters ---------- hints_pep_meta : List[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] List of PEP-compliant type hint metadata describing sample PEP-compliant type hints exercising edge cases in the :mod:`beartype` codebase. ''' # Defer test-specific imports. from beartype._data.hint.pep.sign.datapepsigns import HintSignTypeVar from beartype._util.hint.pep.utilpepget import ( get_hint_pep_typevars, get_hint_pep_sign_or_none, ) from beartype_test.a00_unit.data.hint.data_hint import NOT_HINTS_PEP # For each PEP-compliant hint, assert this getter returns... for hint_pep_meta in hints_pep_meta: # Tuple of all type variables subscripting this hint. hint_typevars = get_hint_pep_typevars(hint_pep_meta.hint) assert isinstance(hint_typevars, tuple) # For typevared hints, one or more type variables. if hint_pep_meta.is_typevars: assert hint_typevars for hint_typevar in hint_typevars: assert get_hint_pep_sign_or_none(hint_typevar) is ( HintSignTypeVar) # For non-typevared hints, *NO* type variables. else: assert hint_typevars == () # Assert this getter returns *NO* type variables for non-"typing" hints. for not_hint_pep in NOT_HINTS_PEP: assert get_hint_pep_typevars(not_hint_pep) == () # ....................{ TESTS ~ sign }.................... def test_get_hint_pep_sign(hints_pep_meta) -> None: ''' Test the :func:`beartype._util.hint.pep.utilpepget.get_hint_pep_sign` getter. Parameters ---------- hints_pep_meta : List[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] List of PEP-compliant type hint metadata describing sample PEP-compliant type hints exercising edge cases in the :mod:`beartype` codebase. ''' # Defer test-specific imports. from beartype.roar import BeartypeDecorHintPepSignException from beartype._util.hint.pep.utilpepget import get_hint_pep_sign from beartype_test.a00_unit.data.hint.data_hint import ( HINTS_NONPEP, NonpepCustomFakeTyping) from pytest import raises # Assert this getter returns the expected unsubscripted "typing" attribute # for all PEP-compliant type hints associated with such an attribute. for hint_pep_meta in hints_pep_meta: assert get_hint_pep_sign(hint_pep_meta.hint) is hint_pep_meta.pep_sign # Assert this getter raises the expected exception for an instance of a # class erroneously masquerading as a "typing" class. with raises(BeartypeDecorHintPepSignException): # Localize this return value to simplify debugging. hint_nonpep_sign = get_hint_pep_sign(NonpepCustomFakeTyping()) # Assert this getter raises the expected exception for non-"typing" hints. for hint_nonpep in HINTS_NONPEP: with raises(BeartypeDecorHintPepSignException): # Localize this return value to simplify debugging. hint_nonpep_sign = get_hint_pep_sign(hint_nonpep) # ....................{ TESTS ~ origin : type }.................... def test_get_hint_pep_type_isinstanceable(hints_pep_meta) -> None: ''' Test the :func:`beartype._util.hint.pep.utilpepget.get_hint_pep_origin_type_isinstanceable` getter. Parameters ---------- hints_pep_meta : List[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] List of PEP-compliant type hint metadata describing sample PEP-compliant type hints exercising edge cases in the :mod:`beartype` codebase. ''' # Defer test-specific imports. from beartype.roar import BeartypeDecorHintPepException from beartype._util.hint.pep.utilpepget import ( get_hint_pep_origin_type_isinstanceable) from beartype_test.a00_unit.data.hint.data_hint import NOT_HINTS_PEP from pytest import raises # Assert this getter... for hint_pep_meta in hints_pep_meta: # Returns the expected type origin for all PEP-compliant type hints # originating from an origin type. if hint_pep_meta.isinstanceable_type is not None: assert get_hint_pep_origin_type_isinstanceable(hint_pep_meta.hint) is ( hint_pep_meta.isinstanceable_type) # Raises the expected exception for all other hints. else: with raises(BeartypeDecorHintPepException): get_hint_pep_origin_type_isinstanceable(hint_pep_meta.hint) # Assert this getter raises the expected exception for non-PEP-compliant # type hints. for not_hint_pep in NOT_HINTS_PEP: with raises(BeartypeDecorHintPepException): get_hint_pep_origin_type_isinstanceable(not_hint_pep) def test_get_hint_pep_type_isinstanceable_or_none(hints_pep_meta) -> None: ''' Test the :func:`beartype._util.hint.pep.utilpepget.get_hint_pep_origin_type_isinstanceable_or_none` getter. Parameters ---------- hints_pep_meta : List[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] List of PEP-compliant type hint metadata describing sample PEP-compliant type hints exercising edge cases in the :mod:`beartype` codebase. ''' # Defer test-specific imports. from beartype._util.hint.pep.utilpepget import ( get_hint_pep_origin_type_isinstanceable_or_none) from beartype_test.a00_unit.data.hint.data_hint import NOT_HINTS_PEP # Assert this getter returns the expected type origin for all PEP-compliant # type hints. for hint_pep_meta in hints_pep_meta: assert get_hint_pep_origin_type_isinstanceable_or_none( hint_pep_meta.hint) is hint_pep_meta.isinstanceable_type # Assert this getter returns "None" for non-PEP-compliant type hints. for not_hint_pep in NOT_HINTS_PEP: assert get_hint_pep_origin_type_isinstanceable_or_none( not_hint_pep) is None beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a00_pep/test_a90_utilpeptest.py000066400000000000000000000271611461113517100301710ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **PEP-compliant type hint tester** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.hint.pep.utilpeptest` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from pytest import raises # ....................{ TESTS }.................... # Fine-grained tests are intentionally performed *BEFORE* coarse-grained tests, # dramatically improving readability of test failures. # ....................{ TESTS ~ kind : typevar }.................... def test_is_hint_pep_typevars(hints_pep_meta) -> None: ''' Test the :func:`beartype._util.hint.pep.utilpeptest.is_hint_pep_typevars` tester. Parameters ---------- hints_pep_meta : List[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] List of PEP-compliant type hint metadata describing sample PEP-compliant type hints exercising edge cases in the :mod:`beartype` codebase. ''' # Defer test-specific imports. from beartype._util.hint.pep.utilpeptest import is_hint_pep_typevars from beartype_test.a00_unit.data.hint.data_hint import HINTS_NONPEP # Assert that various "TypeVar"-centric types are correctly detected. for hint_pep_meta in hints_pep_meta: assert is_hint_pep_typevars(hint_pep_meta.hint) is ( hint_pep_meta.is_typevars) # Assert that various "TypeVar"-agnostic types are correctly detected. for nonhint_pep in HINTS_NONPEP: assert is_hint_pep_typevars(nonhint_pep) is False # ....................{ TESTS ~ typing }.................... def test_is_hint_pep_typing(hints_pep_meta) -> None: ''' Test the :func:`beartype._util.hint.pep.utilpeptest.is_hint_pep_typing` tester. Parameters ---------- hints_pep_meta : List[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] List of PEP-compliant type hint metadata describing sample PEP-compliant type hints exercising edge cases in the :mod:`beartype` codebase. ''' # Defer test-specific imports. from beartype._util.hint.pep.utilpeptest import ( is_hint_pep_typing) from beartype_test.a00_unit.data.hint.data_hint import NOT_HINTS_PEP # Assert this tester accepts PEP-compliant type hints defined by the # "typing" module. for hint_pep_meta in hints_pep_meta: assert is_hint_pep_typing(hint_pep_meta.hint) is ( hint_pep_meta.is_typing) # Assert this tester rejects non-PEP-compliant type hints. for not_hint_pep in NOT_HINTS_PEP: assert is_hint_pep_typing(not_hint_pep) is False def test_is_hint_pep_type_typing(hints_pep_meta) -> None: ''' Test the :func:`beartype._util.hint.pep.utilpeptest.is_hint_pep_type_typing` tester. Parameters ---------- hints_pep_meta : List[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] List of PEP-compliant type hint metadata describing sample PEP-compliant type hints exercising edge cases in the :mod:`beartype` codebase. ''' # Defer test-specific imports. from beartype._util.hint.pep.utilpeptest import ( is_hint_pep_type_typing) from beartype_test.a00_unit.data.hint.data_hint import NOT_HINTS_PEP # Assert this tester accepts PEP-compliant type hints defined by the # "typing" module. for hint_pep_meta in hints_pep_meta: assert is_hint_pep_type_typing(hint_pep_meta.hint) is ( hint_pep_meta.is_type_typing) # Assert this tester rejects non-PEP-compliant type hints. for not_hint_pep in NOT_HINTS_PEP: assert is_hint_pep_type_typing(not_hint_pep) is False # ....................{ TESTS }.................... def test_is_hint_pep(hints_pep_meta, hints_nonpep_meta) -> None: ''' Test the :func:`beartype._util.hint.pep.utilpeptest.is_hint_pep` tester. Parameters ---------- hints_pep_meta : List[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] List of PEP-compliant type hint metadata describing sample PEP-compliant type hints exercising edge cases in the :mod:`beartype` codebase. hints_nonpep_meta : Tuple[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintNonpepMetadata] Tuple of PEP-noncompliant type hint metadata describing PEP-noncompliant type hints exercising edge cases in the :mod:`beartype` codebase. ''' # Defer test-specific imports. from beartype._util.hint.pep.utilpeptest import is_hint_pep from beartype_test.a00_unit.data.hint.data_hint import NOT_HINTS_PEP # Assert this tester accepts PEP-compliant type hints. for hint_pep_meta in hints_pep_meta: assert is_hint_pep(hint_pep_meta.hint) is True # Assert this tester rejects PEP-noncompliant type hints implemented by the # "typing" module as normal types indistinguishable from non-"typing" types # and thus effectively non-PEP-compliant for all practical intents. for hint_nonpep_meta in hints_nonpep_meta: # if is_hint_pep(hint_nonpep_meta.hint) is True: # from beartype._util.hint.pep.utilpepget import ( # get_hint_pep_sign_or_none) # hint = hint_nonpep_meta.hint # print(f'hint {hint} sign: {get_hint_pep_sign_or_none(hint)}') assert is_hint_pep(hint_nonpep_meta.hint) is False # Assert this tester rejects non-PEP-compliant type hints. for not_hint_pep in NOT_HINTS_PEP: assert is_hint_pep(not_hint_pep) is False def test_is_hint_pep_args(hints_pep_meta) -> None: ''' Test the :func:`beartype._util.hint.pep.utilpeptest.is_hint_pep_args` tester. Parameters ---------- hints_pep_meta : List[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] List of PEP-compliant type hint metadata describing sample PEP-compliant type hints exercising edge cases in the :mod:`beartype` codebase. ''' # Defer test-specific imports. from beartype._util.hint.pep.utilpeptest import is_hint_pep_args from beartype_test.a00_unit.data.hint.data_hint import NOT_HINTS_PEP # Assert this tester accepts PEP-compliant subscripted type hints. for hint_pep_meta in hints_pep_meta: assert is_hint_pep_args(hint_pep_meta.hint) is ( hint_pep_meta.is_args) # Assert this tester rejects non-PEP-compliant type hints. for not_hint_pep in NOT_HINTS_PEP: assert is_hint_pep_args(not_hint_pep) is False #FIXME: Implement us up, please. # def test_is_hint_pep_uncached() -> None: # ''' # Test the # :func:`beartype._util.hint.pep.utilpeptest.is_hint_pep_uncached` # tester. # ''' # # # Defer test-specific imports. # from beartype._util.hint.pep.utilpeptest import is_hint_pep_uncached # from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_9 # from beartype_test.a00_unit.data.hint.data_hint import NOT_HINTS_PEP # from beartype_test.a00_unit.data.hint.pep.data_pep import ( # HINTS_PEP_META) # # # Assert this tester accepts concrete PEP-compliant type hints. # for hint_pep_meta in HINTS_PEP_META: # # True only if we expect this hint to be non-self-cached, including. # is_hint_pep_uncached_expected = ( # # If th # hint_pep_meta.is_pep585_builtin_subscripted or # ( # IS_PYTHON_AT_LEAST_3_9 and # hint_pep_meta # ) # ) # # assert is_hint_pep_uncached(hint_pep_meta.hint) is ( # is_hint_pep_uncached_expected) # # # Assert this tester accepts non-PEP-compliant type hints. What? Look, # # folks. This tester should probably raise an exception when passed those # # sort of hints, but this tester *CANNOT* by definition be memoized, which # # means it needs to be fast despite being unmemoized, which means we treat # # *ALL* objects other than a small well-known subset of non-self-cached # # PEP-compliant type hints as self-cached PEP-compliant type hints. *shrug* # for not_hint_pep in NOT_HINTS_PEP: # assert is_hint_pep_uncached(not_hint_pep) is True # ....................{ TESTS ~ supported }.................... def test_is_hint_pep_supported(hints_pep_meta) -> None: ''' Test the :func:`beartype._util.hint.pep.utilpeptest.is_hint_pep_supported` tester. Parameters ---------- hints_pep_meta : List[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] List of PEP-compliant type hint metadata describing sample PEP-compliant type hints exercising edge cases in the :mod:`beartype` codebase. ''' # Defer test-specific imports. from beartype._util.hint.pep.utilpeptest import is_hint_pep_supported from beartype_test.a00_unit.data.hint.data_hint import ( NOT_HINTS_PEP, NOT_HINTS_UNHASHABLE, ) # Assert this tester: # * Accepts supported PEP-compliant type hints. # * Rejects unsupported PEP-compliant type hints. for hint_pep_meta in hints_pep_meta: assert is_hint_pep_supported(hint_pep_meta.hint) is ( hint_pep_meta.is_supported) # Assert this tester rejects objects that are *NOT* PEP-noncompliant. for not_hint_pep in NOT_HINTS_PEP: assert is_hint_pep_supported(not_hint_pep) is False # Assert this tester rejects unhashable objects. for non_hint_unhashable in NOT_HINTS_UNHASHABLE: assert is_hint_pep_supported(non_hint_unhashable) is False def test_die_if_hint_pep_unsupported(hints_pep_meta) -> None: ''' Test the :func:`beartype._util.hint.pep.utilpeptest.die_if_hint_pep_unsupported` validator. Parameters ---------- hints_pep_meta : List[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] List of PEP-compliant type hint metadata describing sample PEP-compliant type hints exercising edge cases in the :mod:`beartype` codebase. ''' # Defer test-specific imports. from beartype.roar import ( BeartypeDecorHintPepException, BeartypeDecorHintPepUnsupportedException, ) from beartype._util.hint.pep.utilpeptest import ( die_if_hint_pep_unsupported) from beartype_test.a00_unit.data.hint.data_hint import ( NOT_HINTS_UNHASHABLE, NOT_HINTS_PEP) # Assert this validator... for hint_pep_meta in hints_pep_meta: # Accepts supported PEP-compliant type hints. if hint_pep_meta.is_supported: die_if_hint_pep_unsupported(hint_pep_meta.hint) # Rejects unsupported PEP-compliant type hints. else: with raises(BeartypeDecorHintPepUnsupportedException): die_if_hint_pep_unsupported(hint_pep_meta.hint) # Assert this validator rejects objects that are *NOT* PEP-noncompliant. for not_hint_pep in NOT_HINTS_PEP: with raises(BeartypeDecorHintPepException): die_if_hint_pep_unsupported(not_hint_pep) # Assert this validator rejects unhashable objects. for non_hint_unhashable in NOT_HINTS_UNHASHABLE: with raises(BeartypeDecorHintPepException): die_if_hint_pep_unsupported(non_hint_unhashable) beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a10_nonpep/000077500000000000000000000000001461113517100242325ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a10_nonpep/__init__.py000066400000000000000000000000001461113517100263310ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a10_nonpep/test_utilhintnonpeptest.py000066400000000000000000000106321461113517100316250ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype PEP-noncompliant type hint utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.hint.nonpep.utilnonpeptest` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ validator }.................... def test_die_unless_hint_nonpep(not_hints_nonpep) -> None: ''' Test the :func:`beartype._util.hint.nonpep.utilnonpeptest.die_unless_hint_nonpep` validator. Parameters ---------- not_hints_nonpep : frozenset Frozen set of various objects that are *not* PEP-noncompliant type hints exercising well-known edge cases. ''' # Defer test-specific imports. from beartype.roar import BeartypeDecorHintNonpepException from beartype._util.hint.nonpep.utilnonpeptest import ( die_unless_hint_nonpep) from beartype_test.a00_unit.data.hint.data_hint import ( HINTS_NONPEP, NOT_HINTS_UNHASHABLE, ) from pytest import raises # Assert this function accepts PEP-noncompliant type hints. for hint_nonpep in HINTS_NONPEP: die_unless_hint_nonpep(hint_nonpep) # Assert this function rejects objects excepted to be rejected. for not_hint_nonpep in not_hints_nonpep: with raises(BeartypeDecorHintNonpepException): die_unless_hint_nonpep(not_hint_nonpep) # Assert this function rejects unhashable objects. for not_hint_unhashable in NOT_HINTS_UNHASHABLE: with raises(BeartypeDecorHintNonpepException): die_unless_hint_nonpep(not_hint_unhashable) # ....................{ TESTS ~ tester }.................... def test_is_hint_nonpep(not_hints_nonpep) -> None: ''' Test the :func:`beartype._util.hint.nonpep.utilnonpeptest.is_hint_nonpep` tester. Parameters ---------- not_hints_nonpep : frozenset Frozen set of various objects that are *not* PEP-noncompliant type hints exercising well-known edge cases. ''' # Defer test-specific imports. from beartype._util.hint.nonpep.utilnonpeptest import is_hint_nonpep from beartype_test.a00_unit.data.hint.data_hint import ( HINTS_NONPEP, NOT_HINTS_UNHASHABLE, ) # Assert this function accepts PEP-noncompliant type hints. for hint_nonpep in HINTS_NONPEP: assert is_hint_nonpep(hint=hint_nonpep, is_str_valid=True) is True # Assert this function rejects objects excepted to be rejected. for not_hint_nonpep in not_hints_nonpep: assert is_hint_nonpep(hint=not_hint_nonpep, is_str_valid=True) is False # Assert this function rejects unhashable objects. for non_hint_unhashable in NOT_HINTS_UNHASHABLE: assert is_hint_nonpep( hint=non_hint_unhashable, is_str_valid=True) is False def test_is_hint_nonpep_tuple(not_hints_nonpep) -> None: ''' Test the :func:`beartype._util.hint.nonpep.utilnonpeptest._is_hint_nonpep_tuple` tester. Parameters ---------- not_hints_nonpep : frozenset Frozen set of various objects that are *not* PEP-noncompliant type hints exercising well-known edge cases. ''' # Defer test-specific imports. from beartype._util.hint.nonpep.utilnonpeptest import _is_hint_nonpep_tuple from beartype_test.a00_unit.data.hint.data_hint import ( HINTS_NONPEP, NOT_HINTS_UNHASHABLE, ) # Assert this function accepts PEP-noncompliant tuples. for hint_nonpep in HINTS_NONPEP: assert _is_hint_nonpep_tuple(hint_nonpep, True) is isinstance( hint_nonpep, tuple) # Assert this function rejects objects excepted to be rejected. for not_hint_nonpep in not_hints_nonpep: assert _is_hint_nonpep_tuple(not_hint_nonpep, True) is False # Assert this function rejects unhashable objects. for non_hint_unhashable in NOT_HINTS_UNHASHABLE: assert _is_hint_nonpep_tuple(non_hint_unhashable, True) is False beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a90_core/000077500000000000000000000000001461113517100236735ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a90_core/__init__.py000066400000000000000000000000001461113517100257720ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a90_core/test_utilhintfactory.py000066400000000000000000000026641461113517100305440ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype PEP-agnostic type hint factory** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.hint.utilhintfactory` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_typehinttypefactory() -> None: ''' Test the :func:`beartype._util.hint.utilhintfactory.TypeHintTypeFactory` class. ''' # Defer test-specific imports. from beartype._util.hint.utilhintfactory import TypeHintTypeFactory # Arbitrary instance of this factory. bool_factory = TypeHintTypeFactory(bool) # Assert that this class memoizes this factory on the passed type. assert bool_factory is TypeHintTypeFactory(bool) # Assert that this factory unconditionally returns this type when # subscripted by any arbitrary object. assert bool_factory['The still and solemn power of many sights,'] is bool beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a90_core/test_utilhintget.py000066400000000000000000000060471461113517100276530ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype PEP-agnostic type hint getter utility** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.hint.utilhintget` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_get_hint_repr(hints_pep_meta) -> None: ''' Test the :func:`beartype._util.hint.utilhintget.get_hint_repr` getter. Parameters ---------- hints_pep_meta : List[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] List of type hint metadata describing sample type hints exercising edge cases in the :mod:`beartype` codebase. ''' # Defer test-specific imports. from beartype._util.hint.utilhintget import get_hint_repr from beartype_test.a00_unit.data.hint.data_hint import ( NOT_HINTS, HINTS_NONPEP) # Assert this getter returns the expected representations of objects *NOT* # supported as either PEP-noncompliant or -compliant type hints. for non_hint in NOT_HINTS: assert get_hint_repr(non_hint) == repr(non_hint) # Assert this getter returns the expected representations of # PEP-noncompliant type hints. for nonhint_pep in HINTS_NONPEP: assert get_hint_repr(nonhint_pep) == repr(nonhint_pep) # Assert this getter returns the expected representations of PEP-compliant # type hints. for hint_pep_meta in hints_pep_meta: # PEP-compliant type hint to be tested. hint_pep = hint_pep_meta.hint # Representation of this hint. hint_pep_repr = repr(hint_pep) # If this representation embeds the representation of a type union, # silently ignore this hint and continue to the next. Why? Because # Python intentionally hashes both PEP 484-compliant type unions (e.g., # "typing.Union[int, str]") *AND* PEP 604-compliant type unions (e.g., # "int | str") to the same integers. However, these two kinds of unions # have fundamentally different representations. # # Specifically, ignore this hint if this representation embeds the # representation of either... if ( # A PEP 484-compliant type union. 'typing.Union[' in hint_pep_repr or # A PEP 604-compliant type union. ' | ' in hint_pep_repr ): continue # Else, this representation contains *NO* type union. # Assert this getter returns the expected representation. assert get_hint_repr(hint_pep) == repr(hint_pep) beartype-0.18.5/beartype_test/a00_unit/a20_util/hint/a90_core/test_utilhinttest.py000066400000000000000000000157221461113517100300530ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype PEP-agnostic type hint tester utility** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.hint.utilhinttest` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ raiser }.................... def test_die_unless_hint(hints_pep_meta) -> None: ''' Test the :func:`beartype._util.hint.utilhinttest.die_unless_hint` raiser. Parameters ---------- hints_pep_meta : List[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] List of type hint metadata describing sample type hints exercising edge cases in the :mod:`beartype` codebase. ''' # Defer test-specific imports. from beartype.roar import ( BeartypeDecorHintNonpepException, BeartypeDecorHintPepUnsupportedException, ) from beartype._util.hint.utilhinttest import die_unless_hint from beartype_test.a00_unit.data.hint.data_hint import ( HINTS_NONPEP, NOT_HINTS, ) from pytest import raises # Assert this function accepts PEP-noncompliant type hints. for nonhint_pep in HINTS_NONPEP: die_unless_hint(nonhint_pep) # Assert this function... for hint_pep_meta in hints_pep_meta: # Accepts supported PEP-compliant type hints. if hint_pep_meta.is_supported: die_unless_hint(hint_pep_meta.hint) # Rejects unsupported PEP-compliant type hints. else: with raises(BeartypeDecorHintPepUnsupportedException): die_unless_hint(hint_pep_meta.hint) # Assert this function rejects objects *NOT* supported as either # PEP-noncompliant or -compliant type hints. for non_hint in NOT_HINTS: with raises(BeartypeDecorHintNonpepException): die_unless_hint(non_hint) # ....................{ TESTS ~ tester }.................... def test_is_hint(hints_pep_meta) -> None: ''' Test the :func:`beartype._util.hint.utilhinttest.is_hint` tester. Parameters ---------- hints_pep_meta : List[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] List of type hint metadata describing sample type hints exercising edge cases in the :mod:`beartype` codebase. ''' # Defer test-specific imports. from beartype._util.hint.utilhinttest import is_hint from beartype_test.a00_unit.data.hint.data_hint import ( HINTS_NONPEP, NOT_HINTS, ) # Assert this tester accepts PEP-noncompliant type hints. for nonhint_pep in HINTS_NONPEP: assert is_hint(nonhint_pep) is True # Assert this tester: # * Accepts supported PEP-compliant type hints. # * Rejects unsupported PEP-compliant type hints. for hint_pep_meta in hints_pep_meta: assert is_hint(hint_pep_meta.hint) is hint_pep_meta.is_supported # Assert this tester rejects objects *NOT* supported as either # PEP-noncompliant or -compliant type hints. for non_hint in NOT_HINTS: assert is_hint(non_hint) is False # Prevent pytest from capturing and displaying all expected non-fatal # beartype-specific warnings emitted by the is_hint_ignorable() tester. # @ignore_warnings(BeartypeDecorHintPepIgnorableDeepWarning) def test_is_hint_ignorable(hints_pep_meta, hints_ignorable) -> None: ''' Test the :func:`beartype._util.hint.utilhinttest.is_hint_ignorable` tester. Parameters ---------- hints_pep_meta : tuple[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] Tuple of type hint metadata describing sample type hints exercising edge cases in the :mod:`beartype` codebase. hints_ignorable : frozenset Frozen set of ignorable PEP-agnostic type hints. ''' # Defer test-specific imports. from beartype._util.hint.utilhinttest import is_hint_ignorable from beartype_test.a00_unit.data.hint.data_hint import ( HINTS_NONPEP_UNIGNORABLE) # Assert this tester accepts ignorable type hints. for hint_ignorable in hints_ignorable: assert is_hint_ignorable(hint_ignorable) is True # Assert this tester rejects unignorable PEP-noncompliant type hints. for hint_unignorable in HINTS_NONPEP_UNIGNORABLE: assert is_hint_ignorable(hint_unignorable) is False # Assert this tester: # * Accepts unignorable PEP-compliant type hints. # * Rejects ignorable PEP-compliant type hints. for hint_pep_meta in hints_pep_meta: assert is_hint_ignorable(hint_pep_meta.hint) is ( hint_pep_meta.is_ignorable) # ....................{ TESTS ~ tester : needs }.................... def test_is_hint_needs_cls_stack(hints_pep_meta) -> None: ''' Test the :func:`beartype._util.hint.utilhinttest.is_hint_needs_cls_stack` tester. Parameters ---------- hints_pep_meta : List[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] List of type hint metadata describing sample type hints exercising edge cases in the :mod:`beartype` codebase. ''' # Defer test-specific imports. from beartype._util.hint.utilhinttest import is_hint_needs_cls_stack from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_11 from beartype_test.a00_unit.data.hint.data_hint import ( HINTS_NONPEP, NOT_HINTS, ) # Assert this tester rejects *ALL* PEP-noncompliant type hints. for nonhint_pep in HINTS_NONPEP: assert is_hint_needs_cls_stack(nonhint_pep) is False # Assert this tester rejects *ALL* type stack-independent PEP-compliant type # hints. for hint_pep_meta in hints_pep_meta: assert is_hint_needs_cls_stack(hint_pep_meta.hint) is False # Assert this tester rejects objects *NOT* supported as either # PEP-noncompliant or -compliant type hints. for non_hint in NOT_HINTS: assert is_hint_needs_cls_stack(non_hint) is False # If the active Python interpreter targets Python >= 3.11 and thus supports # PEP 673 (i.e., "typing.Self")... if IS_PYTHON_AT_LEAST_3_11: # Defer version-specific imports. from beartype.typing import List, Self # Assert this tester returns true for: # * The PEP 673-compliant self type hint singleton (i.e., "Self"). # * An unrelated parent type hint subscripted by the PEP 673-compliant # self type hint singleton (e.g., "List[Self]"). assert is_hint_needs_cls_stack(Self) is True assert is_hint_needs_cls_stack(List[Self]) is True beartype-0.18.5/beartype_test/a00_unit/a20_util/kind/000077500000000000000000000000001461113517100222555ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/kind/__init__.py000066400000000000000000000000001461113517100243540ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/kind/map/000077500000000000000000000000001461113517100230325ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/kind/map/__init__.py000066400000000000000000000000001461113517100251310ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/kind/map/test_utilmapfrozen.py000066400000000000000000000116751461113517100273540ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **dictionary mutator** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.kind.map.utilmapset` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_frozendict() -> None: ''' Test the :func:`beartype._util.kind.map.utilmapfrozen.FrozenDict` subclass. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar import BeartypeKindFrozenDictException from beartype._util.kind.map.utilmapfrozen import FrozenDict from pickle import ( HIGHEST_PROTOCOL, dumps, loads, ) from pytest import raises # ....................{ LOCALS }.................... # Arbitrary non-empty frozen dictionaries. THE_POET = FrozenDict({ 'The Poet wandering on': 'through Arabie', 'And Persia,': 'and the wild Carmanian waste,', }) ITS_LONELIEST_DELL = FrozenDict({ 'Its loneliest dell,': 'where odorous plants entwine', 'Beneath the hollow rocks': 'a natural bower,', }) # Frozen dictionary containing all key-value pairs in the above two. BESIDE_A_SPARKLING_RIVULET = FrozenDict({ 'The Poet wandering on': 'through Arabie', 'And Persia,': 'and the wild Carmanian waste,', 'Its loneliest dell,': 'where odorous plants entwine', 'Beneath the hollow rocks': 'a natural bower,', }) # ....................{ PASS }.................... # Assert that frozen dictionaries are hashable and preserve the same hash # value across consecutive hashings. assert hash(THE_POET) == hash(THE_POET) assert hash(ITS_LONELIEST_DELL) == hash(ITS_LONELIEST_DELL) # Assert that two different frozen dictionaries are both unequal *AND* hash # to different hashings. assert THE_POET != ITS_LONELIEST_DELL assert hash(THE_POET) != hash(ITS_LONELIEST_DELL) # Assert that the machine-readable representation of frozen dictionaries is # prefixed by the classname of these dictionaries followed by the key-value # pairs of these dictionaries. THE_POET_REPR = repr(THE_POET) assert isinstance(THE_POET_REPR, str) assert THE_POET_REPR.startswith(THE_POET.__class__.__name__) assert "'The Poet wandering on': 'through Arabie'" in THE_POET_REPR # Assert that pickling a frozen dictionary into a byte stream and then # unpickling that byte stream back into a frozen dictionary preserves that # dictionary as is. THE_POET_PICKLED = dumps(THE_POET, protocol=HIGHEST_PROTOCOL) THE_POET_UNPICKLED = loads(THE_POET_PICKLED) assert THE_POET_UNPICKLED == THE_POET # Assert that uniting two frozen dictionaries produces a third frozen # dictionary containing all key-value pairs in the first two. HE_STRETCHED = THE_POET | ITS_LONELIEST_DELL assert BESIDE_A_SPARKLING_RIVULET == HE_STRETCHED # Assert that the dict.fromkeys() class method behaves as expected. THE_POET_FROMKEYS = THE_POET.fromkeys( ('The Poet wandering on',), 'His languid limbs. A vision on his sleep',) assert THE_POET_FROMKEYS == FrozenDict({ 'The Poet wandering on': 'His languid limbs. A vision on his sleep',}) # ....................{ FAIL }.................... # Assert that that standard "dict" methods attempting to mutate the current # frozen dictionary raise the expected exception. with raises(BeartypeKindFrozenDictException): THE_POET["And o'er"] = 'the aërial mountains which pour down' with raises(BeartypeKindFrozenDictException): del THE_POET['The Poet wandering on'] with raises(BeartypeKindFrozenDictException): THE_POET.clear() with raises(BeartypeKindFrozenDictException): THE_POET.pop('The Poet wandering on') with raises(BeartypeKindFrozenDictException): THE_POET.pop('And Persia,', 'Indus and Oxus from their icy caves,') with raises(BeartypeKindFrozenDictException): THE_POET.popitem() with raises(BeartypeKindFrozenDictException): THE_POET.setdefault('The Poet wandering on') with raises(BeartypeKindFrozenDictException): THE_POET.setdefault('In joy and exultation', 'held his way;') with raises(BeartypeKindFrozenDictException): THE_POET.update({'Till in the vale of Cashmire,': 'far within'}) beartype-0.18.5/beartype_test/a00_unit/a20_util/kind/map/test_utilmapset.py000066400000000000000000000127341461113517100266410ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **dictionary mutator** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.kind.map.utilmapset` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ updaters }.................... def test_update_mapping() -> None: ''' Test the :func:`beartype._util.kind.map.utilmapset.update_mapping` function. ''' # Defer test-specific imports. from beartype._util.kind.map.utilmapset import update_mapping from beartype_test.a00_unit.data.kind.data_kindmap import ( FAREWELL_O_HIAWATHA, IN_THE_LODGE_OF_HIAWATHA, IN_THE_LODGE_OF_HIAWATHA_FAREWELL_O_HIAWATHA, THE_SONG_OF_HIAWATHA, THE_SONG_OF_HIAWATHA_IN_THE_LODGE_OF_HIAWATHA, ) # Shallow copies of arbitrary mappings to be modified below. farewell_o_hiawatha = FAREWELL_O_HIAWATHA.copy() the_song_of_hiawatha = THE_SONG_OF_HIAWATHA.copy() # Assert this updater preserves this mapping when updated from an empty # mapping. update_mapping(farewell_o_hiawatha, {}) assert farewell_o_hiawatha == FAREWELL_O_HIAWATHA # Assert this updater updates this mapping as expected when updating from a # non-empty mapping containing no key or key-value collisions. update_mapping(the_song_of_hiawatha, IN_THE_LODGE_OF_HIAWATHA) assert the_song_of_hiawatha == ( THE_SONG_OF_HIAWATHA_IN_THE_LODGE_OF_HIAWATHA) # Assert this updater updates this mapping as expected when updating from a # non-empty mapping containing multiple key but no key-value collisions. update_mapping(farewell_o_hiawatha, IN_THE_LODGE_OF_HIAWATHA) assert farewell_o_hiawatha == IN_THE_LODGE_OF_HIAWATHA_FAREWELL_O_HIAWATHA # ....................{ TESTS ~ mergers }.................... def test_merge_mappings_two() -> None: ''' Test the :func:`beartype._util.kind.map.utilmapset.merge_mappings` function passed exactly two mappings. ''' # Defer test-specific imports. from beartype._util.kind.map.utilmapset import merge_mappings from beartype_test.a00_unit.data.kind.data_kindmap import ( FAREWELL_O_HIAWATHA, IN_THE_LODGE_OF_HIAWATHA, IN_THE_LODGE_OF_HIAWATHA_FAREWELL_O_HIAWATHA, THE_SONG_OF_HIAWATHA, THE_SONG_OF_HIAWATHA_IN_THE_LODGE_OF_HIAWATHA, ) # Assert this function merges two empty mappings into a new empty mapping. assert merge_mappings({}, {}) == {} # Assert this function merges a non-empty mapping and an empty mapping into # the same non-empty mapping. assert merge_mappings(THE_SONG_OF_HIAWATHA, {}) == THE_SONG_OF_HIAWATHA assert merge_mappings({}, THE_SONG_OF_HIAWATHA) == THE_SONG_OF_HIAWATHA # Assert this function merges two non-empty mappings containing no key or # key-value collisions into the expected mapping. assert merge_mappings(THE_SONG_OF_HIAWATHA, IN_THE_LODGE_OF_HIAWATHA) == ( THE_SONG_OF_HIAWATHA_IN_THE_LODGE_OF_HIAWATHA) assert merge_mappings(IN_THE_LODGE_OF_HIAWATHA, THE_SONG_OF_HIAWATHA) == ( THE_SONG_OF_HIAWATHA_IN_THE_LODGE_OF_HIAWATHA) # Assert this function merges two non-empty mappings containing multiple # key but no key-value collisions into the expected mapping. assert merge_mappings(IN_THE_LODGE_OF_HIAWATHA, FAREWELL_O_HIAWATHA) == ( IN_THE_LODGE_OF_HIAWATHA_FAREWELL_O_HIAWATHA) assert merge_mappings(FAREWELL_O_HIAWATHA, IN_THE_LODGE_OF_HIAWATHA) == ( IN_THE_LODGE_OF_HIAWATHA_FAREWELL_O_HIAWATHA) def test_merge_mappings_three() -> None: ''' Test the :func:`beartype._util.kind.map.utilmapset.merge_mappings` function passed exactly three mappings. ''' # Defer test-specific imports. from beartype._util.kind.map.utilmapset import merge_mappings from beartype_test.a00_unit.data.kind.data_kindmap import ( FAREWELL_O_HIAWATHA, FROM_THE_BROW_OF_HIAWATHA, FROM_THE_BROW_OF_HIAWATHA_IN_THE_LODGE_OF_HIAWATHA_FAREWELL_O_HIAWATHA, IN_THE_LODGE_OF_HIAWATHA, THE_SONG_OF_HIAWATHA, ) # Assert this function merges three empty mappings into a new empty # mapping. assert merge_mappings({}, {}, {}) == {} # Assert this function merges a non-empty mapping and two empty mappings # into the same non-empty mapping. assert merge_mappings(THE_SONG_OF_HIAWATHA, {}, {}) == THE_SONG_OF_HIAWATHA assert merge_mappings({}, THE_SONG_OF_HIAWATHA, {}) == THE_SONG_OF_HIAWATHA assert merge_mappings({}, {}, THE_SONG_OF_HIAWATHA) == THE_SONG_OF_HIAWATHA # Assert this function merges two non-empty mappings containing multiple # key collisions but no key-value collisions and another non-empty mapping # containing neither key nor key-value collisions into the expected # mapping. assert merge_mappings( FROM_THE_BROW_OF_HIAWATHA, IN_THE_LODGE_OF_HIAWATHA, FAREWELL_O_HIAWATHA, ) == ( FROM_THE_BROW_OF_HIAWATHA_IN_THE_LODGE_OF_HIAWATHA_FAREWELL_O_HIAWATHA) beartype-0.18.5/beartype_test/a00_unit/a20_util/kind/map/test_utilmaptest.py000066400000000000000000000102331461113517100270150ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **dictionary tester** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.kind.map.utilmaptest` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ validators }.................... def test_die_if_mappings_two_items_collide() -> None: ''' Test the :func:`beartype._util.kind.map.utilmaptest.die_if_mappings_two_items_collide` validator. ''' # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilMappingException from beartype._util.kind.map.utilmaptest import ( die_if_mappings_two_items_collide) from beartype_test.a00_unit.data.kind.data_kindmap import ( FAREWELL_O_HIAWATHA, THE_SONG_OF_HIAWATHA, THE_SONG_OF_HIAWATHA_SINGING_IN_THE_SUNSHINE, ) from pytest import raises # Assert this validator raises the expected exception when passed two # non-empty mappings containing one or more key-value collisions whose # values are all hashable. with raises(_BeartypeUtilMappingException): die_if_mappings_two_items_collide( THE_SONG_OF_HIAWATHA, FAREWELL_O_HIAWATHA) with raises(_BeartypeUtilMappingException): die_if_mappings_two_items_collide( FAREWELL_O_HIAWATHA, THE_SONG_OF_HIAWATHA) # Assert this validator raises the expected exception when passed two # non-empty mappings containing one or more key-value collisions such that # some values of the second mapping are unhashable. with raises(_BeartypeUtilMappingException): die_if_mappings_two_items_collide( THE_SONG_OF_HIAWATHA, THE_SONG_OF_HIAWATHA_SINGING_IN_THE_SUNSHINE) # ....................{ TESTS ~ testers }.................... def test_is_mapping_keys_all() -> None: ''' Test the :func:`beartype._util.kind.map.utilmaptest.is_mapping_keys_all` tester. ''' # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilMappingException from beartype._util.kind.map.utilmaptest import is_mapping_keys_all from beartype_test.a00_unit.data.kind.data_kindmap import ( THE_SONG_OF_HIAWATHA, THE_SONG_OF_HIAWATHA_SINGING_IN_THE_SUNSHINE, ) # Assert this tester returns true when passed a mapping containing all keys # in the passed set. assert is_mapping_keys_all( mapping=THE_SONG_OF_HIAWATHA_SINGING_IN_THE_SUNSHINE, keys=THE_SONG_OF_HIAWATHA.keys(), ) is True # Assert this tester returns false when passed a mapping *NOT* containing # all keys in the passed set. assert is_mapping_keys_all( mapping=THE_SONG_OF_HIAWATHA_SINGING_IN_THE_SUNSHINE, keys=THE_SONG_OF_HIAWATHA.keys() | {'As the mist',}, ) is False def test_is_mapping_keys_any() -> None: ''' Test the :func:`beartype._util.kind.map.utilmaptest.is_mapping_keys_any` tester. ''' # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilMappingException from beartype._util.kind.map.utilmaptest import is_mapping_keys_any from beartype_test.a00_unit.data.kind.data_kindmap import ( FAREWELL_O_HIAWATHA) # Assert this tester returns true when passed a mapping containing any key # in the passed set. assert is_mapping_keys_any( mapping=FAREWELL_O_HIAWATHA, keys={'Thus departed', 'By the shore', 'To the portals'}, ) is True # Assert this tester returns false when passed a mapping containing *NO* key # in the passed set. assert is_mapping_keys_any( mapping=FAREWELL_O_HIAWATHA, keys={'By the shore', 'To the portals'}, ) is False beartype-0.18.5/beartype_test/a00_unit/a20_util/module/000077500000000000000000000000001461113517100226155ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/module/__init__.py000066400000000000000000000000001461113517100247140ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/module/test_utilmoddeprecate.py000066400000000000000000000114031461113517100275570ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **Python module deprecation** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.module.utilmoddeprecate` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_deprecate_module_attr() -> None: ''' Test the :func:`beartype._util.module.utilmoddeprecate.deprecate_module_attr` function. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype._util.module.utilmoddeprecate import deprecate_module_attr from pytest import raises, warns # ..................{ LOCALS }.................. # Dictionary mapping from the deprecated to non-deprecated name of # arbitrary objects masquerading as deprecated and non-deprecated # attributes (respectively) of an arbitrary submodule. ATTR_DEPRECATED_NAME_TO_NONDEPRECATED_NAME = { # Deprecated names originating from public non-deprecated names in the # "ATTR_NONDEPRECATED_NAME_TO_VALUE" dictionary defined below. 'Robes_some_unsculptured_image': 'Thine_earthly_rainbows', # Deprecated names originating from private non-deprecated names in # that dictionary, exercising an edge case. 'The_strange_sleep': '_Of_the_aethereal_waterfall', # Deprecated names originating from non-deprecated names *NOT* in that # dictionary, exercising an edge case. 'Wraps_all_in': 'its_own_deep_eternity', } # Dictionary mapping from the name to value of arbitrary objects # masquerading as non-deprecated attributes of an arbitrary submodule. ATTR_NONDEPRECATED_NAME_TO_VALUE = { 'Thine_earthly_rainbows': "stretch'd across the sweep", '_Of_the_aethereal_waterfall': 'whose veil', # Globally scoped attribute required by deprecate_module_attr(). '__name__': 'Lines.Written_in_the.Vale_of.Chamouni', } # ..................{ WARNS }.................. # Assert this function both emits the expected warning and returns the # expected value of a deprecated attribute originating from a public # non-deprecated attribute of an arbitrary submodule. with warns(DeprecationWarning): assert deprecate_module_attr( attr_deprecated_name='Robes_some_unsculptured_image', attr_deprecated_name_to_nondeprecated_name=( ATTR_DEPRECATED_NAME_TO_NONDEPRECATED_NAME), attr_nondeprecated_name_to_value=ATTR_NONDEPRECATED_NAME_TO_VALUE, ) == "stretch'd across the sweep" # Assert this function both emits the expected warning and returns the # expected value of a deprecated attribute originating from a private # non-deprecated attribute of an arbitrary submodule. with warns(DeprecationWarning): assert deprecate_module_attr( attr_deprecated_name='The_strange_sleep', attr_deprecated_name_to_nondeprecated_name=( ATTR_DEPRECATED_NAME_TO_NONDEPRECATED_NAME), attr_nondeprecated_name_to_value=ATTR_NONDEPRECATED_NAME_TO_VALUE, ) == 'whose veil' # ..................{ RAISES }.................. # Assert this function raises the expected exception when passed any name # other than that of a deprecated attribute. with raises(AttributeError): assert deprecate_module_attr( attr_deprecated_name='Which when the voices of the desert fail', attr_deprecated_name_to_nondeprecated_name=( ATTR_DEPRECATED_NAME_TO_NONDEPRECATED_NAME), attr_nondeprecated_name_to_value=ATTR_NONDEPRECATED_NAME_TO_VALUE, ) # Assert this function raises the expected exception when passed the name # of a deprecated attribute whose corresponding non-deprecated attribute is # *NOT* defined by this submodule. with raises(ImportError): assert deprecate_module_attr( attr_deprecated_name='Wraps_all_in', attr_deprecated_name_to_nondeprecated_name=( ATTR_DEPRECATED_NAME_TO_NONDEPRECATED_NAME), attr_nondeprecated_name_to_value=ATTR_NONDEPRECATED_NAME_TO_VALUE, ) beartype-0.18.5/beartype_test/a00_unit/a20_util/module/test_utilmodget.py000066400000000000000000000112021461113517100263770ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **Python module tester** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.module.utilmodtest` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ object }.................... def test_get_object_module_or_none() -> None: ''' Test the :func:`beartype._util.module.utilmodget.get_object_module_or_none` getter. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype._util.module.utilmodget import get_object_module_or_none from beartype_test.a00_unit.data import data_type # ....................{ PASS }.................... # Assert this getter returns the expected module for: # * A user-defined class. # * A user-defined function. assert get_object_module_or_none(data_type.Class) is data_type assert get_object_module_or_none(data_type.function) is data_type # ....................{ FAIL }.................... # Assert this getter returns "None" for an arbitrary object that is neither # a class *NOR* a callable. assert get_object_module_or_none( 'And saw in sleep old palaces and towers') is None def test_get_object_module() -> None: ''' Test the :func:`beartype._util.module.utilmodget.get_object_module` getter. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilModuleException from beartype._util.module.utilmodget import get_object_module from beartype_test.a00_unit.data import data_type from pytest import raises # ....................{ PASS }.................... # Assert this getter returns the expected module for: # * A user-defined class. # * A user-defined function. assert get_object_module(data_type.Class) is data_type assert get_object_module(data_type.function) is data_type # ....................{ FAIL }.................... # Assert this getter raises the expected exception for an arbitrary object # that is neither a class *NOR* a callable. with raises(_BeartypeUtilModuleException): get_object_module("Quivering within the wave's intenser day,") # ....................{ TESTS ~ object : line }.................... def test_get_object_module_line_number_begin() -> None: ''' Test the :func:`beartype._util.module.utilmodget.get_object_module_line_number_begin` getter. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilModuleException from beartype._util.module.utilmodget import ( get_object_module_line_number_begin) from beartype_test.a00_unit.data.util.mod.data_utilmodule_line import ( SlowRollingOn, like_snakes_that_watch_their_prey, ) from pytest import raises # ....................{ PASS }.................... # Assert this getter returns the expected line number for this callable. assert get_object_module_line_number_begin( like_snakes_that_watch_their_prey) == 20 #FIXME: The inspect.findsource() function underlying this call incorrectly #suggests this class to be declared at this line of its submodule, when in #fact this class is declared at the following line of its submodule. Sadly, #there's nothing meaningful we can do about this. Just be aware, please. # Assert this getter returns the expected line number for this class. assert get_object_module_line_number_begin(SlowRollingOn) == 39 # ....................{ FAIL }.................... # Assert this validator raises the expected exception when passed an object # that is neither a callable nor class. with raises(_BeartypeUtilModuleException): get_object_module_line_number_begin( 'Frost and the Sun in scorn of mortal power') beartype-0.18.5/beartype_test/a00_unit/a20_util/module/test_utilmodimport.py000066400000000000000000000250031461113517100271360ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **Python module importer** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.module.utilmodimport` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_import_module_or_none() -> None: ''' Test the :func:`beartype._util.module.utilmodget.import_module_or_none` function. ''' # Defer test-specific imports. import beartype from beartype.roar import BeartypeModuleUnimportableWarning from beartype._util.module.utilmodimport import import_module_or_none from pytest import warns # Assert this function returns the expected module when passed the # fully-qualified name of a previously imported module. assert import_module_or_none('beartype') is beartype # Assert this function returns the expected module when passed the # fully-qualified name of a module effectively guaranteed to *NOT* have # been previously imported by virtue of its irrelevance. xmlrpc_client_dynamic = import_module_or_none('xmlrpc.client') from xmlrpc import client as xmlrpc_client_static assert xmlrpc_client_dynamic is xmlrpc_client_static # Assert this function returns "None" when passed the fully-qualified name # of a module effectively guaranteed to *NEVER* exist by virtue of the # excess inscrutability, stupidity, and verbosity of its name. assert import_module_or_none( 'phnglui_mglwnafh_Cthulhu_Rlyeh_wgahnagl_fhtagn') is None # Assert this function emits the expected warning when passed the # fully-qualified name of an unimportable module. with warns(BeartypeModuleUnimportableWarning): assert import_module_or_none( 'beartype_test.a00_unit.data.util.mod.data_utilmodule_bad') is ( None) # ....................{ TESTS ~ attr }.................... def test_import_module_attr() -> None: ''' Test the :func:`beartype._util.module.utilmodget.import_module_attr` importer. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilModuleException from beartype._util.module.utilmodimport import import_module_attr from pytest import raises # ....................{ LOCALS }.................... # Fully-qualified name of an importable module defining attributes intended # to be dynamically imported below. MODULE_IMPORTABLE_NAME = ( 'beartype_test.a00_unit.data.util.mod.data_utilmodule_good') # Fully-qualified name of an unimportable module. MODULE_UNIMPORTABLE_NAME = 'to.thy_delightful.realms' # ....................{ PASS }.................... # Assert that passing this importer the fully-qualified name of an # existing attribute of an importable module returns that attribute as is. attr_good = import_module_attr(f'{MODULE_IMPORTABLE_NAME}.attrgood') assert isinstance(attr_good, str) assert attr_good.startswith('I started to see human beings as little') # ....................{ FAIL }.................... # Assert that passing this importer the unqualified basename of a # non-existent attribute and *NO* module raises the expected exception. with raises(_BeartypeUtilModuleException): import_module_attr('Conduct_O_Sleep') # Assert that passing this importer the unqualified basename of a # non-existent attribute of an importable module raises the expected # exception. with raises(_BeartypeUtilModuleException): import_module_attr('This_doubt', module_name=MODULE_IMPORTABLE_NAME) # Assert that passing this importer the unqualified basename of a # non-existent attribute of an unimportable module raises the expected # exception. with raises(_BeartypeUtilModuleException): import_module_attr( 'with_sudden_tide', module_name=MODULE_UNIMPORTABLE_NAME) # Assert that passing this importer the fully-qualified basename of a # non-existent attribute of an importable module raises the expected # exception. with raises(_BeartypeUtilModuleException): import_module_attr( 'flowed_on_his_heart', module_name=MODULE_IMPORTABLE_NAME) # Assert that passing this importer the fully-qualified basename of a # non-existent attribute of an unimportable module raises the expected # exception. with raises(_BeartypeUtilModuleException): import_module_attr( 'The_insatiate_hope', module_name=MODULE_UNIMPORTABLE_NAME) def test_import_module_attr_or_none() -> None: ''' Test the :func:`beartype._util.module.utilmodget.import_module_attr_or_none` importer. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilModuleException from beartype._util.module.utilmodimport import import_module_attr_or_none # ....................{ LOCALS }.................... # Fully-qualified name of an importable module defining attributes intended # to be dynamically imported below. MODULE_NAME = 'beartype_test.a00_unit.data.util.mod.data_utilmodule_good' # ....................{ PASS }.................... # Assert that passing this importer the fully-qualified name of an # existing attribute of an importable module returns that attribute as is. attr_good = import_module_attr_or_none(f'{MODULE_NAME}.attrgood') assert isinstance(attr_good, str) assert attr_good.startswith('I started to see human beings as little') # Assert that passing this importer the fully-qualified name of a # non-existent attribute of an importable module returns "None". assert import_module_attr_or_none(f'{MODULE_NAME}.attrbad') is None def test_import_module_attr_or_sentinel() -> None: ''' Test the :func:`beartype._util.module.utilmodget.import_module_attr_or_sentinel` importer. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar import BeartypeModuleUnimportableWarning from beartype.roar._roarexc import _BeartypeUtilModuleException from beartype._util.module.utilmodimport import ( import_module_attr_or_sentinel) from beartype._util.utilobject import SENTINEL from pytest import ( raises, warns, ) # ....................{ LOCALS }.................... # Fully-qualified name of a test-specific module defining attributes # intended to be dynamically imported below. MODULE_NAME = 'beartype_test.a00_unit.data.util.mod.data_utilmodule_good' # ....................{ PASS }.................... # Assert that passing this importer an arbitrary attribute name of an # unimportable module returns the sentinel. assert import_module_attr_or_sentinel( attr_name='Hides_its_dead_eye', module_name='from.the_detested.day', ) is SENTINEL # Assert that passing this importer the unqualified name of an existing # builtin type returns that type as is. assert import_module_attr_or_sentinel('int') is int # Assert that passing this importer the unqualified name of a non-existent # builtin type returns the sentinel. assert import_module_attr_or_sentinel( 'Where_every_shade_which_the_foul_grave_exhales') is SENTINEL # Assert that passing this importer the fully-qualified name of an # existing attribute of an importable module returns that attribute as is. attr_good = import_module_attr_or_sentinel(f'{MODULE_NAME}.attrgood') assert isinstance(attr_good, str) assert attr_good.startswith( 'I started to see human beings as little') # Assert that passing this importer the fully-qualified name of a # non-existent attribute of an importable module which is also the # unqualified name of a builtin type returns that type. assert import_module_attr_or_sentinel( 'str', module_name=MODULE_NAME) is str # Assert that passing this importer the fully-qualified name of a # non-existent attribute of an importable module which is *NOT* also the # unqualified name of a builtin type returns the sentinel. assert import_module_attr_or_sentinel( 'attrbad', module_name=MODULE_NAME) is SENTINEL # ....................{ FAIL }.................... # Assert this function emits the expected warning when passed the # syntactically valid fully-qualified name of a non-existent attribute of an # unimportable module. with warns(BeartypeModuleUnimportableWarning): bad_module_attr = import_module_attr_or_sentinel( 'beartype_test.a00_unit.data.util.mod.data_utilmodule_bad.attrbad') assert bad_module_attr is SENTINEL # Assert this function raises the expected exception when passed a # non-string. with raises(_BeartypeUtilModuleException): import_module_attr_or_sentinel( b'In far countries little men have closely studied your longing ' b'to be an indiscriminate slave.' ) # Assert this function raises the expected exception when passed a # string containing no "." characters. with raises(_BeartypeUtilModuleException): import_module_attr_or_sentinel( 'These little men were not born in mansions, ' 'they rose from your ranks' ) # Assert this function raises the expected exception when passed a # string containing one or more "." characters but syntactically invalid as # a fully-qualified module attribute name. with raises(_BeartypeUtilModuleException): import_module_attr_or_sentinel( 'They have gone hungry like you, suffered like you. And they have ' 'found a quicker way of changing masters.' ) beartype-0.18.5/beartype_test/a00_unit/a20_util/module/test_utilmodtest.py000066400000000000000000000063331461113517100266100ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **Python module tester** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.module.utilmodtest` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ raiser }.................... def test_die_unless_module_attr_name() -> None: ''' Test the :func:`beartype._util.module.utilmodtest.die_unless_module_attr_name` validator. ''' # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilModuleException from beartype._util.module.utilmodtest import die_unless_module_attr_name from pytest import raises # Assert this validator raises *NO* exception when passed a syntactically # valid Python identifier. die_unless_module_attr_name('She_dwelt.among_the.untrodden.ways_2') # Assert this validator raises the expected exception when passed: # * A non-string. # * The empty string. # * A non-empty string containing *NO* "." delimiters. # * A non-empty string syntactically invalid as a Python identifier. with raises(_BeartypeUtilModuleException): die_unless_module_attr_name(b'Sentient.No6') with raises(_BeartypeUtilModuleException): die_unless_module_attr_name('') with raises(_BeartypeUtilModuleException): die_unless_module_attr_name('typing') with raises(_BeartypeUtilModuleException): die_unless_module_attr_name('Sentient.6') # ....................{ TESTS ~ tester }.................... def test_is_module() -> None: ''' Test the :func:`beartype._util.module.utilmodtest.is_module` tester. ''' # Defer test-specific imports. from beartype.roar import BeartypeModuleUnimportableWarning from beartype._util.module.utilmodtest import is_module from pytest import warns # Assert this tester accepts the name of a (possibly unimported) existing # importable module. assert is_module( 'beartype_test.a00_unit.data.util.mod.data_utilmodule_good') is True # Assert this tester accepts the name of an already imported module. assert is_module( 'beartype_test.a00_unit.data.util.mod.data_utilmodule_good') is True # Assert this tester rejects the name of a module guaranteed *NOT* to # exist, because we fully control the "beartype_test" package. assert is_module( 'beartype_test.a00_unit.data.util.mod.data_utilmodule_nonexistent' ) is False # Assert this function emits the expected warning when passed the name of # an existing unimportable module. with warns(BeartypeModuleUnimportableWarning): assert is_module( 'beartype_test.a00_unit.data.util.mod.data_utilmodule_bad') is ( False) beartype-0.18.5/beartype_test/a00_unit/a20_util/os/000077500000000000000000000000001461113517100217515ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/os/__init__.py000066400000000000000000000000001461113517100240500ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/os/test_utilostest.py000066400000000000000000000027411461113517100256050ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **platform tester** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.os.utilostest` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ tester }.................... def test_is_os_macos() -> None: ''' Test the :func:`beartype._util.os.utilostest.is_os_macos` tester. ''' # Defer test-specific imports. from beartype._util.os.utilostest import is_os_macos # Assert this tester returns a boolean. IS_OS_MACOS = is_os_macos() assert isinstance(IS_OS_MACOS, bool) def test_is_os_windows_vanilla() -> None: ''' Test the :func:`beartype._util.os.utilostest.is_os_windows_vanilla` tester. ''' # Defer test-specific imports. from beartype._util.os.utilostest import is_os_windows_vanilla # Assert this tester returns a boolean. IS_OS_WINDOWS_VANILLA = is_os_windows_vanilla() assert isinstance(IS_OS_WINDOWS_VANILLA, bool) beartype-0.18.5/beartype_test/a00_unit/a20_util/os/test_utilostty.py000066400000000000000000000022011461113517100254350ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **terminal tester** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.os.utilostty` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ tester }.................... def test_is_stdout_terminal() -> None: ''' Test the :func:`beartype._util.os.utilostty.is_stdout_terminal` tester. ''' # Defer test-specific imports. from beartype._util.os.utilostty import is_stdout_terminal # Assert this tester returns a boolean. IS_STDOUT_TERMINAL = is_stdout_terminal() assert isinstance(IS_STDOUT_TERMINAL, bool) beartype-0.18.5/beartype_test/a00_unit/a20_util/py/000077500000000000000000000000001461113517100217605ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/py/__init__.py000066400000000000000000000000001461113517100240570ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/py/test_utilpyinterpreter.py000066400000000000000000000101131461113517100271770ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **Python interpreter** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.py.utilpyinterpreter` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ testers }.................... def test_is_python_pypy() -> None: ''' Test the :func:`beartype._util.py.utilpyinterpreter.is_python_pypy` tester. ''' # Defer test-specific imports. from beartype._util.py.utilpyinterpreter import is_python_pypy # Assert this tester returns a boolean. IS_PY_PYPY = is_python_pypy() assert isinstance(IS_PY_PYPY, bool) def test_is_python_optimized(monkeypatch: 'pytest.MonkeyPatch') -> None: ''' Test the :func:`beartype._util.py.utilpyinterpreter.is_python_optimized` tester. Parameters ---------- monkeypatch : MonkeyPatch :mod:`pytest` fixture allowing various state associated with the active Python process to be temporarily changed for the duration of this test. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype._util.py.utilpyinterpreter import is_python_optimized # ....................{ ASSERT }.................... # Assert that the active Python interpreter is currently unoptimized. In # theory, tests should *ALWAYS* be run unoptimized. Why? Because # optimization destructively elides away (i.e., silently reduces to noops) # all "assert" statements, obstructing testing. assert is_python_optimized() is False # Temporarily zero the ${PYTHONOPTIMIZE} environment variable. monkeypatch.setenv('PYTHONOPTIMIZE', '0') # Assert that the active Python interpreter remains unoptimized. assert is_python_optimized() is False # Temporarily set this variable to an arbitrary positive integer. monkeypatch.setenv('PYTHONOPTIMIZE', '1') # Assert that the active Python interpreter is now kinda "optimized." assert is_python_optimized() is True # Temporarily set this variable to an arbitrary string that *CANNOT* be # coerced into an integer. monkeypatch.setenv('PYTHONOPTIMIZE', ( 'Bright in the lustre of their own fond joy.')) # Assert that the active Python interpreter is yet again unoptimized. assert is_python_optimized() is False # ....................{ TESTS ~ getters }.................... def test_get_interpreter_command() -> None: ''' Test the :func:`beartype._util.py.utilpyinterpreter.get_interpreter_command_words` getter. ''' # Defer test-specific imports. from beartype._util.py.utilpyinterpreter import get_interpreter_command_words from collections.abc import Iterable # Iterable of all interpreter shell words. interpreter_command = get_interpreter_command_words() # Assert that this iterable is non-empty. assert isinstance(interpreter_command, Iterable) assert bool(interpreter_command) #FIXME: Additionally assert that this command is successfully runnable. def test_get_interpreter_filename() -> None: ''' Test the :func:`beartype._util.py.utilpyinterpreter.get_interpreter_filename` getter. ''' # Defer test-specific imports. from beartype._util.py.utilpyinterpreter import get_interpreter_filename # Absolute filename of the executable binary underlying this interpreter. interpreter_filename = get_interpreter_filename() # Assert that this filename is a non-empty string. assert isinstance(interpreter_filename, str) assert bool(str) beartype-0.18.5/beartype_test/a00_unit/a20_util/py/test_utilpyweakref.py000066400000000000000000000134151461113517100262700ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **weak reference** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.py.utilpyweakref` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_make_obj_weakref_and_repr() -> None: ''' Test the :func:`beartype._util.py.utilpyweakref.make_obj_weakref_and_repr` factory. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype._util.py.utilpyweakref import ( _WEAKREF_NONE, make_obj_weakref_and_repr, ) from beartype._util.text.utiltextrepr import represent_object from weakref import ref as weakref_ref # ....................{ ASSERT }.................... # Assert this factory when passed "None" returns the expected singleton. obj_weakref, obj_repr = make_obj_weakref_and_repr(None) assert obj_repr == represent_object(None, max_len=999999) assert obj_weakref is _WEAKREF_NONE # Assert this factory when passed a mutable C-based container returns # "None". obj_weakref, obj_repr = make_obj_weakref_and_repr(_WINDS_CONTEND) assert obj_repr == represent_object(_WINDS_CONTEND, max_len=999999) assert obj_weakref is None # Assert this factory when passed an immutable C-based container returns # "None". obj_weakref, obj_repr = make_obj_weakref_and_repr(_ITS_HOME) assert obj_repr == represent_object(_ITS_HOME, max_len=999999) assert obj_weakref is None # Assert this factory when passed any objects *OTHER* than "None" and # C-based containers returns a weak reference to those objects. # # Note that integers and strings are both immutable C-based containers. # Neither suffices here, sadly. obj_weakref, obj_repr = make_obj_weakref_and_repr(_IN_THESE_SOLITUDES) assert obj_repr == represent_object(_IN_THESE_SOLITUDES, max_len=999999) assert isinstance(obj_weakref, weakref_ref) assert obj_weakref() is _IN_THESE_SOLITUDES def test_get_weakref_obj_or_repr() -> None: ''' Test the :func:`beartype._util.py.utilpyweakref.get_weakref_obj_or_repr` getter. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilPythonWeakrefException from beartype._util.py.utilpyweakref import ( get_weakref_obj_or_repr, make_obj_weakref_and_repr, ) from pytest import raises # ....................{ LOCALS }.................... # Iterable of objects that *CAN* be weakly referenced (e.g., due to *NOT* # being C-based containers). _WEAKREFABLES = (None, _IN_THESE_SOLITUDES) # Iterable of objects that *CANNOT* be weakly referenced (e.g., due to being # C-based containers). _NON_WEAKREFABLES = (_WINDS_CONTEND, _ITS_HOME) # ....................{ PASS }.................... # For each object that *CAN* be weakly referenced... for weakrefable in _WEAKREFABLES: # Weak reference to and representation of this object. weakrefable_weakref, weakrefable_repr = make_obj_weakref_and_repr( weakrefable) # Strong reference to the same object accessed via this weak reference. weakrefable_new = get_weakref_obj_or_repr( weakrefable_weakref, weakrefable_repr) # Assert that these objects are, in fact, the same object. assert weakrefable_new is weakrefable # For each object that *CANNOT* be weakly referenced... for non_weakrefable in _NON_WEAKREFABLES: # Fake weak reference to and representation of this object. non_weakrefable_weakref, non_weakrefable_repr = ( make_obj_weakref_and_repr(non_weakrefable)) # Fake strong reference to this same object accessed via this fake weak # reference. non_weakrefable_new = get_weakref_obj_or_repr( non_weakrefable_weakref, non_weakrefable_repr) # Assert that this fake strong reference is actually just the # representation of this object. assert non_weakrefable_new is non_weakrefable_repr # ....................{ FAIL }.................... # Assert that calling this getter with a valid representation but invalid # weak reference raises the expected exception. with raises(_BeartypeUtilPythonWeakrefException): get_weakref_obj_or_repr( 'Rapid and strong, but silently!', '"Its home"') # ....................{ PRIVATE ~ classes }.................... class _TheVoicelessLightning(object): ''' Arbitrary pure-Python object. ''' # ....................{ PRIVATE ~ classes }.................... class _TheVoicelessLightning(object): ''' Arbitrary pure-Python object. ''' # ....................{ PRIVATE ~ globals }.................... _WINDS_CONTEND = ['Silently there', 'and heap the snow with breath'] ''' Mutable C-based container. ''' _ITS_HOME = ('Rapid and strong', 'but silently') ''' Immutable C-based container. ''' _IN_THESE_SOLITUDES = _TheVoicelessLightning() ''' Arbitrary pure-Python object. ''' beartype-0.18.5/beartype_test/a00_unit/a20_util/py/test_utilpyword.py000066400000000000000000000021151461113517100256120ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **Python word size** utility unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.py.utilpyword` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_word_size() -> None: ''' Test the :func:`beartype._util.py.utilpyword.WORD_SIZE` constant. ''' # Defer test-specific imports. from beartype._util.py.utilpyword import WORD_SIZE # Assert the active Python interpreter to be either 32- or 64-bit. assert WORD_SIZE in {32, 64} beartype-0.18.5/beartype_test/a00_unit/a20_util/test_utilobject.py000066400000000000000000000120521461113517100251050ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **object utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.utilobject` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ tester }.................... def test_is_object_hashable() -> None: ''' Test the :func:`beartype._util.utilobject.is_object_hashable` tester. ''' # Defer test-specific imports. from beartype._util.utilobject import is_object_hashable from beartype_test.a00_unit.data.hint.data_hint import ( NOT_HINTS_HASHABLE, NOT_HINTS_UNHASHABLE,) # Assert this tester accepts unhashable objects. for object_hashable in NOT_HINTS_HASHABLE: assert is_object_hashable(object_hashable) is True # Assert this tester rejects unhashable objects. for object_unhashable in NOT_HINTS_UNHASHABLE: assert is_object_hashable(object_unhashable) is False # ....................{ TESTS ~ getter }.................... def test_get_object_basename_scoped() -> None: ''' Test the :func:`beartype._util.utilobject.get_object_basename_scoped` getter. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilObjectNameException from beartype._util.utilobject import get_object_basename_scoped from beartype_test.a00_unit.data.data_type import ( CALLABLES, closure_factory, ) from pytest import raises # ....................{ PASS }.................... # Assert this getter returns the fully-qualified names of non-nested # callables unmodified. for callable_obj in CALLABLES: assert get_object_basename_scoped(callable_obj) == ( callable_obj.__qualname__) # Assert this getter returns the fully-qualified names of closures stripped # of meaningless "." substrings. assert get_object_basename_scoped(closure_factory()) == ( 'closure_factory.closure') # ....................{ FAIL }.................... # Assert this getter raises "AttributeError" exceptions when passed objects # declaring neither "__qualname__" nor "__name__" dunder attributes. with raises(_BeartypeUtilObjectNameException): get_object_basename_scoped( 'From the ice-gulfs that gird his secret throne,') def test_get_object_filename_or_none() -> None: ''' Test the :func:`beartype._util.utilobject.get_object_filename_or_none` getter. ''' # Defer test-specific imports. from beartype._util.utilobject import get_object_filename_or_none from beartype_test.a00_unit.data.data_type import ( Class, function, ) # Assert this getter returns the expected filename for a physical class. assert 'data_type' in get_object_filename_or_none(Class) # Assert this getter returns the expected filename for a physical callable. assert 'data_type' in get_object_filename_or_none(function) # Assert this getter returns "None" when passed an object that is neither a # class *NOR* callable. assert get_object_filename_or_none(Class()) is None def test_get_object_name() -> None: ''' Test the :func:`beartype._util.utilobject.get_object_name` getter. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilObjectNameException from beartype._util.utilobject import get_object_name from beartype_test.a00_unit.data.data_type import function_partial from pytest import raises # ....................{ CALLABLES }.................... def meet_in_the_vale(and_one_majestic_river: str) -> str: ''' Arbitrary nested function. ''' return and_one_majestic_river # ....................{ PASS }.................... # Assert this getter returns the expected name for a nested function. assert get_object_name(meet_in_the_vale) == ( 'beartype_test.a00_unit.a20_util.test_utilobject.' 'test_get_object_name.meet_in_the_vale' ) # ....................{ FAIL }.................... # Assert this getter raises "AttributeError" exceptions when passed objects # declaring neither "__qualname__" nor "__name__" dunder attributes. with raises(_BeartypeUtilObjectNameException): get_object_name(function_partial) beartype-0.18.5/beartype_test/a00_unit/a20_util/text/000077500000000000000000000000001461113517100223145ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a20_util/text/test_utiltextansi.py000066400000000000000000000037321461113517100264670ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. """ **Beartype ANSI utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.text.utiltextansi` submodule. """ # ....................{ IMPORTS }.................... # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ tester }.................... def test_is_text_ansi() -> None: ''' Test the :func:`beartype._util.text.utiltextansi.is_str_ansi` tester. ''' # Defer test-specific imports. from beartype._util.text.utiltextansi import is_str_ansi # Assert that this tester returns false for a string containing *NO* ANSI. assert is_str_ansi('The sea-blooms and the oozy woods which wear') is False # Assert that this tester returns true for a string containing ANSI. assert is_str_ansi('The sapless foliage of the ocean, know\033[92m') is ( True) # ....................{ TESTS ~ stripper }.................... def test_strip_str_ansi() -> None: ''' Test the :func:`beartype._util.text.utiltextansi.strip_str_ansi` stripper. ''' # Defer test-specific imports. from beartype._util.text.utiltextansi import strip_str_ansi # String containing *NO* ANSI. THY_VOICE = 'and suddenly grow gray with fear,' # Assert that this stripper preserves strings containing *NO* ANSI as is. assert strip_str_ansi(THY_VOICE) == THY_VOICE # Assert that this stripper strips *ALL* ANSI from strings containing ANSI. assert strip_str_ansi(f'\033[31m{THY_VOICE}\033[92m') == THY_VOICE beartype-0.18.5/beartype_test/a00_unit/a20_util/text/test_utiltextidentifier.py000066400000000000000000000057631461113517100276650ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **Python identifier utility** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.text.utiltextidentifier` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_die_unless_identifier() -> None: ''' Test the :func:`beartype._util.text.utiltextidentifier.die_unless_identifier` exception-raiser. ''' # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilTextIdentifierException from beartype._util.text.utiltextidentifier import die_unless_identifier from pytest import raises # Assert this raiser accepts an unqualified Python identifier prefixed by # an underscore and suffixed by a digit. die_unless_identifier('_the_lucy_poems_5') # Assert this raiser accepts a fully-qualified Python identifier containing # underscores and digits. die_unless_identifier('She_dwelt.among_the.untrodden.ways_2') # Assert this raiser rejects the empty string. with raises(_BeartypeUtilTextIdentifierException): die_unless_identifier('') # Assert this raiser rejects a non-empty string prefixed by a digit. with raises(_BeartypeUtilTextIdentifierException): die_unless_identifier('42147') # Assert this raiser rejects an unqualified Python identifier suffixed by a # non-empty string prefixed by a digit. with raises(_BeartypeUtilTextIdentifierException): die_unless_identifier('Sentient.6') def test_is_identifier() -> None: ''' Test the :func:`beartype._util.text.utiltextidentifier.is_identifier` tester. ''' # Defer test-specific imports. from beartype._util.text.utiltextidentifier import is_identifier # Assert this tester accepts an unqualified Python identifier prefixed by # an underscore and suffixed by a digit. assert is_identifier('_the_lucy_poems_5') is True # Assert this tester accepts a fully-qualified Python identifier containing # underscores and digits. assert is_identifier('She_dwelt.among_the.untrodden.ways_2') is ( True) # Assert this tester rejects the empty string. assert is_identifier('') is False # Assert this tester rejects a non-empty string prefixed by a digit. assert is_identifier('42147') is False # Assert this tester rejects an unqualified Python identifier suffixed by a # non-empty string prefixed by a digit. assert is_identifier('Sentient.6') is False beartype-0.18.5/beartype_test/a00_unit/a20_util/text/test_utiltextjoin.py000066400000000000000000000102571461113517100264740ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **string-joining utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.text.utiltextjoin` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_join_delimited() -> None: ''' Test the :func:`beartype._util.text.utiltextjoin.join_delimited` function. ''' # Defer test-specific imports. from beartype._util.text.utiltextjoin import join_delimited # Assert that joining a sequence of no strings returns the empty string. assert join_delimited( strs=(), delimiter_if_two='In Xanadu did Kubla Khan', delimiter_if_three_or_more_nonlast='A stately pleasure-dome decree:', delimiter_if_three_or_more_last='Where Alph, the sacred river, ran', ) == '' # Assert that joining a sequence of one string returns that string. assert join_delimited( strs=('Through caverns measureless to man',), delimiter_if_two='Down to a sunless sea.', delimiter_if_three_or_more_nonlast=( 'So twice five miles of fertile ground'), delimiter_if_three_or_more_last=( 'With walls and towers were girdled round;'), ) == 'Through caverns measureless to man' # Assert that joining a sequence of two strings returns these strings # conditionally delimited by the appropriate delimiter. assert join_delimited( strs=( 'And there were gardens bright with sinuous rills,', 'Where blossomed many an incense-bearing tree;', ), delimiter_if_two='And here were forests ancient as the hills,', delimiter_if_three_or_more_nonlast=( 'Enfolding sunny spots of greenery.'), delimiter_if_three_or_more_last=( 'But oh! that deep romantic chasm which slanted'), ) == ( 'And there were gardens bright with sinuous rills,' 'And here were forests ancient as the hills,' 'Where blossomed many an incense-bearing tree;' ) # Assert that joining a sequence of three strings returns these strings # conditionally delimited by the appropriate delimiters. assert join_delimited( strs=( 'Down the green hill athwart a cedarn cover!', 'A savage place! as holy and enchanted', 'As e’er beneath a waning moon was haunted', ), delimiter_if_two='By woman wailing for her demon-lover!', delimiter_if_three_or_more_nonlast=( 'And from this chasm, with ceaseless turmoil seething,'), delimiter_if_three_or_more_last=( 'As if this earth in fast thick pants were breathing,'), ) == ( 'Down the green hill athwart a cedarn cover!' 'And from this chasm, with ceaseless turmoil seething,' 'A savage place! as holy and enchanted' 'As if this earth in fast thick pants were breathing,' 'As e’er beneath a waning moon was haunted' ) def test_join_delimited_disjunction() -> None: ''' Test the :func:`beartype._util.text.utiltextjoin.join_delimited_disjunction` function. ''' # Defer test-specific imports. from beartype._util.text.utiltextjoin import join_delimited_disjunction # Assert that joining a sequence of no strings returns the empty string. assert join_delimited_disjunction(( 'A mighty fountain momently was forced:', 'Amid whose swift half-intermitted burst', 'Huge fragments vaulted like rebounding hail,', )) == ( 'A mighty fountain momently was forced:, ' 'Amid whose swift half-intermitted burst, or ' 'Huge fragments vaulted like rebounding hail,' ) beartype-0.18.5/beartype_test/a00_unit/a20_util/text/test_utiltextlabel.py000066400000000000000000000146531461113517100266200ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. """ **Beartype object label utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.text.utiltextlabel` submodule. """ # ....................{ IMPORTS }.................... # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_label_beartypeable_kind() -> None: ''' Test the :func:`beartype._util.text.utiltextlabel.label_beartypeable_kind` function. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype._util.text.utiltextlabel import label_beartypeable_kind from beartype_test.a00_unit.data.data_type import ( CallableClass, Class, async_coroutine_factory, async_generator_factory, function, sync_generator_factory, ) from beartype_test.a00_unit.data.func.data_func import ( func_args_3_flex_mandatory_optional_varkw) # ....................{ LOCALS }.................... # Tuple of 2-tuples "(beartypeable, kind)", where: # * "beartypeable" is an input object to be passed to this labeller. # * "kind" is the string expected to be returned from this labeller when # passed this input object. BEARTYPEABLES_KINDS = ( # Builtin type. (str, 'class'), # User-defined class. (Class, 'class'), # Pure-Python argumentless function. (function, 'function'), # Pure-Python argumentative function. (func_args_3_flex_mandatory_optional_varkw, 'function'), # Pure-Python instance method. (Class.instance_method, 'method'), # Pure-Python class method. (Class.class_method, 'class method'), # Pure-Python class method. (Class.class_method, 'class method'), # Pure-Python coroutine factory function. (async_coroutine_factory, 'coroutine factory function'), # Pure-Python asynchronous generator factory function. (async_generator_factory, 'asynchronous generator factory function'), # Pure-Python asynchronous generator factory function. (sync_generator_factory, 'generator factory function'), # Object that is neither a pure-Python class, function, *NOR* method. In # this case, pass a pseudo-callable (i.e., object whose class defines # the __call__() dunder method) to exercise a *POSSIBLE* edge case. (CallableClass(), 'object'), ) # ....................{ PASS }.................... # For each such input object and expected string... for beartypeable, kind in BEARTYPEABLES_KINDS: # Assert this labeller returns the expected string when passed this # input object. assert label_beartypeable_kind(beartypeable) == kind def test_label_callable() -> None: ''' Test the :func:`beartype._util.text.utiltextlabel.label_callable` function. ''' # Defer test-specific imports. from beartype._util.text.utiltextlabel import label_callable from beartype_test.a00_unit.data.data_type import ( async_coroutine_factory, async_generator_factory, sync_generator_factory, ) from beartype_test.a00_unit.data.util.mod.data_utilmodule_line import ( like_snakes_that_watch_their_prey, ozymandias, which_yet_survive, ) # Assert this labeller labels an on-disk lambda function as expected. two_vast_and_trunkless_legs_of_stone = label_callable(ozymandias) assert isinstance(two_vast_and_trunkless_legs_of_stone, str) assert 'lambda' in two_vast_and_trunkless_legs_of_stone # Assert this labeller labels an in-memory lambda function as expected. the_hand_that_mocked_them = label_callable(which_yet_survive) assert isinstance(the_hand_that_mocked_them, str) assert 'lambda' in the_hand_that_mocked_them # Assert this labeller labels an on-disk non-lambda callable as expected. tell_that_its_sculptor_well_those_passions_read = label_callable( like_snakes_that_watch_their_prey) assert isinstance(tell_that_its_sculptor_well_those_passions_read, str) assert like_snakes_that_watch_their_prey.__name__ in ( tell_that_its_sculptor_well_those_passions_read) # Assert this labeller labels an on-disk coroutine as expected. async_coroutine_label = label_callable(async_coroutine_factory) assert isinstance(async_coroutine_label, str) assert async_coroutine_factory.__name__ in async_coroutine_label assert 'coroutine' in async_coroutine_label # Assert this labeller labels an on-disk asynchronous generator as expected. async_generator_label = label_callable(async_generator_factory) assert isinstance(async_generator_label, str) assert async_generator_factory.__name__ in async_generator_label assert 'asynchronous generator' in async_generator_label # Assert this labeller labels an on-disk synchronous generator as expected. sync_generator_label = label_callable(sync_generator_factory) assert isinstance(sync_generator_label, str) assert sync_generator_factory.__name__ in sync_generator_label assert 'generator' in sync_generator_label def test_label_type() -> None: ''' Test the :func:`beartype._util.text.utiltextlabel.label_type` function. ''' # Defer test-specific imports. from beartype._util.text.utiltextlabel import label_type from beartype_test.a00_unit.data.data_type import Class # Assert this labeller returns the expected label for a builtin type. assert label_type(str) == 'str' # Assert this labeller returns the expected label for the non-builtin type # of the "None" singleton, exercising a common edge case. assert label_type(type(None)) == '' # Assert this labeller returns the expected label for a user-defined type. assert label_type(Class) == ( '') beartype-0.18.5/beartype_test/a00_unit/a20_util/text/test_utiltextmunge.py000066400000000000000000000213041461113517100266430ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **string munging** utility unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.text.utiltextmunge` submodule. ''' # ....................{ IMPORTS }.................... # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ case }.................... def test_uppercase_str_char_first(): ''' Test the :func:`beartype._util.text.utiltextmunge.uppercase_str_char_first` function. ''' # Defer test-specific imports. from beartype._util.text.utiltextmunge import uppercase_str_char_first from pytest import raises # Assert this munger returns the expected string when passed a string. assert uppercase_str_char_first(text='beArTyPe') == "BeArTyPe" assert uppercase_str_char_first(text="") == "" assert uppercase_str_char_first(text="") == "" # Assert this munger raises the expected exception when passed a non-string. with raises(AssertionError): uppercase_str_char_first(7) # ....................{ TESTS ~ number }.................... def test_number_str_lines(): ''' Test the :func:`beartype._util.text.utiltextmunge.number_str_lines` function. ''' # Defer test-specific imports. from beartype._util.text.utiltextmunge import number_str_lines from pytest import raises from re import search with raises(AssertionError): number_str_lines(7) # No lines to split. assert number_str_lines(text="") == "" NEWLINE_COUNT = 20 base_string = 'bears, beats, battlestar galactica' total_string = f'{base_string}\n' * NEWLINE_COUNT numbered_lines_string = number_str_lines(total_string) numbered_lines_string = numbered_lines_string.splitlines() # Confirm the function preserves newlines as is. assert len(numbered_lines_string) == NEWLINE_COUNT # Confirm the base string is prefixed with something. for line_number in range(NEWLINE_COUNT): assert search( pattern=fr'(?', max_len=4) == '...>' assert truncate_str(text='Sent >', max_len=5) == 'S...>' assert truncate_str(text='Sent t>', max_len=6) == 'Se...>' # Assert that truncating a fairly short string containing two or more # non-period punctuation characters to a length larger than the length of # an ellipsis (i.e., 3 characters) plus the length of those non-period # punctuation characters (e.g., 2 in this case) truncates that string in a # reasonable but complex manner too complex to detail here. *sigh* assert truncate_str(text='Sent/>', max_len=4) == '...>' # Assert that truncating fairly short strings containing two or more # non-period punctuation characters truncates those strings to the desired # maximum length by replacing all trailing characters in those strings up to # but *NOT* including those non-period punctuation characters with an # ellipsis. assert truncate_str(text='Sent />', max_len=5) == '.../>' assert truncate_str(text='Sent t/>', max_len=6) == 'S.../>' assert truncate_str(text='Sent to/>', max_len=7) == 'Se.../>' # Assert that truncating fairly short strings suffixed by one period # followed by two or more non-period punctuation characters truncates those # strings to the desired maximum length by replacing all trailing characters # in those strings up to and including those periods but *NOT* those # non-period punctuation characters with an ellipsis. assert truncate_str(text='Sen."?', max_len=5) == '..."?' assert truncate_str(text='Sent."?', max_len=6) == 'S..."?' assert truncate_str(text='Sent ."?', max_len=7) == 'Se..."?' assert truncate_str(text='Sent t."?', max_len=8) == 'Sen..."?' # Assert that truncating fairly short strings suffixed by an ellipsis # truncates those strings to the desired maximum length while preserving # that ellipsis as is. assert truncate_str(text='Se...', max_len=4) == 'S...' assert truncate_str(text='Se....', max_len=4) == 'S...' assert truncate_str(text='Sen...', max_len=4) == 'S...' assert truncate_str(text='Sent...', max_len=4) == 'S...' beartype-0.18.5/beartype_test/a00_unit/a20_util/text/test_utiltextprefix.py000066400000000000000000000014321461113517100270250ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. """ **Beartype object prefix utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._util.text.utiltextprefix` submodule. """ # ....................{ IMPORTS }.................... # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... beartype-0.18.5/beartype_test/a00_unit/a20_util/text/test_utiltextrepr.py000066400000000000000000000135401461113517100265030ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **string munging** utility unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.text.utiltextmunge` submodule. ''' # ....................{ IMPORTS }.................... # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_represent_object() -> None: ''' Test the :func:`beartype._util.text.utiltextrepr.represent_object` function. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype._util.text.utiltextrepr import represent_object # ....................{ CLASSES }.................... # Arbitrary class defining an unpunctuated representation (i.e., # representation *NOT* already suffixed by punctuation). class ToASkylark(object): def __repr__(self): return 'Like a cloud of fire;' # Arbitrary class defining an empty representation. class ThouDostFloatAndRun(object): def __repr__(self): return '' # ....................{ LOCALS }.................... # Arbitrary object whose representation is both length *AND* contains one # or more newlines. THE_EVERLASTING_UNIVERSE_OF_THINGS = ( 'And singing still dost soar,\nand soaring ever singest.') # ....................{ ASSERTS }.................... # Assert this representer preserves the representations of terse objects # already suffixed by punctuation as is. assert represent_object(b'Higher still and higher') == repr( b'Higher still and higher') assert represent_object('From the earth thou springest') == repr( 'From the earth thou springest') assert represent_object(ToASkylark) == repr(ToASkylark) # Assert this representer punctuates the representations of terse objects # *NOT* already suffixed by punctuation. assert represent_object(ToASkylark()) == '"Like a cloud of fire;"' # Assert this representer double-quotes empty representations. assert represent_object(ThouDostFloatAndRun()) == '""' # Representation of this object. the_blue_deep_thou_wingest = represent_object( obj=THE_EVERLASTING_UNIVERSE_OF_THINGS, max_len=42) # Assert this representer truncates this representations to that length. assert len(the_blue_deep_thou_wingest) == 42 # Assert this representer removes *ALL* newlines from this representation. assert '\n' not in the_blue_deep_thou_wingest # Representation of this object, reproduced to exercise caching # optimizations performed within this function. the_pale_purple_even = represent_object( obj=THE_EVERLASTING_UNIVERSE_OF_THINGS, max_len=42) # Assert the two representations to be the same. assert the_blue_deep_thou_wingest == the_pale_purple_even # Assert this representer truncates the representations of any objects # exceeding an unreasonably small maximum length to that length. like_a_star_of_heaven = represent_object( obj='In the golden lightning\nOf the sunken sun,', max_len=2) assert len(like_a_star_of_heaven) == 2 def test_represent_func() -> None: ''' Test the :func:`beartype._util.text.utiltextrepr.represent_func` function. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype._util.text.utiltextrepr import represent_func from beartype._util.utilobject import get_object_basename_scoped from beartype_test.a00_unit.data.data_type import ( function, function_lambda, function_partial, ) # ....................{ ASSERTS }.................... # Assert that the representation of a pure-Python non-lambda function is # simply its name. assert represent_func(function) == get_object_basename_scoped(function) # Assert that the representation of a pure-Python lambda function is the # source code for that lambda. Although we *COULD* attempt to assert the # actual code, doing so would be fragile across Python versions. Instead, we # simply assert this representation to be a non-empty string for sanity. function_lambda_repr = represent_func(function_lambda) assert isinstance(function_lambda_repr, str) assert function_lambda_repr # Assert that the representation of a pure-Python "functools.partial" object # is its actual repr() string. assert represent_func(function_partial) == repr(function_partial) def test_represent_pith() -> None: ''' Test the Test the :func:`beartype._util.text.utiltextrepr.represent_pith` function. ''' # Defer test-specific imports. from beartype._util.text.utiltextrepr import represent_pith # Custom type to be represented below. class CustomType(object): def __repr__(self) -> str: return ( 'Collaborator‐ily brambling unspiritually') # Assert this representer represents builtin types in the expected way. repr_builtin = represent_pith(42) assert 'int' in repr_builtin assert '42' in repr_builtin # Assert this representer represents custom types in the expected way. repr_nonbuiltin = represent_pith(CustomType()) assert 'CustomType' in repr_nonbuiltin assert 'Collaborator‐ily brambling unspiritually' in repr_nonbuiltin beartype-0.18.5/beartype_test/a00_unit/a20_util/text/test_utiltextversion.py000066400000000000000000000100631461113517100272150ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **version string utility** unit tests. This submodule unit tests the public API of the private :mod:`beartype._util.text.utiltextversion` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_convert_str_version_to_tuple() -> None: ''' Test the :func:`beartype._util.text.utiltextversion.convert_str_version_to_tuple` exception-raiser. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeUtilTextVersionException from beartype._util.text.utiltextversion import convert_str_version_to_tuple from pytest import raises # ....................{ PASS ~ undelimited }.................... # Assert that this converter converts a valid version string containing *NO* # "." delimiter comprising zero to the expected tuple. assert convert_str_version_to_tuple('0') == (0,) # Assert that this converter converts a valid version string containing *NO* # "." delimiter comprising a single positive integer to the expected tuple. assert convert_str_version_to_tuple('26') == (26,) # Assert that this converter converts a valid version string containing *NO* # "." delimiter comprising a single positive integer suffixed by a non-empty # substring that is *NOT* a non-negative integer to the expected tuple. assert convert_str_version_to_tuple('26rc1') == (26,) # ....................{ PASS ~ delimited }.................... # Assert that this converter converts a valid version string comprising one # or more "."-delimited non-negative integers to the expected tuple. assert convert_str_version_to_tuple('0.26.05') == (0, 26, 5,) # Assert that this converter converts a valid version string comprising one # or more "."-delimited non-negative integers suffixed by a non-empty # substring that is *NOT* a non-negative integer to the expected tuple. assert convert_str_version_to_tuple('0.26.05rc1') == (0, 26, 5,) # ....................{ FAIL }.................... # Assert that this converter raises the expected exception when passed an # invalid version string containing *NO* "." delimiter comprising a single # non-empty substring that is *NOT* a non-negative integer. with raises(_BeartypeUtilTextVersionException): convert_str_version_to_tuple('rc1') # Assert that this converter raises the expected exception when passed an # invalid version string containing *NO* "." delimiter comprising a single # negative integer. with raises(_BeartypeUtilTextVersionException): convert_str_version_to_tuple('-15') # Assert that this converter raises the expected exception when passed an # invalid version string comprising (in order): # 1. One or more "."-delimited non-negative integers followed by... # 2. A "."-delimited negative integer. with raises(_BeartypeUtilTextVersionException): convert_str_version_to_tuple('0.-15') # Assert that this converter raises the expected exception when passed an # invalid version string comprising (in order): # 1. One or more "."-delimited non-negative integers followed by... # 2. A "."-delimited substring that is *NOT* a non-negative integer followed # by... # 3. One or more "."-delimited non-negative integers. with raises(_BeartypeUtilTextVersionException): convert_str_version_to_tuple('0.15rc1.2') beartype-0.18.5/beartype_test/a00_unit/a40_api/000077500000000000000000000000001461113517100211265ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a40_api/__init__.py000066400000000000000000000000001461113517100232250ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a40_api/conf/000077500000000000000000000000001461113517100220535ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a40_api/conf/test_confcls.py000066400000000000000000000473741461113517100251320ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype configuration unit tests.** This submodule unit tests the subset of the public API of the :mod:`beartype` package defined by the private :mod:`beartype._conf.confcls` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_conf_dataclass() -> None: ''' Test the public :func:`beartype.BeartypeConf` class. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import ( BeartypeConf, BeartypeHintOverrides, BeartypeStrategy, BeartypeViolationVerbosity, ) from beartype.roar import ( BeartypeConfParamException, BeartypeCallHintParamViolation, BeartypeCallHintReturnViolation, BeartypeDoorHintViolation, ) from beartype.typing import Union from beartype._conf.confoverrides import ( BEARTYPE_HINT_OVERRIDES_EMPTY, BEARTYPE_HINT_OVERRIDES_PEP484_TOWER, ) from beartype._util.utilobject import get_object_type_basename from pytest import raises # ....................{ CLASSES }.................... class FakeBool(object): ''' Fake boolean class, just because. Look. Just accept it. ''' def __bool__(self) -> bool: return False # ....................{ LOCALS }.................... # Tuple of all substrings to assert as trivially contained within the string # returned by the BeartypeConf.__repr__() dunder method, including: # * The unqualified basename of this class. # * The unqualified basenames of and all public fields of this class. BEAR_CONF_REPR_SUBSTRS = ( 'BeartypeConf', 'claw_is_pep526', 'hint_overrides', 'is_color', 'is_debug', 'is_pep484_tower', 'strategy', 'violation_door_type', 'violation_param_type', 'violation_return_type', 'violation_type', 'violation_verbosity', 'warning_cls_on_decorator_exception', ) # Default (i.e., unparametrized) beartype configuration. BEAR_CONF_DEFAULT = BeartypeConf() # Non-empty hint overrides mapping one or more arbitrary source type hints # to corresponding arbitrary target type hints. BEAR_HINT_OVERRIDES_NONEMPTY = BeartypeHintOverrides( {bool: Union[bool, FakeBool]}) # All possible keyword arguments initialized to non-default values with # which to instantiate a non-default beartype configuration. BEAR_CONF_NONDEFAULT_KWARGS = dict( claw_is_pep526=False, hint_overrides=BEAR_HINT_OVERRIDES_NONEMPTY, is_color=True, is_debug=True, is_pep484_tower=True, strategy=BeartypeStrategy.Ologn, violation_door_type=RuntimeError, violation_param_type=TypeError, violation_return_type=ValueError, violation_type=AttributeError, violation_verbosity=BeartypeViolationVerbosity.MINIMAL, warning_cls_on_decorator_exception=None, ) # Arbitrary parametrized beartype configuration. BEAR_CONF_NONDEFAULT = BeartypeConf(**BEAR_CONF_NONDEFAULT_KWARGS) # Arbitrary parametrized beartype configurations setting all possible # combinations of a default violation type and another violation type. BEAR_CONF_NONDEFAULT_VIOLATION_DOOR = BeartypeConf( violation_type=AttributeError, violation_door_type=RuntimeError) BEAR_CONF_NONDEFAULT_VIOLATION_PARAM = BeartypeConf( violation_type=AttributeError, violation_param_type=TypeError) BEAR_CONF_NONDEFAULT_VIOLATION_RETURN = BeartypeConf( violation_type=AttributeError, violation_return_type=ValueError) # # Tuple of these beartype configurations. # BEAR_CONFS = (BEAR_CONF_DEFAULT, BEAR_CONF_NONDEFAULT) # ....................{ PASS }.................... # Assert beartype configurations to be self-memoizing across both # unparametrized and parametrized type instantiations. # # Note that the latter explicitly validates that this memoization ignores # the order in which parameters are passed. assert BeartypeConf() is BeartypeConf() assert ( BeartypeConf( strategy=BeartypeStrategy.On, claw_is_pep526=False, hint_overrides=BEAR_HINT_OVERRIDES_NONEMPTY, is_debug=True, is_color=True, is_pep484_tower=True, violation_door_type=RuntimeError, violation_param_type=TypeError, violation_return_type=ValueError, violation_type=AttributeError, violation_verbosity=BeartypeViolationVerbosity.MINIMAL, warning_cls_on_decorator_exception=UserWarning, ) is BeartypeConf( warning_cls_on_decorator_exception=UserWarning, violation_verbosity=BeartypeViolationVerbosity.MINIMAL, violation_type=AttributeError, violation_return_type=ValueError, violation_param_type=TypeError, violation_door_type=RuntimeError, is_pep484_tower=True, is_color=True, is_debug=True, hint_overrides=BEAR_HINT_OVERRIDES_NONEMPTY, claw_is_pep526=False, strategy=BeartypeStrategy.On, ) ) # ....................{ PASS ~ properties }.................... # Assert that the default configuration contains the expected fields. assert BEAR_CONF_DEFAULT.claw_is_pep526 is True assert BEAR_CONF_DEFAULT.hint_overrides is BEARTYPE_HINT_OVERRIDES_EMPTY assert BEAR_CONF_DEFAULT.is_color is None assert BEAR_CONF_DEFAULT.is_debug is False assert BEAR_CONF_DEFAULT.is_pep484_tower is False assert BEAR_CONF_DEFAULT.strategy is BeartypeStrategy.O1 assert BEAR_CONF_DEFAULT.violation_door_type is ( BeartypeDoorHintViolation) assert BEAR_CONF_DEFAULT.violation_param_type is ( BeartypeCallHintParamViolation) assert BEAR_CONF_DEFAULT.violation_return_type is ( BeartypeCallHintReturnViolation) assert BEAR_CONF_DEFAULT.violation_type is None assert BEAR_CONF_DEFAULT.violation_verbosity is ( BeartypeViolationVerbosity.DEFAULT) assert BEAR_CONF_DEFAULT.warning_cls_on_decorator_exception is None assert BEAR_CONF_DEFAULT._is_warning_cls_on_decorator_exception_set is False # Assert that the non-default configuration contains the expected fields. assert BEAR_CONF_NONDEFAULT.claw_is_pep526 is False assert BEAR_CONF_NONDEFAULT.hint_overrides == ( BEAR_HINT_OVERRIDES_NONEMPTY | BEARTYPE_HINT_OVERRIDES_PEP484_TOWER) assert BEAR_CONF_NONDEFAULT.is_color is True assert BEAR_CONF_NONDEFAULT.is_debug is True assert BEAR_CONF_NONDEFAULT.is_pep484_tower is True assert BEAR_CONF_NONDEFAULT.strategy is BeartypeStrategy.Ologn assert BEAR_CONF_NONDEFAULT.violation_door_type is RuntimeError assert BEAR_CONF_NONDEFAULT.violation_param_type is TypeError assert BEAR_CONF_NONDEFAULT.violation_return_type is ValueError assert BEAR_CONF_NONDEFAULT.violation_type is AttributeError assert BEAR_CONF_NONDEFAULT.violation_verbosity is ( BeartypeViolationVerbosity.MINIMAL) assert BEAR_CONF_NONDEFAULT.warning_cls_on_decorator_exception is None assert BEAR_CONF_NONDEFAULT._is_warning_cls_on_decorator_exception_set is ( True) # Assert that the non-default violation-specific configurations contain the # expected fields. assert BEAR_CONF_NONDEFAULT_VIOLATION_DOOR.violation_type is AttributeError assert BEAR_CONF_NONDEFAULT_VIOLATION_DOOR.violation_door_type is ( RuntimeError) assert BEAR_CONF_NONDEFAULT_VIOLATION_DOOR.violation_param_type is ( AttributeError) assert BEAR_CONF_NONDEFAULT_VIOLATION_DOOR.violation_return_type is ( AttributeError) assert BEAR_CONF_NONDEFAULT_VIOLATION_PARAM.violation_type is AttributeError assert BEAR_CONF_NONDEFAULT_VIOLATION_PARAM.violation_door_type is ( AttributeError) assert BEAR_CONF_NONDEFAULT_VIOLATION_PARAM.violation_param_type is ( TypeError) assert BEAR_CONF_NONDEFAULT_VIOLATION_PARAM.violation_return_type is ( AttributeError) assert BEAR_CONF_NONDEFAULT_VIOLATION_RETURN.violation_type is ( AttributeError) assert BEAR_CONF_NONDEFAULT_VIOLATION_RETURN.violation_door_type is ( AttributeError) assert BEAR_CONF_NONDEFAULT_VIOLATION_RETURN.violation_param_type is ( AttributeError) assert BEAR_CONF_NONDEFAULT_VIOLATION_RETURN.violation_return_type is ( ValueError) # ....................{ PASS ~ equals }.................... # Assert that two differing configurations compare unequal. assert BEAR_CONF_DEFAULT != BEAR_CONF_NONDEFAULT # Assert that two identical configurations compare equal. assert BEAR_CONF_DEFAULT == BeartypeConf() assert BEAR_CONF_NONDEFAULT == BeartypeConf(**BEAR_CONF_NONDEFAULT_KWARGS) # ....................{ PASS ~ hash }.................... # Assert that two identical configurations hash equal. assert hash(BEAR_CONF_DEFAULT) == hash(BeartypeConf()) assert hash(BEAR_CONF_NONDEFAULT) == hash( BeartypeConf(**BEAR_CONF_NONDEFAULT_KWARGS)) # Assert that two differing configurations hash unequal. assert hash(BEAR_CONF_DEFAULT) != hash(BEAR_CONF_NONDEFAULT) # ....................{ PASS ~ repr }.................... # Unqualified basename of the class of all beartype configurations. BEAR_CONF_BASENAME = get_object_type_basename(BEAR_CONF_DEFAULT) # Machine-readable representation of the default configuration. BEAR_CONF_DEFAULT_REPR = repr(BEAR_CONF_DEFAULT) # Assert that this representation is simply the unqualified basename of the # class of this configuration followed by empty parens. assert BEAR_CONF_DEFAULT_REPR == f'{BEAR_CONF_BASENAME}()' # Machine-readable representation of a non-default configuration. BEAR_CONF_NONDEFAULT_REPR = repr(BEAR_CONF_NONDEFAULT) # Assert that this representation is prefixed by the unqualified basename of # the class of this configuration followed by an opening parens. assert BEAR_CONF_NONDEFAULT_REPR.startswith(f'{BEAR_CONF_BASENAME}(') # Assert that this representation is suffixed by a closing parens. assert BEAR_CONF_NONDEFAULT_REPR.endswith(')') # Assert that this representation is *NOT* suffixed by a # whitespace-delimited comma followed by a closing parens. assert not BEAR_CONF_NONDEFAULT_REPR.endswith(', )') # Assert that this representation embeds the names and values of all public # non-default fields of this configuration. for bear_conf_repr_substr in BEAR_CONF_REPR_SUBSTRS: assert bear_conf_repr_substr in BEAR_CONF_NONDEFAULT_REPR # ....................{ FAIL }.................... # Assert that instantiating a configuration with an invalid parameter raises # the expected exception. with raises(BeartypeConfParamException): BeartypeConf(claw_is_pep526=( 'The fountains mingle with the river')) with raises(BeartypeConfParamException): BeartypeConf(hint_overrides=( 'Wildered, and wan, and panting, she returned.')) with raises(BeartypeConfParamException): BeartypeConf(is_color=( 'And many sounds, and much of life and death.')) with raises(BeartypeConfParamException): BeartypeConf(is_debug=( 'Interpret, or make felt, or deeply feel.')) with raises(BeartypeConfParamException): BeartypeConf(is_pep484_tower=( 'In the calm darkness of the moonless nights,')) with raises(BeartypeConfParamException): BeartypeConf(strategy=( 'By all, but which the wise, and great, and good')) with raises(BeartypeConfParamException): BeartypeConf(violation_door_type=( 'A vision to the sleep of him who spurned')) with raises(BeartypeConfParamException): BeartypeConf(violation_door_type=bool) with raises(BeartypeConfParamException): BeartypeConf(violation_param_type=( 'His strong heart sunk and sickened with excess')) with raises(BeartypeConfParamException): BeartypeConf(violation_param_type=str) with raises(BeartypeConfParamException): BeartypeConf(violation_return_type=( 'Of love. He reared his shuddering limbs and quelled')) with raises(BeartypeConfParamException): BeartypeConf(violation_return_type=int) with raises(BeartypeConfParamException): BeartypeConf(violation_type=( 'Her choicest gifts. He eagerly pursues')) with raises(BeartypeConfParamException): BeartypeConf(violation_type=complex) with raises(BeartypeConfParamException): BeartypeConf(violation_verbosity=( 'His gasping breath, and spread his arms to meet')) with raises(BeartypeConfParamException): BeartypeConf(warning_cls_on_decorator_exception=( 'He lived, he died, he sung, in solitude.')) with raises(BeartypeConfParamException): BeartypeConf(warning_cls_on_decorator_exception=RuntimeError) # Assert that instantiating a configuration with conflicting # "is_pep484_tower" and "hint_overrides" parameters raises the expected # exception. with raises(BeartypeConfParamException): BeartypeConf( is_pep484_tower=True, hint_overrides=BeartypeHintOverrides({float: complex}) ) with raises(BeartypeConfParamException): BeartypeConf( is_pep484_tower=True, hint_overrides=BeartypeHintOverrides({complex: int}) ) # Assert that attempting to modify any public read-only property of this # dataclass raises the expected exception. with raises(AttributeError): BEAR_CONF_DEFAULT.claw_is_pep526 = True with raises(AttributeError): BEAR_CONF_DEFAULT.hint_overrides = {} with raises(AttributeError): BEAR_CONF_DEFAULT.is_color = True with raises(AttributeError): BEAR_CONF_DEFAULT.is_debug = True with raises(AttributeError): BEAR_CONF_DEFAULT.is_pep484_tower = True with raises(AttributeError): BEAR_CONF_DEFAULT.strategy = BeartypeStrategy.O0 with raises(AttributeError): BEAR_CONF_DEFAULT.violation_door_type = RuntimeError with raises(AttributeError): BEAR_CONF_DEFAULT.violation_param_type = TypeError with raises(AttributeError): BEAR_CONF_DEFAULT.violation_return_type = ValueError with raises(AttributeError): BEAR_CONF_DEFAULT.violation_type = AttributeError with raises(AttributeError): BEAR_CONF_DEFAULT.violation_verbosity = ( BeartypeViolationVerbosity.MINIMAL) with raises(AttributeError): BEAR_CONF_DEFAULT.warning_cls_on_decorator_exception = None # ....................{ TESTS ~ arg }.................... def test_conf_is_color(monkeypatch: 'pytest.MonkeyPatch') -> None: ''' Test the ``is_color`` parameter accepted by the :class:`beartype.BeartypeConf` class with respect to the external ``${BEARTYPE_IS_COLOR}`` shell environment variable also respected by that class, which interact in non-trivial ways. Parameters ---------- monkeypatch : MonkeyPatch :mod:`pytest` fixture allowing various state associated with the active Python process to be temporarily changed for the duration of this test. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import BeartypeConf from beartype.roar import ( BeartypeConfShellVarException, BeartypeConfShellVarWarning, ) from beartype._data.os.dataosshell import ( SHELL_VAR_CONF_IS_COLOR_NAME) from pytest import ( raises, warns, ) # ....................{ PASS }.................... # Temporarily enable the ${BEARTYPE_IS_COLOR} environment variable. monkeypatch.setenv(SHELL_VAR_CONF_IS_COLOR_NAME, 'True') # Non-default beartype configuration masquerading as the default # beartype configuration. bear_conf = BeartypeConf() # Assert that this configuration enabled the "is_color" parameter # despite the above instantiation *NOT* passing that parameter. assert bear_conf.is_color is True # Temporarily disable the ${BEARTYPE_IS_COLOR} environment variable. monkeypatch.setenv(SHELL_VAR_CONF_IS_COLOR_NAME, 'False') # Non-default beartype configuration masquerading as the default # beartype configuration. bear_conf = BeartypeConf() # Assert that this configuration disabled the "is_color" parameter # despite the above instantiation *NOT* passing that parameter. assert bear_conf.is_color is False # Temporarily nullify the ${BEARTYPE_IS_COLOR} environment variable. monkeypatch.setenv(SHELL_VAR_CONF_IS_COLOR_NAME, 'None') # Non-default beartype configuration masquerading as the default # beartype configuration. bear_conf = BeartypeConf() # Assert that this configuration nullified the "is_color" parameter # despite the above instantiation *NOT* passing that parameter. assert bear_conf.is_color is None # Non-default beartype configuration explicitly passing the same value for # the "is_color" parameter as the ${BEARTYPE_IS_COLOR} environment variable # is also currently set to. bear_conf = BeartypeConf(is_color=None) # Assert that this configuration nullified the "is_color" parameter. assert bear_conf.is_color is None # ....................{ FAIL }.................... # Assert that attempting to instantiate a beartype configuration when the # ${BEARTYPE_IS_COLOR} environment variable is set to a valid string value # differing from the valid non-string value passed for the "is_color" # parameter emits the expected non-fatal warning. with warns(BeartypeConfShellVarWarning): # Non-default beartype configuration explicitly passing a differing # value for the "is_color" parameter as the ${BEARTYPE_IS_COLOR} # environment variable is currently set to. bear_conf = BeartypeConf(is_color=True) # Assert that this configuration override the passed value of the # "is_color" parameter in favour of the corresponding value of the # ${BEARTYPE_IS_COLOR} environment variable. assert bear_conf.is_color is None # Assert that attempting to instantiate a beartype configuration when the # ${BEARTYPE_IS_COLOR} environment variable is set to invalid string value # raises the expected exception. with raises(BeartypeConfShellVarException) as exception_info: # Temporarily set ${BEARTYPE_IS_COLOR} to an invalid value. monkeypatch.setenv( SHELL_VAR_CONF_IS_COLOR_NAME, 'Strangers have wept to hear his passionate notes,', ) # Attempt to instantiate a beartype configuration. BeartypeConf() # Assert that this exception message contains an expected substring, whose # construction is non-trivial and thus liable to improper construction. assert '"True", "False", or "None"' in str(exception_info.value) beartype-0.18.5/beartype_test/a00_unit/a40_api/conf/test_confenum.py000066400000000000000000000034221461113517100252770ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype configuration unit tests.** This submodule unit tests the subset of the public API of the :mod:`beartype` package defined by the private :mod:`beartype._conf.confenum` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_conf_strategy() -> None: ''' Test the public :func:`beartype.BeartypeStrategy` enumeration. ''' # Defer test-specific imports. from beartype import BeartypeStrategy # Assert this enumeration declares the expected members. assert isinstance(BeartypeStrategy.O0, BeartypeStrategy) assert isinstance(BeartypeStrategy.O1, BeartypeStrategy) assert isinstance(BeartypeStrategy.Ologn, BeartypeStrategy) assert isinstance(BeartypeStrategy.On, BeartypeStrategy) def test_conf_violation_verbosity() -> None: ''' Test the public :func:`beartype.BeartypeViolationVerbosity` enumeration. ''' # Defer test-specific imports. from beartype import BeartypeViolationVerbosity # Assert this enumeration declares the expected members. assert isinstance(BeartypeViolationVerbosity.MINIMAL, int) assert isinstance(BeartypeViolationVerbosity.DEFAULT, int) assert isinstance(BeartypeViolationVerbosity.MAXIMAL, int) beartype-0.18.5/beartype_test/a00_unit/a40_api/conf/test_confoverrides.py000066400000000000000000000055131461113517100263400ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype configuration hint overrides unit tests.** This submodule unit tests the public :class:`beartype.BeartypeHintOverrides` class. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_conf_overrides() -> None: ''' Test the public :func:`beartype.BeartypeHintOverrides` class. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import BeartypeHintOverrides from beartype.roar import BeartypeHintOverridesException from beartype.typing import ( List, Tuple, ) from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_10 from numbers import Real from pytest import raises # ....................{ LOCALS }.................... # Problematic hint overrides containing one or more recursive hint overrides # (which currently induce infinite recursion during code generation) *OTHER* # than recursive union hint overrides (which are explicitly supported). hint_overrides_bad = { # Valid non-recursive hint override. float: Real, } # If the active Python interpreter targets Python >= 3.10 and thus supports # PEP 604-compliant new unions... if IS_PYTHON_AT_LEAST_3_10: # Valid recursive union hint override, intentionally listed *BEFORE* an # invalid recursive non-union hint override. Doing so exercises that # @beartype supports the former but *NOT* the latter. hint_overrides_bad[complex] = complex | float | int, # Invalid recursive non-union hint override. hint_overrides_bad[List[str]] = Tuple[List[str], ...], # ....................{ FAIL }.................... # Assert that the "BeartypeHintOverrides" class raises the expected # exception when instantiated with these hint overrides. with raises(BeartypeHintOverridesException) as exception_info: BeartypeHintOverrides(hint_overrides_bad) # Message of the exception raised above. exception_message = str(exception_info.value) # Assert that this message embeds the machine-readable representation of the # invalid recursive non-union type hint in question. assert repr(List[str]) in exception_message beartype-0.18.5/beartype_test/a00_unit/a40_api/door/000077500000000000000000000000001461113517100220715ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a40_api/door/__init__.py000066400000000000000000000000001461113517100241700ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a40_api/door/_doorfixture.py000066400000000000000000000550131461113517100251600ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **Decidedly Object-Oriented Runtime-checking (DOOR) fixtures** (i.e., :mod:`pytest`-specific context managers passed as parameters to unit tests exercising the :mod:`beartype.door` subpackage). ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from pytest import fixture # ....................{ FIXTURES ~ equality }.................... @fixture(scope='session') def door_cases_equality() -> 'Iterable[Tuple[object, object, bool]]': ''' Session-scoped fixture returning an iterable of **hint equality cases** (i.e., 3-tuples ``(hint_a, hint_b, is_equal)`` describing the equality relations between two PEP-compliant type hints), efficiently cached across all tests requiring this fixture. This iterable is intentionally defined by the return of this fixture rather than as a global constant of this submodule. Why? Because the former safely defers all heavyweight imports required to define this iterable to the call of the first unit test requiring this fixture, whereas the latter unsafely performs those imports at pytest test collection time. Returns -------- Iterable[Tuple[object, object, bool]] Iterable of one or more 3-tuples ``(hint_a, hint_b, is_equal)``, where: * ``hint_a`` is the PEP-compliant type hint to be passed as the first parameter to the :meth:`beartype.door.TypeHint.__equals__` tester. * ``hint_b`` is the PEP-compliant type hint to be passed as the second parameter to the :meth:`beartype.door.TypeHint.__equals__` tester. * ``is_equal`` is ``True`` only if these hints are equal according to that tester. ''' # ..................{ IMPORTS }.................. # Defer fixture-specific imports. from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_9 from numbers import Number # Intentionally import from "typing" rather than "beartype.typing" to # guarantee PEP 484-compliant type hints. from typing import ( Any, List, Tuple, Union, ) # ..................{ LISTS }.................. HINT_EQUALITY_CASES = [ # ..................{ HINTS ~ argless : bare }.................. # PEP 484-compliant unsubscripted type hints, which are necessarily # equal to themselves. (tuple, Tuple, True), (list, list, True), (list, List, True), # ..................{ HINTS ~ arg : sequence }.................. # PEP 484-compliant sequence type hints. (list, List[Any], True), (tuple, Tuple[Any, ...], True), # ..................{ HINTS ~ arg : union }.................. # PEP 484-compliant union type hints. (Union[int, str], Union[str, list], False), (Union[Number, int], Union[Number, float], True), # Test that union equality ignores order. (Union[int, str], Union[str, int], True), # Test that union equality compares child type hints collectively rather # than individually. # # Note that this pair of cases tests numerous edge cases, including: # * Equality comparison of non-unions against unions. Although # "Union[int]" superficially appears to be a union, Python reduces # "Union[int]" to simply "int" at runtime. (Union[bool, int], Union[int], True), (Union[int], Union[bool, int], True), ] # If the active Python interpreter targets Python >= 3.9 and thus supports # both PEP 585 and 593... if IS_PYTHON_AT_LEAST_3_9: from beartype.typing import Annotated from collections.abc import ( Awaitable as AwaitableABC, Sequence as SequenceABC, ) # Append cases exercising version-specific relations. HINT_EQUALITY_CASES.extend(( # PEP 585-compliant type hints. (tuple[str, ...], Tuple[str, ...], True), (list[str], List[str], True), (AwaitableABC[SequenceABC[int]], AwaitableABC[SequenceABC[int]], True), # PEP 593-compliant type hints. (Annotated[int, "hi"], Annotated[int, "hi"], True), (Annotated[int, "hi"], Annotated[int, "low"], False), (Annotated[int, "hi"], Annotated[int, "low"], False), )) # Return this mutable list coerced into an immutable tuple for safety. return tuple(HINT_EQUALITY_CASES) # ....................{ FIXTURES ~ subhint }.................... @fixture(scope='session') def door_cases_subhint() -> 'Iterable[Tuple[object, object, bool]]': ''' Session-scoped fixture returning an iterable of **hint subhint cases** (i.e., 3-tuples ``(subhint, superhint, is_subhint)`` describing the subhint relations between two PEP-compliant type hints), efficiently cached across all tests requiring this fixture. This iterable is intentionally defined by the return of this fixture rather than as a global constant of this submodule. Why? Because the former safely defers all heavyweight imports required to define this iterable to the call of the first unit test requiring this fixture, whereas the latter unsafely performs those imports at pytest test collection time. Returns -------- Iterable[Tuple[object, object, bool]] Iterable of one or more 3-tuples ``(subhint, superhint, is_subhint)``, where: * ``subhint`` is the PEP-compliant type hint to be passed as the first parameter to the :func:`beartype.door.is_subhint` tester. * ``superhint`` is the PEP-compliant type hint to be passed as the second parameter to the :func:`beartype.door.is_subhint` tester. * ``is_subhint`` is ``True`` only if that subhint is actually a subhint of that superhint according to that tester. ''' # ..................{ IMPORTS }.................. # Defer fixture-specific imports. import collections.abc import typing from beartype._data.hint.datahinttyping import S, T from beartype._util.hint.pep.utilpepget import get_hint_pep_typevars from beartype._util.py.utilpyversion import ( IS_PYTHON_AT_LEAST_3_9, ) from collections.abc import ( Collection as CollectionABC, Sequence as SequenceABC, ) # Intentionally import from "beartype.typing" rather than "typing" to # guarantee PEP 544-compliant caching protocol type hints. from beartype.typing import ( Literal, Protocol, TypedDict, ) # Intentionally import from "typing" rather than "beartype.typing" to # guarantee PEP 484-compliant type hints. from typing import ( Any, Awaitable, ByteString, Callable, Collection, DefaultDict, Dict, Generic, Hashable, Iterable, List, Mapping, NamedTuple, NewType, Optional, Reversible, Sequence, Sized, Tuple, Type, TypeVar, Union, ) # ..................{ NEWTYPES }.................. NewStr = NewType('NewStr', str) # ..................{ TYPEVARS }.................. # Arbitrary constrained type variables. T_sequence = TypeVar('T_sequence', bound=SequenceABC) T_int_or_str = TypeVar('T_int_or_str', int, str) # ..................{ CLASSES }.................. class MuhThing: def muh_method(self): pass class MuhSubThing(MuhThing): pass class MuhNutherThing: def __len__(self) -> int: pass class MuhDict(TypedDict): ''' Arbitrary typed dictionary. ''' thing_one: str thing_two: int class MuhThingP(Protocol): ''' Arbitrary caching @beartype protocol. ''' def muh_method(self): ... class MuhTuple(NamedTuple): ''' Arbitrary named tuple. ''' thing_one: str thing_two: int # ..................{ CLASSES ~ generics }.................. class MuhGeneric(Generic[T]): ''' Arbitrary generic parametrized by one unconstrained type variable. ''' pass class MuhGenericTwo(Generic[S, T]): ''' Arbitrary generic parametrized by two unconstrained type variables. ''' pass class MuhGenericTwoIntInt(MuhGenericTwo[int, int]): ''' Arbitrary concrete generic subclass inheriting the :class:`.MuhGenericTwo` generic superclass subscripted twice by the builtin :class:`int` type. ''' pass # ..................{ LISTS ~ cases }.................. # List of all hint subhint cases (i.e., 3-tuples "(subhint, superhint, # is_subhint)" describing the subhint relations between two PEP-compliant # type hints) to be returned by this fixture. HINT_SUBHINT_CASES = [ # ..................{ PEP 484 ~ argless : any }.................. # PEP 484-compliant catch-all type hint. (MuhThing, Any, True), (Tuple[object, ...], Any, True), (Union[int, MuhThing], Any, True), # Although *ALL* type hints are subhints of "Any", "Any" is only a # subhint of itself. (Any, Any, True), (Any, object, False), # ..................{ PEP 484 ~ argless : bare }.................. # PEP 484-compliant unsubscripted type hints, which are necessarily # subhints of themselves. (list, list, True), (list, List, True), # PEP 484-compliant unsubscripted sequence type hints. (Sequence, List, False), (Sequence, list, False), (List, Sequence, True), (list, Sequence, True), (list, SequenceABC, True), (list, CollectionABC, True), # ..................{ PEP 484 ~ argless : type }.................. # PEP 484-compliant argumentless abstract base classes (ABCs). (bytes, ByteString, True), (str, Hashable, True), (MuhNutherThing, Sized, True), (MuhTuple, tuple, True), # not really types # PEP 484-compliant new type type hints. (NewStr, NewStr, True), (NewStr, int, False), (NewStr, str, True), (int, NewStr, False), (str, NewStr, False), # NewType act like subtypes # ..................{ PEP 484 ~ argless : typevar }.................. # PEP 484-compliant type variables. (list, T_sequence, True), (T_sequence, list, False), (int, T_int_or_str, True), (str, T_int_or_str, True), (list, T_int_or_str, False), (Union[int, str], T_int_or_str, True), (Union[int, str, None], T_int_or_str, False), (T, T_sequence, False), (T_sequence, T, True), (T_sequence, Any, True), (Any, T, True), # Any is compatible with an unconstrained TypeVar (Any, T_sequence, False), # but not vice versa # ..................{ PEP 484 ~ argless : number }.................. # Blame Guido. (bool, int, True), # PEP 484-compliant implicit numeric tower, which we explicitly and # intentionally do *NOT* comply with. Floats are not integers. Notably, # floats *CANNOT* losslessly represent many integers and are thus # incompatible in general. (float, int, False), (complex, int, False), (complex, float, False), (int, float, False), (float, complex, False), # ..................{ PEP 484 ~ arg : callable }.................. # PEP 484-compliant callable type hints. (Callable, Callable[..., Any], True), (Callable[[], int], Callable[..., Any], True), (Callable[[int, str], List[int]], Callable, True), (Callable[[int, str], List[int]], Callable, True), ( Callable[[float, List[str]], int], Callable[[int, Sequence[str]], int], True, ), (Callable[[Sequence], int], Callable[[list], int], False), (Callable[[], int], Callable[..., None], False), (Callable[..., Any], Callable[..., None], False), (Callable[[float], None], Callable[[float, int], None], False), (Callable[[], int], Sequence[int], False), (Callable[[int, str], int], Callable[[int, str], Any], True), # (types.FunctionType, Callable, True), # FIXME # ..................{ PEP 484 ~ arg : generic }.................. # PEP 484-compliant generics parametrized by one type variable. (MuhGeneric, MuhGeneric, True), (MuhGeneric, MuhGeneric[int], False), (MuhGeneric[int], MuhGeneric, True), (MuhGeneric[int], MuhGeneric[T_sequence], False), (MuhGeneric[list], MuhGeneric[T_sequence], True), (MuhGeneric[list], MuhGeneric[Sequence], True), (MuhGeneric[str], MuhGeneric[T_sequence], True), (MuhGeneric[Sequence], MuhGeneric[list], False), (MuhGeneric[T_sequence], MuhGeneric, True), #FIXME: Uncomment after resolving open issue #271, please. # PEP 484-compliant generics parametrized by two type variables. # (MuhGenericTwoIntInt, MuhGenericTwo[int, int], True), # ..................{ PEP 484 ~ arg : mapping }.................. # PEP 484-compliant mapping type hints. (dict, Dict, True), (Dict[str, int], Dict, True), (dict, Dict[str, int], False), ( DefaultDict[str, Sequence[int]], Mapping[Union[str, int], Iterable[Union[int, str]]], True, ), # ..................{ PEP 484 ~ arg : sequence }.................. # PEP 484-compliant sequence type hints. (List[int], List[int], True), (List[int], Sequence[int], True), (Sequence[int], Iterable[int], True), (Iterable[int], Sequence[int], False), (Sequence[int], Reversible[int], True), (Sequence[int], Reversible[str], False), (Collection[int], Sized, True), (List[int], List, True), # if the super is un-subscripted, assume Any (List[int], List[Any], True), (Awaitable, Awaitable[str], False), (List[int], List[str], False), # PEP 484-compliant tuple type hints. (tuple, Tuple, True), (Tuple, Tuple, True), (tuple, Tuple[Any, ...], True), (tuple, Tuple[()], False), (Tuple[()], tuple, True), (Tuple[int, str], Tuple[int, str], True), (Tuple[int, str], Tuple[int, str, int], False), (Tuple[int, str], Tuple[int, Union[int, list]], False), (Tuple[Union[int, str], ...], Tuple[int, str], False), (Tuple[int, str], Tuple[str, ...], False), (Tuple[int, str], Tuple[Union[int, str], ...], True), (Tuple[Union[int, str], ...], Tuple[Union[int, str], ...], True), (Tuple[int], Dict[str, int], False), (Tuple[Any, ...], Tuple[str, int], False), # PEP 484-compliant nested sequence type hints. (List[int], Union[str, List[Union[int, str]]], True), # ..................{ PEP 484 ~ arg : subclass }.................. # PEP 484-compliant subclass type hints. (Type[int], Type[int], True), (Type[int], Type[str], False), (Type[MuhSubThing], Type[MuhThing], True), (Type[MuhThing], Type[MuhSubThing], False), (MuhThing, Type[MuhThing], False), # ..................{ PEP 484 ~ arg : union }.................. # PEP 484-compliant unions. (int, Union[int, str], True), (Union[int, str], Union[list, int, str], True), (Union[str, int], Union[int, str, list], True), # order doesn't matter (Union[str, list], Union[str, int], False), (Union[int, str, list], list, False), (Union[int, str, list], Union[int, str], False), (int, Optional[int], True), (Optional[int], int, False), (list, Optional[Sequence], True), # ..................{ PEP 544 }.................. # PEP 544-compliant type hints. (MuhThing, MuhThingP, True), (MuhNutherThing, MuhThingP, False), (MuhThingP, MuhThing, False), # ..................{ PEP 586 }.................. # PEP 586-compliant type hints. (Literal[7], int, True), (Literal["a"], str, True), (Literal[7, 8, "3"], Union[int, str], True), (Literal[7, 8, "3"], Union[list, int], False), (Literal[True], Union[Literal[True], Literal[False]], True), (Literal[7, 8], Literal[7, 8, 9], True), (int, Literal[7], False), (Union[Literal[True], Literal[False]], Literal[True], False), # ..................{ PEP 589 }.................. # PEP 589-compliant type hints. (MuhDict, dict, True), ] # ..................{ LISTS ~ typing }.................. # List of the unqualified basenames of all standard ABCs published by # the standard "collections.abc" module, defined as... COLLECTIONS_ABC_BASENAMES = [ # For the unqualified basename of each attribute defined by the standard # "collections.abc" module... COLLECTIONS_ABC_BASENAME for COLLECTIONS_ABC_BASENAME in dir(collections.abc) # If this basename is *NOT* prefixed by an underscore, this attribute is # public and thus an actual ABC. In this case, include this ABC. if not COLLECTIONS_ABC_BASENAME.startswith('_') # Else, this is an unrelated private attribute. In this case, silently # ignore this attribute and continue to the next. ] # List of the unqualified basenames of all standard abstract base classes # (ABCs) supported by the standard "typing" module, defined as the # concatenation of... TYPING_ABC_BASENAMES = ( # List of the unqualified basenames of all standard ABCs published by # the standard "collections.abc" module *PLUS*... COLLECTIONS_ABC_BASENAMES + # List of the unqualified basenames of all ancillary ABCs *NOT* # published by the standard "collections.abc" module but nonetheless # supported by the standard "typing" module. ['Deque'] ) # ..................{ HINTS ~ abcs }.................. # For the unqualified basename of each standard ABCs supported by the # standard "typing" module... # # Note this also constitutes a smoke test (i.e., high-level test validating # core functionality) for whether the DOOR API supports standard abstract # base classes (ABCs). Smoke out those API inconsistencies, pytest! for TYPING_ABC_BASENAME in TYPING_ABC_BASENAMES: #FIXME: This logic is likely to fail under a future Python release. # Type hint factory published by the "typing" module corresponding to # this ABC if any *OR* "None" otherwise (i.e., if "typing" publishes # *NO* such type hint factory). TypingABC = getattr(typing, TYPING_ABC_BASENAME, None) # If "typing" publishes *NO* such type hint factory, silently ignore # this ABC and continue to the next. if TypingABC is None: continue # Else, "typing" publishes this type hint factory. # Number of type variables parametrizing this ABC, defined as either... TYPING_ABC_TYPEVARS_LEN = ( # If the active Python interpreter targets Python >= 3.9, a private # instance variable of this type hint factory yielding this # metadata. Under Python >= 3.9, unsubscripted type hint factories # are *NOT* parametrized by type variables. TypingABC._nparams if IS_PYTHON_AT_LEAST_3_9 else # Else, the active Python interpreter targets Python < 3.9. In this # case, the number of type variables directly parametrizing this # ABC. len(get_hint_pep_typevars(TypingABC)) ) # If this ABC is parametrized by one or more type variables, exercise # that this ABC subscripted by one or more arbitrary concrete types is a # non-trivial subhint of this same ABC subscripted by one or more # arbitrary different ABCs of those concrete types. if TYPING_ABC_TYPEVARS_LEN: subhint = TypingABC[(list,) * TYPING_ABC_TYPEVARS_LEN] superhint = TypingABC[(Sequence,) * TYPING_ABC_TYPEVARS_LEN] # Else, this ABC is parametrized by *NO* type variables. In this case, # fallback to exercising that this ABC is a trivial subhint of itself. else: subhint = TypingABC superhint = TypingABC # Append a new hint subhint case exercising that this subhint is # actually a subhint of this superhint. HINT_SUBHINT_CASES.append((subhint, superhint, True)) # ..................{ HINTS ~ version }.................. # If the active Python interpreter targets Python >= 3.9 and thus # supports PEP 585 and 593... if IS_PYTHON_AT_LEAST_3_9: from beartype.typing import Annotated # Append cases exercising version-specific relations. HINT_SUBHINT_CASES.extend(( # PEP 585-compliant type hints. (tuple, Tuple, True), (tuple[()], Tuple[()], True), # PEP 593-compliant type hints. (Annotated[int, "a note"], int, True), # annotated is subtype of unannotated (int, Annotated[int, "a note"], False), # but not vice versa (Annotated[list, True], Annotated[Sequence, True], True), (Annotated[list, False], Annotated[Sequence, True], False), (Annotated[list, 0, 0], Annotated[list, 0], False), # must have same num args (Annotated[List[int], "metadata"], List[int], True), )) # Return this mutable list coerced into an immutable tuple for safety. return tuple(HINT_SUBHINT_CASES) beartype-0.18.5/beartype_test/a00_unit/a40_api/door/a00_type/000077500000000000000000000000001461113517100235125ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a40_api/door/a00_type/__init__.py000066400000000000000000000000001461113517100256110ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a40_api/door/a00_type/test_door_typehint.py000066400000000000000000000465121461113517100300220ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **Decidedly Object-Oriented Runtime-checking (DOOR) API object-oriented** unit tests. This submodule unit tests the subset of the public API of the public :mod:`beartype.door` subpackage that is object-oriented. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ dunder ~ creation }.................... def test_door_typehint_new() -> None: ''' Test the :meth:`beartype.door.TypeHint.__new__` factory method. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.door import TypeHint from beartype.roar import BeartypeDoorNonpepException from beartype.typing import ( Any, Union, ) from pytest import raises # Intentionally import from "typing" rather than "beartype.typing" to # guarantee PEP 484-compliant type hints. from typing import List # ....................{ PASS }.................... # Assert that recreating a type hint against identical input yields the same # previously memoized type hint. assert TypeHint(List[Any]) is TypeHint(List[Any]) assert TypeHint(int) is TypeHint(int) #FIXME: Generalize "TypeHint" to ensure that these two type hints actually #do reduce to the same "TypeHint" object. Specifically, "List" and "list" #are both indeed semantically equivalent to "List[Any]". # Assert that recreating a type hint against non-identical but semantically # equivalent input does *NOT* reduce to the same previously memoized type # hint, sadly. assert TypeHint(List) is not TypeHint(list) # Assert that nested type hint invocations internally avoid nesting by # yielding the same previously memoized type hint. assert TypeHint(TypeHint(int)) is TypeHint(int) # Assert that public concrete subclasses of the "TypeHint" abstract base # class (ABC) pretend to reside in the top-level public "beartype.door" # subpackage rather than in a leaf private subpackage of that package. assert TypeHint(Union[int, str]).__class__.__module__ == 'beartype.door' # ....................{ FAIL }.................... # Assert this factory raises the expected exception when passed an object # that is *not* a PEP-compliant type hint. with raises(BeartypeDoorNonpepException): # Intentionally localized to assist in debugging test failures. typehint = TypeHint(b'Is there, that from the boundaries of the sky') def test_door_typehint_mapping(iter_hints_piths_meta) -> None: ''' Test that the :meth:`beartype.door.TypeHint.__new__` factory method successfully creates and returns an instance of a concrete subclass of the abstract :class:`beartype.door.TypeHint` superclass conditionally handling the kind of low-level type hint passed to that factory method. Parameters ---------- iter_hints_piths_meta : Callable[[], Iterable[beartype_test.a00_unit.data.hint.util.data_hintmetautil.HintPithMetadata]] Factory function creating and returning a generator iteratively yielding ``HintPithMetadata`` instances, each describing a sample type hint exercising an edge case in the :mod:`beartype` codebase paired with a related object either satisfying or violating that hint. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.door import TypeHint from beartype_test.a00_unit.data.hint.util.data_hintmetacls import ( HintPepMetadata) # ....................{ ASSERTS }.................... # For each predefined type hint and associated metadata... for hint_pith_meta in iter_hints_piths_meta(): # ....................{ METADATA }.................... # Metadata describing this type hint. hint_meta = hint_pith_meta.hint_meta # This type hint. hint = hint_meta.hint # If either... if ( # This hint is PEP-noncompliant *OR*... not isinstance(hint_meta, HintPepMetadata) or # This kind of type hint is currently unsupported by the # "beartype.door" submodule... hint_meta.typehint_cls is None ): # Silently ignore this hint and continue to the next. continue # Else, this kind of type hint is currently supported by the # "beartype.door" submodule *AND* this hint is PEP-compliant. # Instance of a concrete subclass of the abstract "TypeHint" superclass # conditionally handling this kind of type hint. wrapper = TypeHint(hint) # Assert that this instance is of the expected subclass. assert isinstance(wrapper, hint_meta.typehint_cls) # ....................{ PROPERTIES }.................... # Assert that the type hint wrapped by this instance is the same hint. wrapper_hint = wrapper.hint # print(f'wrapper_hint: {repr(wrapper_hint), id(wrapper_hint), type(wrapper_hint)}') # print(f'hint: {repr(hint), id(hint), type(hint)}') assert wrapper_hint is hint # ....................{ TESTS ~ dunders }.................... #FIXME: Insufficient. Generalize to test *ALL* possible kinds of type hints. def test_door_typehint_repr() -> None: ''' Test the :meth:`beartype.door.TypeHint.__repr__` dunder method. ''' # Defer test-specific imports. from beartype.door import TypeHint from beartype.typing import Callable annotation = Callable[[], list] hint = TypeHint(annotation) assert repr(annotation) in repr(hint) # ....................{ TESTS ~ dunders : compare }.................... def test_door_typehint_equals( door_cases_equality: 'Iterable[Tuple[object, object, bool]]') -> None: ''' Test the :meth:`beartype.door.TypeHint.__equals__` dunder method. Parameters ---------- door_cases_equality : Iterable[Tuple[object, object, bool]] Iterable of one or more 3-tuples ``(hint_a, hint_b, is_equal)``, declared by the :func:`hint_subhint_cases` fixture. ''' # Defer test-specific imports. from beartype.door import TypeHint # Intentionally import from "typing" rather than "beartype.typing" to # guarantee PEP 484-compliant type hints. from typing import ( Generator, Union, ) # Arbitrary hint guaranteed to be unequal to every other hint listed in the # "hint_equality_cases" iterable. typehint_unequal = TypeHint(Generator[Union[list, str], str, None]) # Arbitrary non-hint object. Note that strings are valid type hints! nonhint = b'Of insects, beasts, and birds, becomes its spoil;' # For each equality relation to be tested... for hint_a, hint_b, IS_EQUAL in door_cases_equality: # "TypeHint" instances encapsulating these hints. typehint_a = TypeHint(hint_a) typehint_b = TypeHint(hint_b) # Assert this tester returns the expected boolean for these hints. is_equal = (typehint_a == typehint_b) assert is_equal is IS_EQUAL # Assert this tester returns the expected boolean for each such hint and # another arbitrary hint guaranteed to be unequal to these hints. In # other words, perform a smoke test. assert typehint_a != typehint_unequal assert typehint_b != typehint_unequal # Assert this tester returns the expected boolean for each such hint and # an arbitrary non-hint. In other words, perform another smoke test. assert typehint_a != nonhint assert typehint_b != nonhint def test_door_typehint_compare_fail() -> None: ''' Test unsuccessful usage the rich comparison dunder methods defined by the :class:`beartype.door.TypeHint` class. ''' # Defer test-specific imports. from beartype.door import TypeHint from beartype.typing import ( Any, Callable, Sequence, ) from pytest import raises a = TypeHint(Callable[[], list]) b = TypeHint(Callable[..., Sequence[Any]]) assert a <= b assert a < b assert a != b assert not a > b assert not a >= b with raises(TypeError, match='not supported between'): a <= 1 with raises(TypeError, match='not supported between'): a < 1 with raises(TypeError, match='not supported between'): a >= 1 with raises(TypeError, match='not supported between'): a > 1 # ....................{ TESTS ~ dunders : iterable }.................... #FIXME: Insufficient. Generalize to test *ALL* possible kinds of type hints. def test_door_typehint_contains() -> None: ''' Test the :meth:`beartype.door.TypeHint.__contains__` dunder method. ''' # Defer test-specific imports. from beartype.door import TypeHint from beartype.typing import Union # Sample type hint wrappers. wrapper_int = TypeHint(int) wrapper_str = TypeHint(str) wrapper_int_str = TypeHint(Union[int, str]) # Assert that various parent type hints contain the expected child type # hints. assert wrapper_int in wrapper_int_str assert wrapper_str in wrapper_int_str # Assert that various parent type hints do *NOT* contain the expected child # type hints. assert wrapper_int not in wrapper_int assert wrapper_str not in wrapper_int assert TypeHint(bool) not in wrapper_int_str #FIXME: Insufficient. Generalize to test *ALL* possible kinds of type hints. def test_door_typehint_iter() -> None: ''' Test the :meth:`beartype.door.TypeHint.__iter__` dunder method. ''' # Defer test-specific imports. from beartype.door import TypeHint from beartype.typing import Union # Note that unions are *NOT* order-preserving in the general case. Although # unions are order-preserving in isolated test cases, self-caching employed # behind-the-scenes by unions prevent order from being reliably tested. assert set(TypeHint(Union[int, str])) == {TypeHint(int), TypeHint(str)} assert not list(TypeHint(int)) #FIXME: Insufficient. Generalize to test *ALL* possible kinds of type hints. def test_door_typehint_getitem() -> None: ''' Test the :meth:`beartype.door.TypeHint.__getitem__` dunder method. ''' # Defer test-specific imports. from beartype.door import TypeHint from beartype.typing import Union # Arbitrary wrapper wrapping a type hint subscripted by multiple children. typehint = TypeHint(Union[int, str, None]) # Assert that subscripting this wrapper by a positive index yields a wrapper # wrapping the expected child type hint at that index. assert typehint[0] == TypeHint(int) # Assert that subscripting this wrapper by a negative index yields a wrapper # wrapping the expected child type hint at that index. assert typehint[-1] == TypeHint(None) # Assert that subscripting this wrapper by a slice yields a tuple of zero or # more wrappers wrapping the expected child type hints at those indices. assert typehint[0:2] == (TypeHint(int), TypeHint(str)) # ....................{ TESTS ~ dunders : iterable : sized }.................... #FIXME: Insufficient. Generalize to test *ALL* possible kinds of type hints. def test_door_typehint_bool() -> None: ''' Test the :meth:`beartype.door.TypeHint.__len__` dunder method. ''' # Defer test-specific imports. from beartype.door import TypeHint from beartype.typing import ( Tuple, Union, ) # Assert that various type hints evaluate to the expected booleans. assert bool(TypeHint(Tuple[()])) is False assert bool(TypeHint(Union[int, str])) is True #FIXME: Insufficient. Generalize to test *ALL* possible kinds of type hints. def test_door_typehint_len(): ''' Test the :meth:`beartype.door.TypeHint.__len__` dunder method. ''' # Defer test-specific imports. from beartype.door import TypeHint from beartype.typing import ( Tuple, Union, ) # Assert that various type hints evaluate to the expected lengths. assert len(TypeHint(Tuple[()])) == 0 assert len(TypeHint(Union[int, str])) == 2 # ....................{ TESTS ~ properties }.................... #FIXME: Implement this up, please. We'll want to pay particular attention to #edge cases in those "TypeHint" implementations overriding the _make_args() #superclass method. In all likelihood, this will warrant yet another fixture. # def test_door_typehint_args(hints_pep_meta, hints_ignorable) -> None: # ''' # Test the read-only :meth:`beartype.door.TypeHint.args` property. # # Parameters # ---------- # hints_pep_meta : tuple[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] # Tuple of type hint metadata describing sample type hints exercising edge # cases in the :mod:`beartype` codebase. # hints_ignorable : frozenset # Frozen set of ignorable PEP-agnostic type hints. # ''' # # # Defer test-specific imports. # from beartype.door import TypeHint # from beartype.roar import BeartypeDoorException, BeartypeDoorNonpepException # from contextlib import suppress # # # Assert this method: # # * Accepts unignorable PEP-compliant type hints. # # * Rejects ignorable PEP-compliant type hints. # for hint_pep_meta in hints_pep_meta: # #FIXME: Remove this suppression *AFTER* improving "TypeHint" to support # #all currently unsupported type hints. Most of these will be # #"BeartypeDoorNonpepException", but there are some covariant type hints # #(e.g. numpy.dtype[+ScalarType]) that will raise a "not invariant" # #exception in the "TypeVarTypeHint" subclass. # with suppress(BeartypeDoorException): # assert TypeHint(hint_pep_meta.hint).is_ignorable is ( # hint_pep_meta.is_ignorable) def test_door_typehint_is_ignorable(hints_pep_meta, hints_ignorable) -> None: ''' Test the :meth:`beartype.door.TypeHint.is_ignorable` property. Parameters ---------- hints_pep_meta : tuple[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] Tuple of type hint metadata describing sample type hints exercising edge cases in the :mod:`beartype` codebase. hints_ignorable : frozenset Frozen set of ignorable PEP-agnostic type hints. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.door import TypeHint from beartype.roar import BeartypeDoorException, BeartypeDoorNonpepException from beartype.typing import TypeVar from beartype._util.hint.utilhinttest import is_hint_ignorable from beartype._util.hint.pep.proposal.pep484.utilpep484typevar import ( get_hint_pep484_typevar_bound_or_none) from contextlib import suppress # ....................{ PASS }.................... # Assert this property accepts ignorable type hints. for hint_ignorable in hints_ignorable: #FIXME: Remove this suppression *AFTER* improving "TypeHint" to support #all currently unsupported type hints. with suppress(BeartypeDoorNonpepException): assert TypeHint(hint_ignorable).is_ignorable is True # Assert this property: # * Accepts unignorable PEP-compliant type hints. # * Rejects ignorable PEP-compliant type hints. for hint_pep_meta in hints_pep_meta: # Hint to be tested. hint = hint_pep_meta.hint # True only if the @beartype decorator currently ignores this hint. hint_is_ignorable = hint_pep_meta.is_ignorable # If this hint is a type variable... # # Note that the @beartype decorator and the "beartype.door" API # currently disagree as to whether type variables are ignorable. From: # the perspective of: # * The low-level @beartype decorator, they mostly are. # * The high-level "beartype.door" API, they mostly are *NOT*. # # Since the "hint_pep_meta.is_ignorable" variable reflects the # perspective of the @beartype decorator rather than the "beartype.door" # API, further logic is needed to decide whether this type variable is # actually ignorable from the "beartype.door" perspective. if isinstance(hint, TypeVar): # Type hint synthesized from all bounded constraints parametrizing # this type variable if any *OR* "None" otherwise. hint_typevar_bound = get_hint_pep484_typevar_bound_or_none(hint) # This type hint is ignorable only if either... hint_is_ignorable = ( # This type hint is unconstrained *OR*... hint_typevar_bound is None or # This type hint is constrained by one or more ignorable # constraints. is_hint_ignorable(hint_typevar_bound) ) # Else, this hint is *NOT* a type variable. #FIXME: Remove this suppression *AFTER* improving "TypeHint" to support #all currently unsupported type hints. Most of these will be #"BeartypeDoorNonpepException", but there are some covariant type hints #(e.g. numpy.dtype[+ScalarType]) that will raise a "not invariant" #exception in the "TypeVarTypeHint" subclass. with suppress(BeartypeDoorException): assert TypeHint(hint).is_ignorable is hint_is_ignorable # assert TypeHint(hint_pep_meta.hint).is_ignorable is ( # hint_pep_meta.is_ignorable) # ....................{ TESTS ~ testers }.................... def test_door_typehint_is_subhint_fail() -> None: ''' Test unsuccessful usage of the :meth:`beartype.door.TypeHint.is_subhint` tester method. ''' # Defer test-specific imports. from beartype.door import TypeHint from beartype.roar import BeartypeDoorException from beartype.typing import Callable from pytest import raises hint = TypeHint(Callable[[], list]) with raises(BeartypeDoorException, match='not type hint wrapper'): hint.is_subhint(int) def test_door_typehint_is_args_ignorable(): ''' Test the private :attr:`beartype.door.TypeHint._is_args_ignorable` boolean instance variable. ''' from beartype.door import TypeHint from beartype.typing import ( Any, Callable, Tuple, ) assert TypeHint(Callable)._is_args_ignorable assert TypeHint(Callable[..., Any])._is_args_ignorable assert TypeHint(Tuple)._is_args_ignorable assert TypeHint(Tuple[Any, ...])._is_args_ignorable assert TypeHint(int)._is_args_ignorable #FIXME: Implement us up at some point, yo. # def test_door_callable_param_spec(): # # TODO # with pytest.raises(NotImplementedError): # TypeHint(t.Callable[t.ParamSpec("P"), t.TypeVar("T")]) beartype-0.18.5/beartype_test/a00_unit/a40_api/door/a90_func/000077500000000000000000000000001461113517100234755ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a40_api/door/a90_func/__init__.py000066400000000000000000000000001461113517100255740ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a40_api/door/a90_func/test_door_func.py000066400000000000000000000030461461113517100270670ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype Decidedly Object-Oriented Runtime-checking (DOOR) API procedural unit tests.** This submodule unit tests the subset of the public API of the public :mod:`beartype.door` subpackage that is procedural. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ testers }.................... def test_door_is_subhint( door_cases_subhint: 'Iterable[Tuple[object, object, bool]]') -> None: ''' Test the :func:`beartype.door.is_subhint` tester. Parameters ---------- door_cases_subhint : Iterable[Tuple[object, object, bool]] Iterable of one or more 3-tuples ``(subhint, superhint, is_subhint)``, declared by the :func:`hint_subhint_cases` fixture. ''' # Defer test-specific imports. from beartype.door import is_subhint # For each subhint relation to be tested... for subhint, superhint, IS_SUBHINT in door_cases_subhint: # Assert this tester returns the expected boolean for these hints. assert is_subhint(subhint, superhint) is IS_SUBHINT beartype-0.18.5/beartype_test/a00_unit/a40_api/door/conftest.py000066400000000000000000000013441461113517100242720ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Test configuration** (i.e., :mod:`pytest`-specific early-time configuration guaranteed to be implicitly imported by :mod:`pytest` into *all* sibling and child submodules of the test subpackage containing this :mod:`pytest` plugin). ''' # ....................{ IMPORTS }.................... # Import all subpackage-specific fixtures implicitly required by tests defined # by submodules of this subpackage. from beartype_test.a00_unit.a40_api.door._doorfixture import ( door_cases_equality, door_cases_subhint, ) beartype-0.18.5/beartype_test/a00_unit/a40_api/plug/000077500000000000000000000000001461113517100220755ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a40_api/plug/__init__.py000066400000000000000000000000001461113517100241740ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a40_api/plug/test_plugmixin.py000066400000000000000000000041401461113517100255210ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype plugin mixin unit tests.** This submodule unit tests protocols defined by the private :func:`beartype.plug._plughintable` submodule, most of which are publicly exported to end users and thus critically important. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_beartypehintable() -> None: ''' Test the :class:`beartype.plug._plughintable.BeartypeHintable` mixin. ''' # .....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.plug import BeartypeHintable from pytest import raises # .....................{ CLASSES }.................... class NorWhenTheFlakesBurn(BeartypeHintable): ''' Arbitrary class explicitly satisfying the :class:`BeartypeHintable` protocol. ''' @classmethod def __beartype_hint__(cls) -> object: ''' Arbitrary beartype type hint transform reducing to an arbitrary PEP-compliant type hint. ''' return str # .....................{ ASSERTS }.................... # Assert that calling a concrete __beartype_hint__() class method of a # "BeartypeHintable" subclass returns the expected type hint. assert NorWhenTheFlakesBurn.__beartype_hint__() is str # Assert that calling the abstract __beartype_hint__() class method raises # the expected exception. with raises(NotImplementedError): BeartypeHintable.__beartype_hint__() beartype-0.18.5/beartype_test/a00_unit/a40_api/test_api_cave.py000066400000000000000000000671421461113517100243200ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype cave API unit tests.** This submodule unit tests the public API of the :mod:`beartype.cave` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! import argparse, functools, re, sys, weakref from beartype_test._util.mark.pytskip import skip_if_pypy, skip_unless_package from collections import deque from collections.abc import Iterable from decimal import Decimal from enum import Enum from fractions import Fraction # ....................{ TODO }.................... #FIXME: Unit test the following types, which remain untested for the initial #0.1.0 release due to non-trivialities with asynchronous testing: #* "AsyncGeneratorCType". #* "AsyncCoroutineCType". #* "AsyncCTypes". #* "CallableCTypes". # ....................{ CLASSES }.................... #FIXME: Replace most of the following with trivial access of attributes already #defined by the existing "beartype_test.a00_unit.data.data_type" submodule. # Test class defining all possible class-specific callables, including... class _WeHaveFedOurSeaForAThousandYears(object): # Instance method. def and_she_calls_us_still_unfed(self): pass # Class method. @classmethod def though_theres_never_a_wave_of_all_her_waves(cls): pass # Static method. @staticmethod def but_marks_our_english_dead(): pass # Property getter method. @property def we_have_strawed_our_best_to_the_weeds_unrest(self): pass # Property setter method. @we_have_strawed_our_best_to_the_weeds_unrest.setter def we_have_strawed_our_best_to_the_weeds_unrest( self, to_the_shark_and_the_sheering_gull): pass # Test enumeration class. class _AsTheDeerBreaksAsTheSteerBreaksFromTheHerdWhereTheyGraze(Enum): IN_THE_FAITH_OF_LITTLE_CHILDREN_WE_WENT_ON_OUR_WAYS = 1 THEN_THE_WOOD_FAILED = 2 THEN_THE_FOOD_FAILED = 3 THEN_THE_LAST_WATER_DRIED = 4 IN_THE_FAITH_OF_LITTLE_CHILDREN_WE_LAY_DOWN_AND_DIED = 5 # ....................{ FUNCTIONS }.................... # Test vanilla function. def _we_were_dreamers_dreaming_greatly_in_the_man_stifled_town(): pass # Test generator function. def _we_yearned_beyond_the_sky_line_where_the_strange_roads_go_down(): yield # ....................{ GLOBALS }.................... # Test user-defined class instance. _LORD_GOD_WE_HA_PAID_IN_FULL = _WeHaveFedOurSeaForAThousandYears() # Test user-defined Unicode string. _THE_PHANTOM_RICKSHAW = ( 'More men are killed by overwork ' 'than the importance of the world justifies.' ) # ....................{ GLOBALS ~ generator }.................... # Test generator function return type. _CAME_THE_WHISPER_CAME_THE_VISION_CAME_THE_POWER_WITH_THE_NEED = ( _we_yearned_beyond_the_sky_line_where_the_strange_roads_go_down()) # Test generator comprehension. _TILL_THE_SOUL_THAT_IS_NOT_MANS_SOUL_WAS_LENT_US_TO_LEAD = ( l33t for l33t in range(0x1CEB00DA, 0xC00010FF)) # ....................{ GLOBALS ~ container }.................... # Test mutable sequence. _THE_SONG_OF_THE_DEAD = [ 'Hear now the Song of the Dead -- in the North by the torn berg-edges --', 'They that look still to the Pole, asleep by their hide-stripped sledges.', 'Song of the Dead in the South -- in the sun by their skeleton horses,', 'Where the warrigal whimpers and bays through the dust', 'of the sear river-courses.', ] # Test mutable mapping. _EPITAPHS_OF_THE_WAR = { 'COMMON FORM': ( 'If any question why we died,', 'Tell them, because our fathers lied.', ), 'A DEAD STATESMAN': ( 'I could not dig: I dared not rob:', 'Therefore I lied to please the mob.', 'Now all my lies are proved untrue', 'And I must face the men I slew.', 'What tale shall serve me here among', 'Mine angry and defrauded young?', ), } # Test double-ended queue. _RECESSIONAL = deque(( 'For heathen heart that puts her trust', ' in reeking tube and iron shard--', 'All valiant dust that builds on dust,', ' and guarding, calls not Thee to guard,', 'For frantic boast and foolish word--', 'Thy mercy on Thy People, Lord!', )) # ....................{ GLOBALS ~ regex }.................... # Test regular expression compiled object. _IN_THE_SAND_DRIFT_ON_THE_VELDT_SIDE_IN_THE_FERN_SCRUB_WE_LAY = re.compile( r'\b[Ff]ollow after\b') # Test regular expression match object. _THAT_OUR_SONS_MIGHT_FOLLOW_AFTER_BY_THE_BONES_ON_THE_WAY = re.match( _IN_THE_SAND_DRIFT_ON_THE_VELDT_SIDE_IN_THE_FERN_SCRUB_WE_LAY, 'Follow after -- follow after! We have watered the root,') # ....................{ ASSERTERS }.................... def _assert_types_objects(types: Iterable, *objects: object) -> None: ''' Assert all passed objects to be instances of all types contained in the passed iterable of such types. Parameters ---------- types : Iterable[type] Iterable of types to validate these objects to be instances of. objects : tuple Tuple of all objects to be validated as instances of these types. ''' # Assert that this iterable of types actually is. assert isinstance(types, Iterable) # For each type in this iterable, assert these objects to all be instances # of this type. for cls in types: _assert_type_objects(cls, *objects) def _assert_type_objects(cls: type, *objects: object) -> None: ''' Assert all passed objects to be instances of the passed type. Parameters ---------- cls : type Type to validate these objects to be instances of. objects : tuple Tuple of all objects to be validated as instances of this type. ''' # Assert that this type actually is. assert isinstance(cls, type) # Assert these objects to all be instances of this type. for obj in objects: assert isinstance(obj, cls) # ....................{ ASSERTERS ~ tuple }.................... def _assert_tuples_objects(tuples: Iterable, *objects: object) -> None: ''' Assert all passed objects to be instances of one or more types contained in each tuple in the passed iterable of such tuples. Parameters ---------- tuples : Iterable[tuples] Iterable of tuples of types to validate these objects to be instances of. objects : tuple Tuple of all objects to be validated as instances of these types. ''' # Assert that this iterable of tuples actually is. assert isinstance(tuples, Iterable) # For each tuple in this iterable, assert these objects to all be instances # of one or more types contained in this tuple. for types in tuples: _assert_tuple_objects(types, *objects) def _assert_tuple_objects(types: tuple, *objects: object) -> None: ''' Assert all passed objects to be instances of one or more types contained in the passed tuple. Parameters ---------- types : tuple Tuple of types to validate these objects to be instances of. objects : tuple Tuple of all objects to be validated as instances of these types. ''' # Assert that this tuple actually is. assert isinstance(types, tuple) # Assert all items of this tuple to be types. for cls in types: assert isinstance(cls, type) # Assert these objects to all be instances of this type. for obj in objects: assert isinstance(obj, types) # ....................{ TESTS ~ type }.................... def test_api_cave_type_core() -> None: ''' Test all **core simple types** (i.e., types unconditionally published for *all* supported Python versions regardless of the importability of optional third-party dependencies) published by the :mod:`beartype.cave` submodule. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. For each simple type published by the # beartype cave, assert below that: # * This type is a simple type. # * An object expected to be of this type is of this type. from beartype import cave from beartype_test.a00_unit.data.data_type import builtin_partial # ....................{ ASSERTS }.................... # Test "UnavailableType". By definition, no objects of this type exist; # ergo, we only test that this type is actually a type. _assert_type_objects(cave.UnavailableType) # Test "AnyType". _assert_type_objects(cave.AnyType, object()) # Test "NoneType". _assert_type_objects(cave.NoneType, None) # Test "ClassType". _assert_type_objects(cave.ClassType, _WeHaveFedOurSeaForAThousandYears) # Test "CollectionType". _assert_type_objects(cave.CollectionType, _THE_SONG_OF_THE_DEAD) # Test "FileType". with open(__file__, 'r') as ( by_the_bones_about_the_wayside_ye_shall_come_to_your_own): _assert_type_objects( cave.FileType, by_the_bones_about_the_wayside_ye_shall_come_to_your_own) # Test "ModuleType". _assert_type_objects(cave.ModuleType, sys.modules[__name__]) # Test "CallableFunctoolsPartialType". _assert_type_objects(cave.CallableFunctoolsPartialType, builtin_partial) # Test "FunctionType". Since many types not commonly thought of as # functions are ambiguously implemented as functions, explicitly test... _assert_type_objects( cave.FunctionType, # Standard function. _we_were_dreamers_dreaming_greatly_in_the_man_stifled_town, # Lambda function. lambda: None, # Unbound instance method. _WeHaveFedOurSeaForAThousandYears.and_she_calls_us_still_unfed, # Static method accessed on a class. _WeHaveFedOurSeaForAThousandYears.but_marks_our_english_dead, # Static method accessed on an instance. _LORD_GOD_WE_HA_PAID_IN_FULL.but_marks_our_english_dead, ) # Test "FunctionOrMethodCType" against an unbound C-based function. # # Note that testing this type against a bound C-based instance non-dunder # method is PyPy-incompatible and *MUST* thus be deferred to the # test_api_cave_types_core_nonpypy() unit test. _assert_type_objects(cave.FunctionOrMethodCType, id) # Test "MethodBoundInstanceOrClassType" against... _assert_type_objects( cave.MethodBoundInstanceOrClassType, # Bound instance method. _LORD_GOD_WE_HA_PAID_IN_FULL.and_she_calls_us_still_unfed, # Bound class method accessed on a class. _WeHaveFedOurSeaForAThousandYears.though_theres_never_a_wave_of_all_her_waves, # Bound class method accessed on an instance. _LORD_GOD_WE_HA_PAID_IN_FULL.though_theres_never_a_wave_of_all_her_waves, ) # Test "MethodBoundInstanceDunderCType". _assert_type_objects(cave.MethodBoundInstanceDunderCType, ''.__add__) # Test "MethodUnboundClassCType". _assert_type_objects( cave.MethodUnboundClassCType, dict.__dict__['fromkeys']) # Test "MethodUnboundInstanceDunderCType". _assert_type_objects(cave.MethodUnboundInstanceDunderCType, str.__add__) # Test "MethodUnboundInstanceNondunderCType". _assert_type_objects(cave.MethodUnboundInstanceNondunderCType, str.upper) # Test "MethodDecoratorClassType". Note that instances of this type are # *ONLY* accessible with the low-level "object.__dict__" dictionary. _assert_type_objects( cave.MethodDecoratorClassType, _WeHaveFedOurSeaForAThousandYears.__dict__[ 'though_theres_never_a_wave_of_all_her_waves']) # Test "MethodDecoratorPropertyType". _assert_type_objects( cave.MethodDecoratorPropertyType, _WeHaveFedOurSeaForAThousandYears.we_have_strawed_our_best_to_the_weeds_unrest) # Test "MethodDecoratorStaticType". Note that instances of this type are # *ONLY* accessible with the low-level "object.__dict__" dictionary. _assert_type_objects( cave.MethodDecoratorStaticType, _WeHaveFedOurSeaForAThousandYears.__dict__[ 'but_marks_our_english_dead']) #FIXME: Also test a class implementing "collections.abc.Generator" by #subclassing "_WeHaveFedOurSeaForAThousandYears" from this class and #implementing the requisite abstract methods. # Test "GeneratorType" against... _assert_type_objects( cave.GeneratorType, # Generator function return object. _CAME_THE_WHISPER_CAME_THE_VISION_CAME_THE_POWER_WITH_THE_NEED, # Generator comprehension. _TILL_THE_SOUL_THAT_IS_NOT_MANS_SOUL_WAS_LENT_US_TO_LEAD, ) # Test "GeneratorCType". _assert_type_objects( cave.GeneratorCType, _CAME_THE_WHISPER_CAME_THE_VISION_CAME_THE_POWER_WITH_THE_NEED) # Test "WeakRefCType" against... _assert_type_objects( cave.WeakRefCType, # Weak non-method reference. weakref.ref(_we_were_dreamers_dreaming_greatly_in_the_man_stifled_town), # Weak method reference. weakref.WeakMethod( _LORD_GOD_WE_HA_PAID_IN_FULL.and_she_calls_us_still_unfed), ) # Test "ContainerType", "SizedType", "IterableType", and "SequenceType" # against... _assert_types_objects( { cave.ContainerType, cave.SizedType, cave.IterableType, cave.SequenceType, }, # Immutable sequence. _EPITAPHS_OF_THE_WAR['COMMON FORM'], # Mutable sequence. _THE_SONG_OF_THE_DEAD, # Double-ended queue. _RECESSIONAL, ) # Test "SequenceMutableType" against... _assert_type_objects( cave.SequenceMutableType, # Mutable sequence. _THE_SONG_OF_THE_DEAD, # Double-ended queue. _RECESSIONAL, ) # Test "ContainerType", "MappingType", and "MappingMutableType" against... _assert_types_objects( { cave.ContainerType, cave.MappingType, cave.MappingMutableType, }, # Mutable mapping. _EPITAPHS_OF_THE_WAR, ) # Test "IteratorType". _assert_type_objects(cave.IteratorType, iter(_THE_SONG_OF_THE_DEAD)) # Test "QueueType". _assert_type_objects(cave.QueueType, _RECESSIONAL) # Test "SetType" against... _assert_type_objects( cave.SetType, # Immutable set. frozenset(_THE_SONG_OF_THE_DEAD), # Mutable set. set(_THE_SONG_OF_THE_DEAD), ) # Test "HashableType". _assert_type_objects(cave.HashableType, _THE_SONG_OF_THE_DEAD[0]) # Test "EnumType". _assert_type_objects( cave.EnumType, _AsTheDeerBreaksAsTheSteerBreaksFromTheHerdWhereTheyGraze) # Test "EnumMemberType". _assert_type_objects( cave.EnumMemberType, _AsTheDeerBreaksAsTheSteerBreaksFromTheHerdWhereTheyGraze.THEN_THE_WOOD_FAILED) # Test "BoolType." _assert_type_objects(cave.BoolType, False, True) # Test "StrType". _assert_type_objects(cave.StrType, _THE_PHANTOM_RICKSHAW) # Test "NumberType", "IntOrFloatType", and ""IntType" # against... _assert_types_objects( { cave.NumberType, cave.IntOrFloatType, cave.IntType, }, # Integer. 0xCAFED00D, ) # Test "NumberType" and "IntOrFloatType" against... _assert_types_objects( { cave.NumberType, cave.IntOrFloatType, }, # Float. 1.1851851851851851, # Stdlib fraction. Fraction(32, 27), ) #FIXME: Test builtin complex numbers here as well. # Test "NumberType" against... _assert_type_objects( cave.NumberType, # Stdlib decimal, which oddly only satisfies "numbers.Number". *sigh* Decimal('0.1428571428571428571428571429'), ) # Test "ArgParserType". arg_parser = argparse.ArgumentParser() _assert_type_objects(cave.ArgParserType, arg_parser) # Test "ArgSubparsersType". _assert_type_objects(cave.ArgSubparsersType, arg_parser.add_subparsers()) # Test "RegexCompiledType". _assert_type_objects( cave.RegexCompiledType, _IN_THE_SAND_DRIFT_ON_THE_VELDT_SIDE_IN_THE_FERN_SCRUB_WE_LAY) # Test "RegexMatchType". _assert_type_objects( cave.RegexMatchType, _THAT_OUR_SONS_MIGHT_FOLLOW_AFTER_BY_THE_BONES_ON_THE_WAY) # ....................{ TESTS ~ type : skip }.................... @skip_if_pypy() def test_api_cave_type_core_nonpypy() -> None: ''' Test all core simple types published by the :mod:`beartype.cave` submodule requiring the active Python interpreter to *not* be PyPy where this is the case *or* reduce to a noop otherwise. ''' # Import this submodule. from beartype import cave # Test "FunctionOrMethodCType" against a bound C-based instance non-dunder # method. Under PyPy, this method is a regular pure-Python bound method. _assert_type_objects( cave.FunctionOrMethodCType, _IN_THE_SAND_DRIFT_ON_THE_VELDT_SIDE_IN_THE_FERN_SCRUB_WE_LAY.sub) # ....................{ TESTS ~ tuple }.................... def test_api_cave_tuple_core() -> None: ''' Test all **core tuple types** (i.e., tuples of types unconditionally published for *all* supported Python versions regardless of the importability of optional third-party dependencies) published by the :mod:`beartype.cave` submodule. ''' # Defer test-specific imports. For each tuple type published by the beartype # cave, assert below that: # * This tuple contains only simple types. # * One or more objects expected to be of one or more types in this tuple # are of these types. from beartype import cave # Test "UnavailableTypes". By definition, no objects of these types exist; # ergo, we only test that this tuple is simply an empty tuple. _assert_tuple_objects(cave.UnavailableTypes) assert cave.UnavailableTypes == () # Test "ModuleOrStrTypes" against... _assert_tuple_objects( cave.ModuleOrStrTypes, # Module object. sys.modules[__name__], # Arbitrary string. 'beartype', ) # Test "TestableTypes" against... _assert_tuple_objects( cave.TestableTypes, # User-defined class. _WeHaveFedOurSeaForAThousandYears, # Arbitrary tuple. _EPITAPHS_OF_THE_WAR['A DEAD STATESMAN'], ) # Tuple of all tuples of types matching at least callable types. all_callable_types = ( cave.CallableTypes, cave.CallableOrStrTypes, cave.DecoratorTypes, ) # Test "FunctionTypes" and all derived types against... _assert_tuples_objects( (cave.FunctionTypes,) + all_callable_types, # Pure-Python function. Since the test_api_cave_types_core() unit test # already exhaustively tests *ALL* possible pure-Python function types, # testing only one such type here suffices. _we_were_dreamers_dreaming_greatly_in_the_man_stifled_town, # C-based builtin function. id, ) # Test "MethodBoundTypes" and all derived types against... _assert_tuples_objects( (cave.MethodBoundTypes, cave.MethodTypes,) + all_callable_types, # Bound pure-Python instance method. _LORD_GOD_WE_HA_PAID_IN_FULL.and_she_calls_us_still_unfed, # Bound C-based instance dunder method. ''.__add__, ) # Test "MethodUnboundTypes" and all derived types against... _assert_tuples_objects( (cave.MethodUnboundTypes, cave.MethodTypes,) + all_callable_types, # Unbound class method. dict.__dict__['fromkeys'], # Unbound C-based instance dunder method. str.__add__, # Unbound C-based instance non-dunder method. str.upper, ) # Test "MethodDecoratorBuiltinTypes" against... _assert_tuple_objects( cave.MethodDecoratorBuiltinTypes, # Unbound class method decorator object. _WeHaveFedOurSeaForAThousandYears.__dict__[ 'though_theres_never_a_wave_of_all_her_waves'], # Unbound property method decorator object. _WeHaveFedOurSeaForAThousandYears.we_have_strawed_our_best_to_the_weeds_unrest, # Unbound static method decorator object. _WeHaveFedOurSeaForAThousandYears.__dict__[ 'but_marks_our_english_dead'], ) # Test "MethodTypes" against only the following, as prior logic already # tested this tuple against all other types of callables... _assert_tuple_objects( cave.MethodTypes, # Bound C-based instance dunder method. _IN_THE_SAND_DRIFT_ON_THE_VELDT_SIDE_IN_THE_FERN_SCRUB_WE_LAY.sub, ) # Test "CallableOrStrTypes" against only a string, as prior logic already # tested this tuple against all types of callables. _assert_tuple_objects(cave.CallableOrStrTypes, 'beartype.beartype') # Test "DecoratorTypes" against only a class, as prior logic already # tested this tuple against all types of callables. _assert_tuple_objects( cave.DecoratorTypes, _WeHaveFedOurSeaForAThousandYears) # Test "WeakRefProxyCTypes" against... _assert_tuple_objects( cave.WeakRefProxyCTypes, # Callable weak reference proxy. weakref.proxy( _we_were_dreamers_dreaming_greatly_in_the_man_stifled_town), # Uncallable weak reference proxy. weakref.proxy(_LORD_GOD_WE_HA_PAID_IN_FULL), ) # Test "BoolOrNumberTypes" against... _assert_tuple_objects( cave.BoolOrNumberTypes, # Boolean. True, # Integer. 0xCAFED00D, # Float. 1.1851851851851851, ) # Test "RegexTypes" against... _assert_tuple_objects( cave.RegexTypes, # Compiled regular expression. _IN_THE_SAND_DRIFT_ON_THE_VELDT_SIDE_IN_THE_FERN_SCRUB_WE_LAY, # Builtin Unicode string. _THE_PHANTOM_RICKSHAW, ) def test_api_cave_tuple_nonetypeor() -> None: ''' Test the **core :class:`beartype.cave.NoneType` tuple factory** (i.e., the :class:`beartype.cave.NoneTypeOr` mutable mapping) published by the :mod:`beartype.cave` submodule, which is sufficiently distinct from all other attributes defined by that submodule to warrant distinct unit tests. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.cave import ( AnyType, CallableTypes, MappingMutableType, NoneType, NoneTypeOr, ) # from beartype.roar import ( # BeartypeCaveNoneTypeOrKeyException, # BeartypeCaveNoneTypeOrMutabilityException, # ) # ....................{ PASS }.................... # Assert this factory to be a mutable mapping. assert isinstance(NoneTypeOr, MappingMutableType) # Assert that indexing this factory with "NoneType" creates, caches, and # returns a tuple containing only "NoneType". NoneTypes = NoneTypeOr[NoneType] assert NoneTypes == (NoneType,) # Assert that indexing this factory with "NoneType" again returns the same # tuple implicitly created and cached by the prior indexation. assert NoneTypeOr[NoneType] is NoneTypes # Assert that indexing this factory with any type *EXCEPT* "NoneType" # creates, caches, and returns a new tuple containing that type followed by # "NoneType". AnyOrNoneTypes = (AnyType, NoneType,) assert NoneTypeOr[AnyType] == AnyOrNoneTypes # Assert that indexing this factory with a tuple already containing # "NoneType" returns the same tuple unmodified. assert NoneTypeOr[AnyOrNoneTypes] == AnyOrNoneTypes # Assert that indexing this factory with a tuple *NOT* already containing # "NoneType" creates, caches, and returns a new tuple containing the types # contained in the original tuple followed by "NoneType". assert NoneTypeOr[CallableTypes] == CallableTypes + (NoneType,) # ....................{ FAIL }.................... #FIXME: Restore this edge case. For simplicity, we currently ignore this. # # Avoid asserting this factory to be indexable or *NOT* indexable by # # various types, as the existing test_utilhint_die_unless_hint_nonpep() # # unit test already exercises these edge cases. # # # # Assert this factory to *NOT* be explicitly settable. # with raises(BeartypeCaveNoneTypeOrMutabilityException): # NoneTypeOr['If you can meet with'] = 'Triumph and Disaster' # ....................{ TESTS ~ lib }.................... @skip_unless_package('numpy') def test_api_cave_lib_numpy() -> None: ''' Test all core simple types published by the :mod:`beartype.cave` submodule against various NumPy objects if the third-party :mod:`numpy` package is importable *or* reduce to a noop otherwise. ''' # Defer test-specific imports. import numpy from beartype import cave # NumPy boolean array prepopulated with NumPy boolean false values. array_bool = numpy.ones((6,)) # NumPy floating-point array prepopulated with an infamous rational series. array_float = numpy.asarray((1, 3/2, 7/5, 17/12, 41/29, 99/70)) # NumPy integer array prepopulated with the GIF header in ASCII code. array_int = numpy.asarray((47, 49, 46, 38, 39, 61)) # NumPy string array prepopulated with Rudyard Kipling, because Truth™. array_str = numpy.asarray(( 'I keep six honest serving-men:', '(They taught me all I knew)', 'Their names are What and Where and When', 'And How and Why and Who.', )) # Test all container protocols satisfied by NumPy arrays against these # specific arrays. _assert_types_objects( { cave.ContainerType, cave.SizedType, cave.IterableType, }, # NumPy boolean array. array_bool, # NumPy floating-point array. array_float, # NumPy integer array. array_int, # NumPy string array. array_str, ) # Test all boolean protocols against items of this boolean arrays. _assert_types_objects( { cave.BoolType, }, # NumPy boolean. array_bool[0], ) # Test all string protocols against items of this string array. _assert_types_objects( { cave.StrType, }, # NumPy Unicode string. array_str[0], ) # Test all number protocols satisfied by NumPy numbers against items of # these specific arrays. _assert_types_objects( { cave.NumberType, cave.IntOrFloatType, }, # NumPy floating-point number. array_float[0], ) _assert_types_objects( { cave.NumberType, cave.IntOrFloatType, cave.IntType, }, # NumPy integer. array_int[0], ) # Assert all NumPy scalar types to be general-purpose scalar types. _assert_tuple_objects( cave.ScalarTypes, # NumPy boolean. array_bool[0], # NumPy Unicode string. array_str[0], # NumPy floating-point number. array_float[0], # NumPy integer. array_int[0], ) beartype-0.18.5/beartype_test/a00_unit/a40_api/test_api_meta.py000066400000000000000000000041041461113517100243150ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Package metadata API** unit tests. This submodule unit tests the public API of the :mod:`beartype.meta` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_api_meta() -> None: ''' Test the public API of the :mod:`beartype.meta` submodule. ''' # Defer test-specific imports. from beartype import meta # Assert this submodule's public attributes to be of the expected types. assert isinstance(meta.NAME, str) assert isinstance(meta.PACKAGE_NAME, str) assert isinstance(meta.LICENSE, str) assert isinstance(meta.PYTHON_VERSION_MIN, str) assert isinstance(meta.PYTHON_VERSION_MINOR_MAX, int) assert isinstance(meta.PYTHON_VERSION_MIN_PARTS, tuple) assert isinstance(meta.VERSION, str) assert isinstance(meta.VERSION_PARTS, tuple) assert isinstance(meta.SYNOPSIS, str) assert isinstance(meta.AUTHORS, str) assert isinstance(meta.AUTHOR_EMAIL, str) assert isinstance(meta.COPYRIGHT, str) assert isinstance(meta.URL_HOMEPAGE, str) assert isinstance(meta.URL_DOWNLOAD, str) assert isinstance(meta.URL_ISSUES, str) assert isinstance(meta.LIBS_RUNTIME_OPTIONAL, tuple) assert isinstance(meta.LIBS_TESTTIME_MANDATORY, tuple) assert isinstance(meta.LIBS_TESTTIME_MANDATORY_TOX, tuple) assert isinstance(meta.LIBS_TESTTIME_OPTIONAL, tuple) assert isinstance(meta.LIBS_DOCTIME_MANDATORY, tuple) assert isinstance(meta.LIBS_DOCTIME_MANDATORY_RTD, tuple) assert isinstance(meta.LIBS_DEVELOPER_MANDATORY, tuple) beartype-0.18.5/beartype_test/a00_unit/a40_api/typing/000077500000000000000000000000001461113517100224405ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a40_api/typing/__init__.py000066400000000000000000000000001461113517100245370ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a40_api/typing/test_typingpep544.py000066400000000000000000000411161461113517100263300ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype :pep:`544` **optimization layer** unit tests. This submodule unit tests both the public *and* private API of the private :mod:`beartype.typing._typingpep544` subpackage for sanity. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype_test._util.mark.pytskip import skip_if_python_version_less_than # ....................{ TESTS }.................... def test_typingpep544_metaclass() -> None: ''' Test the private :class:`beartype.typing._typingpep544._CachingProtocolMeta` metaclass. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from abc import abstractmethod from beartype.typing import ( Any, Protocol, TypeVar, runtime_checkable, ) from beartype.typing._typingpep544 import _CachingProtocolMeta # ....................{ LOCALS }.................... # Arbitrary type variable. _T_co = TypeVar('_T_co', covariant=True) # ....................{ CLASSES }.................... # Can we really have it all?! @runtime_checkable # <-- unnecessary at runtime, but Mypy is confused without it class SupportsRoundFromScratch(Protocol[_T_co]): __slots__: Any = () @abstractmethod def __round__(self, ndigits: int = 0) -> _T_co: pass supports_round: SupportsRoundFromScratch = 0 # ....................{ PASS }.................... assert isinstance(supports_round, SupportsRoundFromScratch) assert issubclass(type(SupportsRoundFromScratch), _CachingProtocolMeta) def test_typingpep544_superclass() -> None: ''' Test the public :class:`beartype.typing.Protocol` superclass. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.typing import ( Protocol as ProtocolFast, TypeVar, ) from beartype.typing._typingpep544 import _ProtocolSlow as ProtocolSlow from pytest import raises # ....................{ LOCALS }.................... # Arbitrary type variable. _T_co = TypeVar('_T_co', covariant=True) # Representations of protocols parametrized by one or more type variables. fast_repr = repr(ProtocolFast[_T_co]) slow_repr = repr(ProtocolSlow[_T_co]) # ....................{ PASS }.................... # Assert that our caching protocol superclass memoizes subscriptions. assert ProtocolFast.__module__ == 'beartype.typing' assert ProtocolFast[_T_co] is ProtocolFast[_T_co] # Assert that the representation of a caching protocol is prefixed by the # expected prefix. assert fast_repr.startswith('beartype.typing.Protocol[') # Assert that these representation are suffixed by the same "["- and "] # "-delimited strings. fast_repr_suffix = fast_repr[fast_repr.rindex('['):] slow_repr_suffix = slow_repr[slow_repr.rindex('['):] assert fast_repr_suffix == slow_repr_suffix # ....................{ FAIL }.................... # Assert that attempting to directly subscript the caching protocol # superclass by a non-type variable raises the expected exception. with raises(TypeError): ProtocolFast[str] def test_typingpep544_subclass() -> None: ''' Test expected behaviour of user-defined subclasses of the public :class:`beartype.typing.Protocol` superclass. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from abc import abstractmethod from beartype.typing import ( AnyStr, Protocol, runtime_checkable ) from pytest import raises # ....................{ CLASSES }.................... class SupportsFeebleDreams(Protocol[AnyStr]): ''' Arbitrary protocol directly subclassing the protocol superclass subscripted by one or more type variables, exercising subtle edge cases. ''' @abstractmethod def torpor_of_the_year(self) -> AnyStr: pass class SupportsHiddenBuds(SupportsFeebleDreams[str]): ''' Arbitrary protocol directly subclassing the above protocol superclass subscripted by one or more non-type variables satisfying the type variables subscripting that superclass, exercising subtle edge cases. ''' @abstractmethod def dreamless_sleep(self) -> str: pass # ....................{ PASS }.................... # Assert that optionally decorating protocols by the standard # @typing.runtime_checkable() decorator reduces to a noop. assert runtime_checkable(SupportsFeebleDreams) is SupportsFeebleDreams # Assert that a caching protocol subclass also memoizes subscriptions. assert SupportsFeebleDreams[str] is SupportsFeebleDreams[str] # Assert that optionally decorating abstract protocols by the standard # @typing.runtime_checkable() decorator reduces to a noop. assert runtime_checkable(SupportsFeebleDreams) is SupportsFeebleDreams # ....................{ FAIL }.................... # Assert that attempting to decorate concrete protocol subclasses by # the same decorator raises the expected exception. with raises(TypeError): runtime_checkable(SupportsHiddenBuds) def test_typingpep544_protocols_typing() -> None: ''' Test the public retinue of ``beartype.typing.Supports*`` protocols with respect to both caching optimizations implemented by the public :class:`beartype.typing.Protocol` subclass *and* the private :class:`beartype.typing._typingpep544._CachingProtocolMeta` metaclass. ''' # Defer test-specific imports. from decimal import Decimal from fractions import Fraction from beartype.typing import ( SupportsAbs, SupportsBytes, SupportsComplex, SupportsFloat, SupportsIndex, SupportsInt, SupportsRound, ) from beartype.typing._typingpep544 import _CachingProtocolMeta # Assert *ALL* standard protocols share the expected metaclass. for supports_t in ( SupportsAbs, SupportsBytes, SupportsComplex, SupportsFloat, SupportsIndex, SupportsInt, SupportsRound, ): assert issubclass(type(supports_t), _CachingProtocolMeta) assert supports_t.__module__ == 'beartype.typing' def _assert_isinstance(*types: type, target_t: type) -> None: assert issubclass( target_t.__class__, _CachingProtocolMeta ), ( f'{repr(target_t.__class__)} metaclass not ' f'{repr(_CachingProtocolMeta)}.' ) for t in types: v = t(0) assert isinstance(v, target_t), ( f'{repr(t)} not instance of {repr(target_t)}.') # supports_abs: SupportsAbs = 0 _assert_isinstance( int, float, bool, Decimal, Fraction, target_t=SupportsAbs) # supports_complex: SupportsComplex = Fraction(0, 1) _assert_isinstance( Decimal, Fraction, target_t=SupportsComplex) # supports_float: SupportsFloat = 0 _assert_isinstance( int, float, bool, Decimal, Fraction, target_t=SupportsFloat) # supports_int: SupportsInt = 0 _assert_isinstance( int, float, bool, target_t=SupportsInt) # supports_index: SupportsIndex = 0 _assert_isinstance( int, bool, target_t=SupportsIndex) # supports_round: SupportsRound = 0 _assert_isinstance( int, float, bool, Decimal, Fraction, target_t=SupportsRound) # ....................{ TESTS ~ custom : direct }.................... def test_typingpep544_protocol_custom_direct() -> None: ''' Test the core operation of the public :class:`beartype.typing.Protocol` subclass with respect to user-defined :pep:`544`-compliant protocols directly subclassing :class:`beartype.typing.Protocol` under the :func:`beartype.beartype` decorator. ''' # Defer test-specific imports. from abc import abstractmethod from beartype import beartype from beartype.roar import ( BeartypeCallHintParamViolation, BeartypeCallHintReturnViolation, ) from beartype.typing import ( Protocol, Tuple, ) from pytest import raises # Arbitrary direct protocol. class SupportsFish(Protocol): @abstractmethod def fish(self) -> int: pass # Arbitrary classes satisfying this protocol *WITHOUT* explicitly # subclassing this protocol. class OneFish: def fish(self) -> int: return 1 class TwoFish: def fish(self) -> int: return 2 # Arbitrary classes violating this protocol. class RedSnapper: def oh(self) -> str: return 'snap' # Arbitrary @beartype-decorated callable validating both parameters and # returns to be instances of arbitrary classes satisfying this protocol. @beartype def _supports_fish_identity(arg: SupportsFish) -> SupportsFish: return arg # Assert that instances of classes satisfying this protocol *WITHOUT* # subclassing this protocol satisfy @beartype validation as expected. assert isinstance(_supports_fish_identity(OneFish()), SupportsFish) assert isinstance(_supports_fish_identity(TwoFish()), SupportsFish) # Assert that instances of classes violating this protocol violate # @beartype validation as expected. with raises(BeartypeCallHintParamViolation): _supports_fish_identity(RedSnapper()) # type: ignore [arg-type] # Arbitrary @beartype-decorated callable guaranteed to *ALWAYS* raise a # violation by returning an object that *NEVER* satisfies its type hint. @beartype def _lies_all_lies(arg: SupportsFish) -> Tuple[str]: return (arg.fish(),) # type: ignore [return-value] # Assert this callable raises the expected exception when passed an # instance of a class otherwise satisfying this protocol. with raises(BeartypeCallHintReturnViolation): _lies_all_lies(OneFish()) def test_typingpep544_protocol_custom_direct_typevar() -> None: ''' Test the core operation of the public :class:`beartype.typing.Protocol` subclass with respect to user-defined :pep:`544`-compliant protocols directly subclassing :class:`beartype.typing.Protocol` and parametrized by one or more type variables under the :func:`beartype.beartype` decorator. ''' # Defer test-specific imports. from abc import abstractmethod from beartype import beartype from beartype.typing import ( Any, Protocol, TypeVar, runtime_checkable, ) # Arbitrary type variable. _T_co = TypeVar('_T_co', covariant=True) # Arbitrary direct protocol subscripted by this type variable. @runtime_checkable class SupportsAbsToo(Protocol[_T_co]): __slots__: Any = () @abstractmethod def __abs__(self) -> _T_co: pass # Arbitrary @beartype-decorated callable validating a parameter to be an # instance of arbitrary classes satisfying this protocol. @beartype def myabs(arg: SupportsAbsToo[_T_co]) -> _T_co: return abs(arg) # Assert @beartype wrapped this callable with the expected type-checking. assert myabs(-1) == 1 # ....................{ TESTS ~ custom : indirect }.................... def test_typingpep544_protocol_custom_indirect() -> None: ''' Test the core operation of the public :class:`beartype.typing.Protocol` subclass with respect to user-defined :pep:`544`-compliant protocols indirectly subclassing :class:`beartype.typing.Protocol` under the :func:`beartype.beartype` decorator. ''' # Defer test-specific imports. from abc import abstractmethod from beartype import beartype from beartype.roar import ( BeartypeCallHintParamViolation, BeartypeCallHintReturnViolation, ) from beartype.typing import ( Protocol, Tuple, ) from pytest import raises # Arbitrary direct protocol. class SupportsFish(Protocol): @abstractmethod def fish(self) -> int: pass # Arbitrary indirect protocol subclassing the above direct protocol. class SupportsCod(SupportsFish): @abstractmethod def dear_cod(self) -> str: pass # Arbitrary classes satisfying this protocol *WITHOUT* explicitly # subclassing this protocol. class OneCod: def fish(self) -> int: return 1 def dear_cod(self) -> str: return 'Not bad, cod do better...' class TwoCod: def fish(self) -> int: return 2 def dear_cod(self) -> str: return "I wouldn't be cod dead in that." # Arbitrary classes violating this protocol. class PacificSnapper: # def fish(self) -> int: # return 0xFEEDBEEF def dear_cod(self) -> str: return 'Cod you pass the butterfish?' def berry_punny(self) -> str: return 'Had a girlfriend, I lobster. But then I flounder!' # Arbitrary @beartype-decorated callable validating both parameters and # returns to be instances of arbitrary classes satisfying this protocol. @beartype def _supports_cod_identity(arg: SupportsCod) -> SupportsCod: return arg # Assert that instances of classes satisfying this protocol *WITHOUT* # subclassing this protocol satisfy @beartype validation as expected. assert isinstance(_supports_cod_identity(OneCod()), SupportsCod) assert isinstance(_supports_cod_identity(TwoCod()), SupportsCod) # Assert that instances of classes violating this protocol violate # @beartype validation as expected. with raises(BeartypeCallHintParamViolation): _supports_cod_identity(PacificSnapper()) # type: ignore [arg-type] # Arbitrary @beartype-decorated callable guaranteed to *ALWAYS* raise a # violation by returning an object that *NEVER* satisfies its type hint. @beartype def _lies_all_lies(arg: SupportsCod) -> Tuple[int]: return (arg.dear_cod(),) # type: ignore [return-value] # Assert this callable raises the expected exception when passed an # instance of a class otherwise satisfying this protocol. with raises(BeartypeCallHintReturnViolation): _lies_all_lies(OneCod()) # ....................{ TESTS ~ pep 593 }.................... # If the active Python interpreter targets Python < 3.9 and thus fails to # support PEP 593, skip all PEP 593-specific tests declared below. #FIXME: Generalize to support "typing_extensions.Annotated" as well. *sigh* @skip_if_python_version_less_than('3.9.0') def test_typingpep544_pep593_integration() -> None: ''' Test the public :class:`beartype.typing.Protocol` subclass when nested within a :pep:`593`-compliant . ''' # Defer test-specific imports. from abc import abstractmethod from beartype import beartype from beartype.roar import BeartypeException from beartype.typing import ( Annotated, Protocol, ) from beartype.vale import Is from pytest import raises class SupportsOne(Protocol): @abstractmethod def one(self) -> int: pass class TallCoolOne: def one(self) -> int: return 1 class FalseOne: def one(self) -> int: return 0 class NotEvenOne: def two(self) -> str: return "two" @beartype def _there_can_be_only_one( n: Annotated[SupportsOne, Is[lambda x: x.one() == 1]], # type: ignore[name-defined] ) -> int: val = n.one() assert val == 1 # <-- should never fail because it's caught by beartype first return val _there_can_be_only_one(TallCoolOne()) with raises(BeartypeException): _there_can_be_only_one(FalseOne()) with raises(BeartypeException): _there_can_be_only_one(NotEvenOne()) # type: ignore [arg-type] beartype-0.18.5/beartype_test/a00_unit/a40_api/vale/000077500000000000000000000000001461113517100220555ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a40_api/vale/__init__.py000066400000000000000000000000001461113517100241540ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a40_api/vale/_core/000077500000000000000000000000001461113517100231445ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a40_api/vale/_core/test_valecore.py000066400000000000000000000167231461113517100263660ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype validator unit tests.** This submodule unit tests the private :mod:`beartype.vale._core._valecore` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_api_vale_validator_pass() -> None: ''' Test successful usage of the private :class:`beartype.vale._core._valecore.BeartypeValidator` class. ''' # Defer test-specific imports. from beartype.vale._core._valecore import BeartypeValidator # Arbitrary valid data validator. not_though_the_soldier_knew = lambda text: bool('Someone had blundered.') # Keyword arguments passing arguments describing this validator excluding # the "get_repr" argument. kwargs_sans_get_repr = dict( is_valid=not_though_the_soldier_knew, is_valid_code_locals={'yum': not_though_the_soldier_knew}, ) # Keyword arguments passing arguments describing this validator including # the "get_repr" argument. kwargs = kwargs_sans_get_repr.copy() kwargs['get_repr'] = lambda: ( "Is[lambda text: bool('Someone had blundered.')]") # Code already prefixed by "(" and suffixed by ")". is_valid_code_delimited = "({obj} == 'Was there a man dismayed?')" # Code *NOT* already prefixed by "(" and suffixed by ")". is_valid_code_undelimited = "{obj} == 'All in the valley of Death'" # Assert that a beartype validator preserves delimited code as is. validator_delimited = BeartypeValidator( is_valid_code=is_valid_code_delimited, **kwargs) assert validator_delimited._is_valid_code is is_valid_code_delimited # Assert that this validator reports the expected diagnosis. validator_delimited_diagnosis = validator_delimited.get_diagnosis( obj='Flashed all their sabres bare,', indent_level_outer=' ', indent_level_inner='', ) assert 'True' in validator_delimited_diagnosis assert 'Someone had blundered.' in validator_delimited_diagnosis # Assert that a beartype validator delimits undelimited code as well as # providing non-default validation diagnoses. validator_undelimited = BeartypeValidator( is_valid_code=is_valid_code_undelimited, **kwargs) assert ( validator_undelimited._is_valid_code == f'({is_valid_code_undelimited})' ) # Assert that a beartype validator also accepts a string representer. validator_repr_str = BeartypeValidator( is_valid_code=is_valid_code_delimited, get_repr='All that was left of them,', **kwargs_sans_get_repr ) # Assert these objects have the expected representations. assert 'Someone had blundered.' in repr(validator_delimited) assert repr(validator_repr_str) == 'All that was left of them,' def test_api_vale_validator_fail() -> None: ''' Test unsuccessful usage of the private :class:`beartype.vale._core._valecore.BeartypeValidator` class. ''' # Defer test-specific imports. from beartype.roar import BeartypeValeSubscriptionException from beartype.vale._core._valecore import BeartypeValidator from pytest import raises # Arbitrary valid data validator. into_the_jaws_of_death = lambda text: bool('Into the mouth of hell') # Assert that attempting to instantiate the "BeartypeValidator" class with # non-string code raises the expected exception. with raises(BeartypeValeSubscriptionException): BeartypeValidator( is_valid=into_the_jaws_of_death, is_valid_code=b'Into the jaws of Death,', is_valid_code_locals={'yum': into_the_jaws_of_death}, get_repr=lambda: "Is[lambda text: bool('Into the mouth of hell')]", ) # Assert that attempting to instantiate the "BeartypeValidator" class with # empty code raises the expected exception. with raises(BeartypeValeSubscriptionException): BeartypeValidator( is_valid=into_the_jaws_of_death, is_valid_code='', is_valid_code_locals={'yum': into_the_jaws_of_death}, get_repr=lambda: "Is[lambda text: bool('Into the mouth of hell')]", ) # Assert that attempting to instantiate the "BeartypeValidator" class with # code *NOT* containing the substring "{obj}" raises the expected # exception. with raises(BeartypeValeSubscriptionException): BeartypeValidator( is_valid=into_the_jaws_of_death, is_valid_code='Came through the jaws of Death,', is_valid_code_locals={'yum': into_the_jaws_of_death}, get_repr=lambda: "Is[lambda text: bool('Into the mouth of hell')]", ) # Assert that attempting to instantiate the "BeartypeValidator" class with # valid code and non-dictionary code locals raises the expected exception. with raises(BeartypeValeSubscriptionException): BeartypeValidator( is_valid=into_the_jaws_of_death, is_valid_code="{obj} == 'Back from the mouth of hell,'", is_valid_code_locals={'yum', into_the_jaws_of_death}, get_repr=lambda: "Is[lambda text: bool('Into the mouth of hell')]", ) # Keyword arguments passing valid code and non-dictionary code locals. kwargs_is_valid = dict( is_valid=into_the_jaws_of_death, is_valid_code="{obj} == 'Back from the mouth of hell,'", is_valid_code_locals={'yum': into_the_jaws_of_death}, ) # Assert that attempting to instantiate the "BeartypeValidator" class with # valid code and code locals but a representer of an invalid type raises # the expected exception. with raises(BeartypeValeSubscriptionException): BeartypeValidator( get_repr=b'All that was left of them,', **kwargs_is_valid) # Assert that attempting to instantiate the "BeartypeValidator" class with # valid code and code locals but an empty-string representer raises the # expected exception. with raises(BeartypeValeSubscriptionException): BeartypeValidator(get_repr='', **kwargs_is_valid) # Assert that attempting to instantiate the "BeartypeValidator" class with # valid code and code locals but a C-based representer raises the expected # exception. with raises(BeartypeValeSubscriptionException): BeartypeValidator(get_repr=iter, **kwargs_is_valid) # Assert that attempting to instantiate the "BeartypeValidator" class with # valid code and code locals but a pure-Python representer accepting one or # more parameters raises the expected exception. with raises(BeartypeValeSubscriptionException): BeartypeValidator( get_repr=lambda rode, the, six, hundred: 'Into the valley of Death', **kwargs_is_valid ) #FIXME: Uncomment when inevitably needed again. *sigh* # # Keyword arguments passing valid code, non-dictionary code locals, and a # # representer. # kwargs_is_valid_get_repr = dict( # get_repr='Spread far around and inaccessibly', # **kwargs_is_valid # ) beartype-0.18.5/beartype_test/a00_unit/a40_api/vale/_core/test_valecorebinary.py000066400000000000000000000041031461113517100275600ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype validator unit tests.** This submodule unit tests the private :mod:`beartype.vale._core._valecore` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... #FIXME: Uncomment after refactoring "BeartypeValidatorBinaryABC", please. # def test_api_vale_validator_conjunction() -> None: # ''' # Test successful usage of the private # :class:`beartype.vale._core._valecore.BeartypeValidatorConjunction` class. # ''' # # # Defer test-specific imports. # from beartype.roar import BeartypeValeSubscriptionException # from beartype.vale._core._valecore import BeartypeValidator # from beartype.vale._core._valecorebinary import ( # BeartypeValidatorConjunction) # from pytest import raises # # not_though_the_soldier_knew = lambda text: bool('Someone had blundered.') # validator_operand_1 = BeartypeValidator( # is_valid_code="({obj} == 'Was there a man dismayed?')", # is_valid=not_though_the_soldier_knew, # is_valid_code_locals={'yum': not_though_the_soldier_knew}, # get_repr=lambda: "Is[lambda text: bool('Someone had blundered.')]", # ) # # # Assert that a beartype validator preserves delimited code as is. # validator_delimited = BeartypeValidatorConjunction( # validator_operand_1=validator_operand_1, # ) # # # Assert that attempting to instantiate the "BeartypeValidator" class with # # non-string code raises the expected exception. # with raises(BeartypeValeSubscriptionException): beartype-0.18.5/beartype_test/a00_unit/a40_api/vale/_is/000077500000000000000000000000001461113517100226275ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a40_api/vale/_is/__init__.py000066400000000000000000000000001461113517100247260ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a40_api/vale/_is/test_valeis.py000066400000000000000000000246001461113517100255250ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype callable-based data validation unit tests.** This submodule unit tests the subset of the public API of the :mod:`beartype.vale` subpackage defined by the private :mod:`beartype.vale._is._valeis` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ class : is }.................... def test_api_vale_is_pass() -> None: ''' Test successful usage of the :mod:`beartype.vale.Is` factory. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from beartype._util.func.utilfuncmake import make_func from beartype.vale import Is from beartype.vale._core._valecore import BeartypeValidator from collections.abc import Mapping # ....................{ CLASSES }.................... class FalseFake(object): ''' Arbitrary class whose instances are implicitly convertible into **booleans** (i.e., :class:`bool` objects) via the :meth:`__bool__` dunder method despite *not* subclassing the builtin :class:`bool` type. ''' def __bool__(self) -> bool: ''' Dunder method coercing this non-boolean into a boolean. ''' # Return false rather than true to exercise a non-trivial edge case # in which the callable subscripting an "Is[...]" validator returns # a non-boolean object, which then induces a validation failure, # which then displays a validator diagnosis embedding that object. return False # ....................{ FUNCS }.................... # Undecorated non-lambda function with which to subscript the "Is" factory. def _is_quoted(text): return '"' in text or "'" in text # Undecorated non-lambda function returning a non-boolean object implicitly # convertible into a boolean with which to subscript the "Is" factory, # exercising an edge case coercing the former to the latter in that factory. def _is_false_fake(obj): return FalseFake() # Decorated non-lambda function with which to subscript the "Is" factory, # exercising an edge case validating callable parameters in that factory. @beartype def _is_exclamatory(text: str): return '!' in text # Dynamically generated lambda function. To exercise in-memory validators # dynamically defined outside of a standard on-disk module (e.g., "smart" # REPLs like IPython), this function's code is intentionally cached with # the standard "linecache" module. _is_commentary = make_func( func_name='_is_commentary', func_code=( """_is_commentary = lambda text: text and text[-1] == ','"""), is_debug=True, ) # ....................{ VALIDATORS }.................... # Validators produced by subscripting this factory with lambda functions # satisfying the expected API defined physically in this on-disk module. IsLengthy = Is[lambda text: len(text) > 30] IsSentence = Is[lambda text: text and text[-1] == '.'] # Validators produced by subscripting this factory with lambda functions # satisfying the expected API defined dynamically in-memory. IsCommentary = Is[_is_commentary] # Validator produced by subscripting this factory with non-lambda functions # satisfying the expected API. IsQuoted = Is[_is_quoted] IsExclamatory = Is[_is_exclamatory] IsFalseFake = Is[_is_false_fake] # Validator synthesized from the above validators with the domain-specific # language (DSL) supported by those validators. IsLengthyOrUnquotedSentence = IsLengthy | (IsSentence & ~IsQuoted) # ....................{ ASSERTS ~ instance }.................... # Assert these validators satisfy the expected API. assert isinstance(IsLengthy, BeartypeValidator) assert isinstance(IsLengthyOrUnquotedSentence, BeartypeValidator) # Assert a validator provides both non-empty code and code locals. assert isinstance(IsLengthy._is_valid_code, str) assert isinstance(IsLengthy._is_valid_code_locals, Mapping) assert bool(IsLengthy._is_valid_code) assert bool(IsLengthy._is_valid_code_locals) # ....................{ ASSERTS ~ is_valid }.................... # Assert that non-composite validators perform the expected validation. assert IsLengthy.is_valid('Plunged in the battery-smoke') is False assert IsLengthy.is_valid('Right through the line they broke;') is True assert IsSentence.is_valid('Theirs not to make reply,') is False assert IsSentence.is_valid('Theirs but to do and die.') is True assert IsCommentary.is_valid('Remote, serene, and inaccessible:') is False assert IsCommentary.is_valid( 'Power dwells apart in its tranquillity,') is True assert IsQuoted.is_valid('Theirs not to reason why,') is False assert IsQuoted.is_valid('"Forward, the Light Brigade!"') is True assert IsExclamatory.is_valid( 'Thou art the path of that unresting sound—') is False assert IsExclamatory.is_valid( '"Dizzy Ravine! and when I gaze on thee"') is True # Assert that a composite validator performs the expected validation. assert IsLengthyOrUnquotedSentence.is_valid( 'Stormed at with shot and shell,') is True assert IsLengthyOrUnquotedSentence.is_valid( 'Rode the six hundred.') is True assert IsLengthyOrUnquotedSentence.is_valid( '"Forward, the Light Brigade.') is False assert IsLengthyOrUnquotedSentence.is_valid( 'Into the valley of Death') is False # ....................{ ASSERTS ~ repr }.................... # Assert a physical lambda-based validator has the expected representation. IsLengthyRepr = repr(IsLengthy) assert 'len(text) > 30' in IsLengthyRepr # Assert a dynamic lambda-based validator has the expected representation. IsCommentary = repr(IsCommentary) assert "text[-1] == ','" in IsCommentary # Assert a non-lambda-based validator has the expected representation. IsQuotedRepr = repr(IsQuoted) assert '._is_quoted' in IsQuotedRepr # Assert that repeated accesses of that representation are memoized by # efficiently returning the same string. assert repr(IsLengthy) is IsLengthyRepr # Assert this validator provides the expected representation. IsLengthyOrUnquotedSentence_repr = repr(IsLengthyOrUnquotedSentence) assert '|' in IsLengthyOrUnquotedSentence_repr assert '&' in IsLengthyOrUnquotedSentence_repr assert '~' in IsLengthyOrUnquotedSentence_repr # ....................{ ASSERTS ~ diagnosis }.................... # Assert that a composite validator reports the expected diagnosis for a # string violating one of the subvalidators composing this validator. IsLengthyOrUnquotedSentence_diagnosis = ( IsLengthyOrUnquotedSentence.get_diagnosis( obj='For "the very spirit" fails.', indent_level_outer=' ', indent_level_inner='', )) assert IsLengthyOrUnquotedSentence_diagnosis.count('False') == 4 assert IsLengthyOrUnquotedSentence_diagnosis.count('True') == 2 assert '|' in IsLengthyOrUnquotedSentence_diagnosis assert '&' in IsLengthyOrUnquotedSentence_diagnosis assert '~' in IsLengthyOrUnquotedSentence_diagnosis # Assert that a non-composite validator subscripted by a callable returning # a non-boolean implicitly convertible into boolean "False" reports the # expected diagnosis for any arbitrary object. IsFalseFake_diagnosis = IsFalseFake.get_diagnosis( obj="Shine in the rushing torrents' restless gleam,", indent_level_outer=' ', indent_level_inner='', ) assert IsFalseFake_diagnosis.count('False') == 1 def test_api_vale_is_fail() -> None: ''' Test unsuccessful usage of the :mod:`beartype.vale.Is` factory. ''' # Defer test-specific imports. from beartype.roar import BeartypeValeSubscriptionException from beartype.vale import Is from pytest import raises # Assert that subscripting this factory with the empty tuple raises the # expected exception. with raises(BeartypeValeSubscriptionException): Is[()] # Assert that subscripting this factory with two or more arguments raises # the expected exception. with raises(BeartypeValeSubscriptionException): Is['Cannon to right of them,', 'Cannon to left of them,'] # Assert that subscripting this factory with a non-callable argument # raises the expected exception. with raises(BeartypeValeSubscriptionException): Is['Cannon in front of them'] # Assert that subscripting this factory with a C-based callable argument # raises the expected exception. with raises(BeartypeValeSubscriptionException): Is[iter] # Assert that subscripting this factory with a pure-Python callable that # does *NOT* accept exactly one argument raises the expected exception. with raises(BeartypeValeSubscriptionException): Is[lambda: True] # Assert that subscripting this factory with a pure-Python callable that # does *NOT* accept exactly one argument raises the expected exception. with raises(BeartypeValeSubscriptionException): Is[lambda: True] # Object produced by subscripting this factory with a valid validator. IsNonEmpty = Is[lambda text: bool(text)] # Assert that attempting to synthesize new objects from the above object # with the domain-specific language (DSL) supported by that object and an # arbitrary object that is *NOT* an instance of the same class raises the # expected exception. with raises(BeartypeValeSubscriptionException): IsNonEmpty & 'While horse and hero fell.' with raises(BeartypeValeSubscriptionException): IsNonEmpty | 'While horse and hero fell.' beartype-0.18.5/beartype_test/a00_unit/a40_api/vale/_is/test_valeiscls.py000066400000000000000000000273141461113517100262340ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype code-based type data validation unit tests.** This submodule unit tests the subset of the public API of the :mod:`beartype.vale` subpackage defined by the private :mod:`beartype.vale._is._valeistype` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ class : isinstance }.................... def test_api_vale_isinstance_pass() -> None: ''' Test successful usage of the :class:`beartype.vale.IsInstance` factory. ''' # Defer test-specific imports. from beartype.vale import IsInstance from beartype.vale._core._valecore import BeartypeValidator from beartype._util.utilobject import get_object_name from beartype_test.a00_unit.data.data_type import ( Class, Subclass, SubclassSubclass) # Subclasses of classes subscripting this factory below. class UnicodeStr(str): pass class ByteStr(bytes): pass # Instances of classes imported or declared above. class_instance = Class() subclass_instance = Subclass() subclasssubclass_instance = SubclassSubclass() unicodestr_instance = UnicodeStr( 'Driven like a homeless cloud from steep to steep') bytestr_instance = ByteStr(b'That vanishes among the viewless gales!') # Validators produced by subscripting this factory with one superclass. IsClassInstance = IsInstance[Class] IsSubclassInstance = IsInstance[Subclass] # Validator produced by subscripting this factory with two or more # superclasses. IsStrOrBytesInstance = IsInstance[str, bytes] # Assert these validators satisfy the expected API. assert isinstance(IsClassInstance, BeartypeValidator) assert isinstance(IsSubclassInstance, BeartypeValidator) assert isinstance(IsStrOrBytesInstance, BeartypeValidator) # Assert these validators produce the same objects when subscripted by the # same arguments (and are thus memoized on subscripted arguments). assert IsClassInstance is IsInstance[Class] assert IsSubclassInstance is IsInstance[Subclass] assert IsStrOrBytesInstance is IsInstance[str, bytes] # Assert these validators accept the superclasses subscripting these # validators. By definition, any class is a subclass of itself. assert IsClassInstance.is_valid(class_instance) is True assert IsSubclassInstance.is_valid(subclass_instance) is True assert IsStrOrBytesInstance.is_valid(unicodestr_instance) is True assert IsStrOrBytesInstance.is_valid(bytestr_instance) is True # Assert these validators accept instances of subclasses of the # superclasses subscripting these validators. assert IsClassInstance.is_valid(subclass_instance) is True assert IsSubclassInstance.is_valid(subclasssubclass_instance) is True assert IsStrOrBytesInstance.is_valid(unicodestr_instance) is True assert IsStrOrBytesInstance.is_valid(bytestr_instance) is True # Assert these validators reject objects *NOT* instancing the superclasses # subscripting these validators. assert IsClassInstance.is_valid( 'Far, far above, piercing the infinite sky,') is False assert IsSubclassInstance.is_valid(class_instance) is False assert IsStrOrBytesInstance.is_valid(subclass_instance) is False # Assert these validators reject classes. assert IsClassInstance.is_valid(Class) is False assert IsSubclassInstance.is_valid(Subclass) is False assert IsStrOrBytesInstance.is_valid(str) is False assert IsStrOrBytesInstance.is_valid(bytes) is False # Assert these validators have the expected representation. assert get_object_name(Class) in repr(IsClassInstance) assert get_object_name(Subclass) in repr(IsSubclassInstance) assert 'str' in repr(IsStrOrBytesInstance) assert 'bytes' in repr(IsStrOrBytesInstance) # Validator synthesized from the above validators with the domain-specific # language (DSL) supported by these validators. IsClassOrStrOrBytesInstance = ( IsSubclassInstance | IsStrOrBytesInstance | IsClassInstance) # Assert this validator performs the expected validation. assert IsClassOrStrOrBytesInstance.is_valid(class_instance) is True assert IsClassOrStrOrBytesInstance.is_valid(subclass_instance) is True assert IsClassOrStrOrBytesInstance.is_valid(subclasssubclass_instance) is ( True) assert IsClassOrStrOrBytesInstance.is_valid(unicodestr_instance) is True assert IsClassOrStrOrBytesInstance.is_valid(bytestr_instance) is True # Assert this validator provides the expected representation. assert '|' in repr(IsClassOrStrOrBytesInstance) def test_api_vale_isinstance_fail() -> None: ''' Test unsuccessful usage of the :class:`beartype.vale.IsInstance` factory. ''' # Defer test-specific imports. from beartype.roar import BeartypeValeSubscriptionException from beartype.vale import IsInstance from beartype_test.a00_unit.data.data_type import NonIsinstanceableClass from pytest import raises # Assert that subscripting this factory with the empty tuple raises the # expected exception. with raises(BeartypeValeSubscriptionException): IsInstance[()] # Assert that subscripting this factory with a non-class raises the # expected exception. with raises(BeartypeValeSubscriptionException): IsInstance['Mont Blanc appears—still, snowy, and serene;'] # Assert that subscripting this factory with a non-isinstanceable class # raises the expected exception. with raises(BeartypeValeSubscriptionException): IsInstance[NonIsinstanceableClass] # Assert that subscripting this factory with both a class and non-class # raises the expected exception. with raises(BeartypeValeSubscriptionException): IsInstance[str, 'Its subject mountains their unearthly forms'] # Assert that subscripting this factory with both an isinstanceable and # non-isinstanceable class raises the expected exception. with raises(BeartypeValeSubscriptionException): IsInstance[bytes, NonIsinstanceableClass] # ....................{ TESTS ~ class : issubclass }.................... def test_api_vale_issubclass_pass() -> None: ''' Test successful usage of the :class:`beartype.vale.IsSubclass` factory. ''' # Defer test-specific imports. from beartype.vale import IsSubclass from beartype.vale._core._valecore import BeartypeValidator from beartype._util.utilobject import get_object_name from beartype_test.a00_unit.data.data_type import ( Class, Subclass, SubclassSubclass) # Subclasses of classes subscripting this factory below. class UnicodeStr(str): pass class ByteStr(bytes): pass # Validators produced by subscripting this factory with one superclass. IsClassSubclass = IsSubclass[Class] IsSubclassSubclass = IsSubclass[Subclass] # Validator produced by subscripting this factory with two or more # superclasses. IsStrOrBytesSubclass = IsSubclass[str, bytes] # Assert these validators satisfy the expected API. assert isinstance(IsClassSubclass, BeartypeValidator) assert isinstance(IsSubclassSubclass, BeartypeValidator) assert isinstance(IsStrOrBytesSubclass, BeartypeValidator) # Assert these validators produce the same objects when subscripted by the # same arguments (and are thus memoized on subscripted arguments). assert IsClassSubclass is IsSubclass[Class] assert IsSubclassSubclass is IsSubclass[Subclass] assert IsStrOrBytesSubclass is IsSubclass[str, bytes] # Assert these validators accept the superclasses subscripting these # validators. By definition, any class is a subclass of itself. assert IsClassSubclass.is_valid(Class) is True assert IsSubclassSubclass.is_valid(Subclass) is True assert IsStrOrBytesSubclass.is_valid(str) is True assert IsStrOrBytesSubclass.is_valid(bytes) is True # Assert these validators accept subclasses of the superclasses # subscripting these validators. assert IsClassSubclass.is_valid(Subclass) is True assert IsSubclassSubclass.is_valid(SubclassSubclass) is True assert IsStrOrBytesSubclass.is_valid(UnicodeStr) is True assert IsStrOrBytesSubclass.is_valid(ByteStr) is True # Assert these validators reject classes *NOT* subclassing the superclasses # subscripting these validators. assert IsClassSubclass.is_valid(type) is False assert IsSubclassSubclass.is_valid(Class) is False assert IsStrOrBytesSubclass.is_valid(Subclass) is False # Assert these validators reject non-classes. assert IsClassSubclass.is_valid( 'Over whose pines, and crags, and caverns sail') is False assert IsSubclassSubclass.is_valid( 'Fast cloud-shadows and sunbeams: awful scene,') is False assert IsStrOrBytesSubclass.is_valid( 'Bursting through these dark mountains like the flame') is False # Assert these validators have the expected representation. assert get_object_name(Class) in repr(IsClassSubclass) assert get_object_name(Subclass) in repr(IsSubclassSubclass) assert 'str' in repr(IsStrOrBytesSubclass) assert 'bytes' in repr(IsStrOrBytesSubclass) # Validator synthesized from the above validators with the domain-specific # language (DSL) supported by these validators. IsClassOrStrOrBytesSubclass = ( IsSubclassSubclass | IsStrOrBytesSubclass | IsClassSubclass) # Assert this validator performs the expected validation. assert IsClassOrStrOrBytesSubclass.is_valid(Class) is True assert IsClassOrStrOrBytesSubclass.is_valid(Subclass) is True assert IsClassOrStrOrBytesSubclass.is_valid(SubclassSubclass) is True assert IsClassOrStrOrBytesSubclass.is_valid(UnicodeStr) is True assert IsClassOrStrOrBytesSubclass.is_valid(ByteStr) is True # Assert this validator provides the expected representation. assert '|' in repr(IsClassOrStrOrBytesSubclass) def test_api_vale_issubclass_fail() -> None: ''' Test unsuccessful usage of the :class:`beartype.vale.IsSubclass` factory. ''' # Defer test-specific imports. from beartype.roar import BeartypeValeSubscriptionException from beartype.vale import IsSubclass from beartype_test.a00_unit.data.data_type import NonIssubclassableClass from pytest import raises # Assert that subscripting this factory with the empty tuple raises the # expected exception. with raises(BeartypeValeSubscriptionException): IsSubclass[()] # Assert that subscripting this factory with a non-class raises the # expected exception. with raises(BeartypeValeSubscriptionException): IsSubclass['Where Power in likeness of the Arve comes down'] # Assert that subscripting this factory with a non-issubclassable class # raises the expected exception. with raises(BeartypeValeSubscriptionException): IsSubclass[NonIssubclassableClass] # Assert that subscripting this factory with both a class and non-class # raises the expected exception. with raises(BeartypeValeSubscriptionException): IsSubclass[str, 'Of lightning through the tempest;—thou dost lie,'] # Assert that subscripting this factory with both an issubclassable and # non-issubclassable class raises the expected exception. with raises(BeartypeValeSubscriptionException): IsSubclass[bytes, NonIssubclassableClass] beartype-0.18.5/beartype_test/a00_unit/a40_api/vale/_is/test_valeisobj.py000066400000000000000000000227551461113517100262310ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype code-based object data validation unit tests.** This submodule unit tests the subset of the public API of the :mod:`beartype.vale` subpackage defined by the private :mod:`beartype.vale._is._valeisobj` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ CONSTANTS }.................... AS_WHEN_NIGHT_IS_BARE = ( "From one lonely cloud", "The moon rains out her beams, and Heaven is overflow'd.", ) ''' Arbitrary tuple to be assigned to an arbitrary instance variable of the arbitrary class defined below. ''' # ....................{ CLASSES ~ good }.................... class AllTheEarthAndAir(object): ''' Arbitrary class defining an arbitrary attribute whose value has *no* accessible attributes but satisfies a validator tested below. ''' def __init__(self) -> None: ''' Initialize this object by defining this attribute. ''' # Initialize this attribute to a shallow copy of this list rather # than this list itself to properly test equality comparison. self.with_thy_voice_is_loud = AS_WHEN_NIGHT_IS_BARE[:] class WhatThouArtWeKnowNot(object): ''' Arbitrary class defining an arbitrary attribute whose value is an instance of another class defining another arbitrary attribute. ''' def __init__(self) -> None: ''' Initialize this object by defining this attribute. ''' self.what_is_most_like_thee = AllTheEarthAndAir() # ....................{ CLASSES ~ bad }.................... class InAPalaceTower(object): ''' Arbitrary class defining an arbitrary attribute of a differing name as that defined by the :class:`AllTheEarthAndAir` class. ''' def __init__(self) -> None: ''' Initialize this object by defining this attribute. ''' self.soothing_her_love_laden = [ 'Soul in secret hour', 'With music sweet as love, which overflows her bower:', ] class TillTheWorldIsWrought(object): ''' Arbitrary class defining an arbitrary attribute of the same name as that defined by the :class:`AllTheEarthAndAir` class whose value has *no* accessible attributes and does *not* satisfy a validator tested below. ''' def __init__(self) -> None: ''' Initialize this object by defining this attribute. ''' # Initialize this attribute to an arbitrary list unequal to the value # initializing the comparable # "AllTheEarthAndAir.with_thy_voice_is_loud" attribute. self.with_thy_voice_is_loud = [ 'To sympathy with hopes and fears it heeded not:', 'As from thy presence showers a rain of melody.', ] class InADellOfDew(object): ''' Arbitrary class defining an arbitrary attribute of the same name as that defined by the :class:`WhatThouArtWeKnowNot` class whose value is an instance of another class defining an attribute of the same name as that defined by the :class:`AllTheEarthAndAir` class whose value does *not* satisfy a validator tested below. ''' def __init__(self) -> None: ''' Initialize this object by defining this attribute. ''' self.what_is_most_like_thee = TillTheWorldIsWrought() # ....................{ TESTS ~ class : isattr }.................... def test_api_vale_isattr_pass() -> None: ''' Test successful usage of the :mod:`beartype.vale.IsAttr` factory. ''' # Defer test-specific imports. from beartype.vale import IsAttr, IsEqual from beartype.vale._core._valecore import BeartypeValidator # Instances of valid test classes declared above. from_rainbow_clouds = AllTheEarthAndAir() drops_so_bright_to_see = WhatThouArtWeKnowNot() # Instances of invalid test classes declared above. like_a_glow_worm_golden = InAPalaceTower() like_a_high_born_maiden = TillTheWorldIsWrought() scattering_unbeholden = InADellOfDew() # Validator produced by subscripting this factory with the name of an # attribute defined by the former class and that attribute's value. IsInTheLightOfThought = IsAttr[ 'with_thy_voice_is_loud', IsEqual[AS_WHEN_NIGHT_IS_BARE]] # Validator produced by subscripting this factory with the name of an # attribute defined by the latter class and the prior validator. IsSingingHymnsUnbidden = IsAttr[ 'what_is_most_like_thee', IsInTheLightOfThought] # Assert these validators satisfy the expected API. assert isinstance(IsInTheLightOfThought, BeartypeValidator) assert isinstance(IsSingingHymnsUnbidden, BeartypeValidator) # Assert these validators produce the same objects when subscripted by the # same arguments (and are thus memoized on subscripted arguments). assert IsSingingHymnsUnbidden is IsAttr[ 'what_is_most_like_thee', IsInTheLightOfThought] # Assert these validators accept objects defining attributes with the same # names and values as those subscripting these validators. assert IsInTheLightOfThought.is_valid(from_rainbow_clouds) is True assert IsSingingHymnsUnbidden.is_valid(drops_so_bright_to_see) is True # Assert these validators reject objects defining attributes with differing # names to those subscripting these validators. assert IsInTheLightOfThought.is_valid(like_a_glow_worm_golden) is False assert IsSingingHymnsUnbidden.is_valid(like_a_glow_worm_golden) is False # Assert these validators reject objects defining attributes with the same # names but differing values to those subscripting these validators. assert IsInTheLightOfThought.is_valid(like_a_high_born_maiden) is False assert IsSingingHymnsUnbidden.is_valid(scattering_unbeholden) is False # Assert these validators have the expected representation. IsInTheLightOfThought_repr = repr(IsInTheLightOfThought) assert repr('with_thy_voice_is_loud') in IsInTheLightOfThought_repr assert repr(AS_WHEN_NIGHT_IS_BARE) in IsInTheLightOfThought_repr # Validator synthesized from the above validators with the domain-specific # language (DSL) supported by these validators. IsInTheLightOfThoughtOrSingingHymnsUnbidden = ( IsInTheLightOfThought | IsSingingHymnsUnbidden) # Assert this object performs the expected validation. assert IsInTheLightOfThoughtOrSingingHymnsUnbidden.is_valid( AllTheEarthAndAir()) is True assert IsInTheLightOfThoughtOrSingingHymnsUnbidden.is_valid( WhatThouArtWeKnowNot()) is True assert IsInTheLightOfThoughtOrSingingHymnsUnbidden.is_valid( InAPalaceTower()) is False assert IsInTheLightOfThoughtOrSingingHymnsUnbidden.is_valid( TillTheWorldIsWrought()) is False assert IsInTheLightOfThoughtOrSingingHymnsUnbidden.is_valid( InADellOfDew()) is False # Assert this object provides the expected representation. assert '|' in repr(IsInTheLightOfThoughtOrSingingHymnsUnbidden) def test_api_vale_isattr_fail() -> None: ''' Test unsuccessful usage of the :mod:`beartype.vale.IsAttr` factory. ''' # Defer test-specific imports. from beartype.roar import BeartypeValeSubscriptionException from beartype.vale import IsAttr, IsEqual from pytest import raises # Assert that subscripting this factory with the empty tuple raises the # expected exception. with raises(BeartypeValeSubscriptionException): IsAttr[()] # Assert that subscripting this factory with the empty tuple raises the # expected exception. with raises(BeartypeValeSubscriptionException): IsAttr[()] # Assert that subscripting this factory with one non-tuple argument raises # the expected exception. with raises(BeartypeValeSubscriptionException): IsAttr['Among the flowers and grass, which screen it from the view:'] # Assert that subscripting this factory with three or more arguments raises # the expected exception. with raises(BeartypeValeSubscriptionException): IsAttr[ "Like a rose embower'd", "In its own green leaves,", "By warm winds deflower'd,", ] # Assert that subscripting this factory with two arguments whose first # argument is *NOT* a string raises the expected exception. with raises(BeartypeValeSubscriptionException): IsAttr[ IsEqual['Till the scent it gives'], IsEqual[ 'Makes faint with too much sweet those heavy-winged thieves:']] # Assert that subscripting this factory with two arguments whose first # argument is the empty string raises the expected exception. with raises(BeartypeValeSubscriptionException): IsAttr['', IsEqual['Sound of vernal showers']] # Assert that subscripting this factory with two arguments whose first # argument is an invalid Python identifier raises the expected exception. with raises(BeartypeValeSubscriptionException): IsAttr['On the twinkling grass,', IsEqual["Rain-awaken'd flowers,"]] beartype-0.18.5/beartype_test/a00_unit/a40_api/vale/_is/test_valeisoper.py000066400000000000000000000102301461113517100264050ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype code-based operator data validation unit tests.** This submodule unit tests the subset of the public API of the :mod:`beartype.vale` subpackage defined by the private :mod:`beartype.vale._is._valeisoper` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ class : isequal }.................... def test_api_vale_isequal_pass() -> None: ''' Test successful usage of the :mod:`beartype.vale.IsEqual` factory. ''' # Defer test-specific imports. from beartype.vale import IsEqual from beartype.vale._core._valecore import BeartypeValidator # Arbitrary tuple objects with which to subscript the "IsEqual" class, # exercising edge cases in the __class_getitem__() dunder method. UNBODIED_JOY = ('Like an unbodied joy', 'whose race is just begun.') # Arbitrary hashable non-tuple objects with which to subscript the # "IsEqual" class, exercising edge cases in the __class_getitem__() dunder # method. KEEN_ARROWS = frozenset( {'Keen as are the arrows', 'Of that silver sphere,'}) # Arbitrary non-tuple object we don't particularly care about. HARDLY_SEE = ['Until we hardly see,', 'we feel that it is there.'] # Validator produced by subscripting the "IsEqual" class with exactly one # non-tuple object. IsUnbodiedJoy = IsEqual[UNBODIED_JOY] IsKeenArrows = IsEqual[KEEN_ARROWS] # Assert these validators satisfy the expected API. assert isinstance(IsUnbodiedJoy, BeartypeValidator) assert isinstance(IsKeenArrows, BeartypeValidator) # Assert these validators produce the same objects when subscripted by the # same arguments (and are thus memoized on subscripted arguments). assert IsUnbodiedJoy is IsEqual[UNBODIED_JOY] assert IsKeenArrows is IsEqual[KEEN_ARROWS] # Assert these validators accept the objects subscripting these validators. assert IsUnbodiedJoy.is_valid(UNBODIED_JOY) is True assert IsKeenArrows.is_valid(KEEN_ARROWS) is True # Assert these validators accept objects *NOT* subscripting these # validators but equal to the objects subscripting these validators. assert IsUnbodiedJoy.is_valid(UNBODIED_JOY[:]) is True assert IsKeenArrows.is_valid(KEEN_ARROWS.copy()) is True # Assert these validators reject objects unequal to the objects # subscripting these validators. assert IsUnbodiedJoy.is_valid(HARDLY_SEE) is False assert IsKeenArrows.is_valid(HARDLY_SEE) is False # Assert these validators have the expected representation. assert repr(UNBODIED_JOY) in repr(IsUnbodiedJoy) assert repr(KEEN_ARROWS) in repr(IsKeenArrows) # Validator synthesized from the above validators with the domain-specific # language (DSL) supported by these validators. IsUnbodiedJoyOrKeenArrows = IsUnbodiedJoy | IsKeenArrows # Assert this validator performs the expected validation. assert IsUnbodiedJoyOrKeenArrows.is_valid(UNBODIED_JOY[:]) is True assert IsUnbodiedJoyOrKeenArrows.is_valid(KEEN_ARROWS.copy()) is True assert IsUnbodiedJoyOrKeenArrows.is_valid(HARDLY_SEE) is False # Assert this validator provides the expected representation. assert '|' in repr(IsUnbodiedJoyOrKeenArrows) def test_api_vale_isequal_fail() -> None: ''' Test unsuccessful usage of the :mod:`beartype.vale.IsEqual` factory. ''' # Defer test-specific imports. from beartype.roar import BeartypeValeSubscriptionException from beartype.vale import IsEqual from pytest import raises # Assert that subscripting the "IsEqual" class with the empty tuple raises # the expected exception. with raises(BeartypeValeSubscriptionException): IsEqual[()] beartype-0.18.5/beartype_test/a00_unit/a40_api/vale/_util/000077500000000000000000000000001461113517100231715ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a40_api/vale/_util/__init__.py000066400000000000000000000000001461113517100252700ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a40_api/vale/_util/test_valeutiltext.py000066400000000000000000000043241461113517100273370ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype validator text utility unit tests.** This submodule unit tests the private :mod:`beartype.vale._util._valeutiltext` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ class : subscriptedis }.................... def test_api_vale_format_diagnosis_line() -> None: ''' Test successful usage of the private :func:`beartype.vale._util._valeutiltext.format_diagnosis_line` formatter. ''' # Defer test-specific imports. from beartype.vale._util._valeutiltext import format_diagnosis_line # Assert this formatter accepts a true boolean value. visit_the_soul_in_sleep = format_diagnosis_line( validator_repr='Some say that gleams of a remoter world', indent_level_outer=' ', indent_level_inner='', is_obj_valid=True, ) assert 'Some say that gleams of a remoter world' in visit_the_soul_in_sleep assert 'True' in visit_the_soul_in_sleep # Assert this formatter accepts a true boolean value. that_death_is_slumber = format_diagnosis_line( validator_repr='And that its shapes the busy thoughts outnumber', indent_level_outer=' ', indent_level_inner='', is_obj_valid=False, ) assert 'And that its shapes the busy thoughts outnumber' in ( that_death_is_slumber) assert 'False' in that_death_is_slumber # Assert this formatter accepts *NO* boolean value. i_look_on_high = format_diagnosis_line( validator_repr='Of those who wake and live.', indent_level_outer=' ', indent_level_inner='', ) assert 'Of those who wake and live.' in i_look_on_high assert 'True' not in i_look_on_high assert 'False' not in i_look_on_high beartype-0.18.5/beartype_test/a00_unit/a60_check/000077500000000000000000000000001461113517100214345ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a60_check/__init__.py000066400000000000000000000000001461113517100235330ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a60_check/a00_code/000077500000000000000000000000001461113517100230065ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a60_check/a00_code/__init__.py000066400000000000000000000000001461113517100251050ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a60_check/a00_code/snip/000077500000000000000000000000001461113517100237575ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a60_check/a00_code/snip/__init__.py000066400000000000000000000000001461113517100260560ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a60_check/a00_code/snip/test_codesnipcls.py000066400000000000000000000043501461113517100277000ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **type-checking expression snippet class unit tests.** This submodule unit tests the public API of the public :mod:`beartype._check.code.snip.codesnipcls.PITH_INDEX_TO_VAR_NAME` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_pith_index_to_var_name() -> None: ''' Test the :obj:`beartype._check.code.snip.codesnipcls.PITH_INDEX_TO_VAR_NAME` dictionary singleton. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype._check.code.snip.codesnipcls import PITH_INDEX_TO_VAR_NAME from beartype._check.checkmagic import VAR_NAME_PITH_PREFIX from pytest import raises # ....................{ PASS }.................... # Assert this dictionary indexed by various positive integers creates and # returns the expected local pith variable names. assert PITH_INDEX_TO_VAR_NAME[1] == f'{VAR_NAME_PITH_PREFIX}1' assert PITH_INDEX_TO_VAR_NAME[2] == f'{VAR_NAME_PITH_PREFIX}2' # Assert this dictionary internally caches these constants. assert PITH_INDEX_TO_VAR_NAME[1] is PITH_INDEX_TO_VAR_NAME[1] assert PITH_INDEX_TO_VAR_NAME[2] is PITH_INDEX_TO_VAR_NAME[2] # ....................{ FAIL }.................... # Assert that attempting to index this dictionary by non-integer indices # raises the expected exception. with raises(AssertionError): PITH_INDEX_TO_VAR_NAME[2.34] # Assert that attempting to index this dictionary by negative indices # raises the expected exception. with raises(AssertionError): PITH_INDEX_TO_VAR_NAME[-1] beartype-0.18.5/beartype_test/a00_unit/a60_check/a00_code/test_codemake.py000066400000000000000000000025231461113517100261710ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator PEP-compliant type-checking code generator unit tests.** This submodule unit tests the public API of the private :mod:`beartype._check.code.codemake` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ pass : check }.................... def test_make_check_code_decoration() -> None: ''' Test the :func:`beartype._check.code.codemake.make_check_expr` function. ''' # Defer test-specific imports. from beartype._check.code.codemake import make_check_expr from beartype._conf.confcls import BEARTYPE_CONF_DEFAULT # Assert this function generates identical code for identical hints and is # thus cached via memoization. assert ( make_check_expr(str, BEARTYPE_CONF_DEFAULT) is make_check_expr(str, BEARTYPE_CONF_DEFAULT) ) beartype-0.18.5/beartype_test/a00_unit/a60_check/a00_code/test_codescope.py000066400000000000000000000370431461113517100263720ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator PEP-compliant code wrapper scope utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._check.code.codescope` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ adder }.................... def test_add_func_scope_type() -> None: ''' Test the :func:`beartype._check.code.codescope.add_func_scope_type` adder. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar import BeartypeDecorHintPep3119Exception from beartype._cave._cavefast import NoneType, RegexCompiledType from beartype._check.code.codescope import add_func_scope_type from beartype._util.utilobject import get_object_type_basename from beartype_test.a00_unit.data.data_type import NonIsinstanceableClass from pytest import raises # ....................{ LOCALS }.................... # Arbitrary scope to be added to below. func_scope = {} # Tuple of arbitrary non-builtin types. TYPES_NONBUILTIN = ( # Adding a non-builtin type. RegexCompiledType, # Readding that same type. RegexCompiledType, # Adding the type of the "None" singleton (despite technically being # listed as belonging to the "builtin" module) under a unique name # rather than its unqualified basename "NoneType" (which doesn't # actually exist, which is inconsistent nonsense, but whatever). NoneType, ) # ....................{ PASS }.................... # Assert this function supports non-builtin types. for cls in TYPES_NONBUILTIN: cls_scope_name = add_func_scope_type(cls=cls, func_scope=func_scope) assert cls_scope_name != get_object_type_basename(cls) assert func_scope[cls_scope_name] is cls # Assert this function does *NOT* add builtin types but instead simply # returns the unqualified basenames of those types. cls = list cls_scope_name = add_func_scope_type(cls=cls, func_scope=func_scope) assert cls_scope_name == get_object_type_basename(cls) assert cls_scope_name not in func_scope # ....................{ FAIL }.................... # Arbitrary scope to be added to below. func_scope = {} # Assert this function raises the expected exception for non-types. with raises(BeartypeDecorHintPep3119Exception): add_func_scope_type( cls=( 'The best lack all conviction, while the worst', 'Are full of passionate intensity', ), func_scope=func_scope, ) # Assert this function raises the expected exception for PEP 560-compliant # classes whose metaclasses define an __instancecheck__() dunder method to # unconditionally raise exceptions. with raises(BeartypeDecorHintPep3119Exception): add_func_scope_type(cls=NonIsinstanceableClass, func_scope=func_scope) def test_add_func_scope_types() -> None: ''' Test the :func:`beartype._check.code.codescope.add_func_scope_types` adder. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar import ( BeartypeDecorHintNonpepException, BeartypeDecorHintPep3119Exception, ) from beartype._cave._cavefast import CallableTypes, ModuleOrStrTypes from beartype._cave._cavemap import NoneTypeOr from beartype._check.code.codescope import add_func_scope_types from beartype._util.utilobject import get_object_type_basename from beartype_test.a00_unit.data.check.forward.data_fwdref import ( FORWARDREF_ABSOLUTE, FORWARDREF_RELATIVE, ) from beartype_test.a00_unit.data.data_type import ( Class, NonIsinstanceableClass, ) from pytest import raises # ....................{ LOCALS }.................... # Arbitrary scope to be added to below. func_scope = {} # Arbitrary tuple of one or more standard types. types = CallableTypes # ....................{ PASS }.................... # Assert this function adds a tuple of one or more standard types. # # Note that, unlike types, tuples are internally added under different # objects than their originals (e.g., to ignore both duplicates and # ordering) and *MUST* thus be tested by conversion to sets. types_scope_name = add_func_scope_types( types=types, func_scope=func_scope) assert set(types) == set(func_scope[types_scope_name]) # Assert this function readds the same tuple as well. types_scope_name_again = add_func_scope_types( types=types, func_scope=func_scope) assert types_scope_name == types_scope_name_again # Assert this function adds a frozenset of one or more standard types. types = frozenset(ModuleOrStrTypes) types_scope_name = add_func_scope_types( types=types, func_scope=func_scope) assert set(types) == set(func_scope[types_scope_name]) # Assert this function does *NOT* add tuples of one non-builtin types but # instead simply returns the unqualified basenames of those types. types = (int,) types_scope_name = add_func_scope_types( types=types, func_scope=func_scope) assert types_scope_name == get_object_type_basename(types[0]) assert types_scope_name not in func_scope # Assert this function adds tuples of one non-builtin type as merely that # type rather than that tuple. types = (Class,) types_scope_name = add_func_scope_types(types=types, func_scope=func_scope) assert func_scope[types_scope_name] is Class # Assert this function adds tuples containing duplicate types as tuples # containing only the proper subset of non-duplicate types. types = (Class,)*3 types_scope_name = add_func_scope_types(types=types, func_scope=func_scope) assert func_scope[types_scope_name] == (Class,) # Assert this function registers tuples containing *NO* duplicate types. types = NoneTypeOr[CallableTypes] types_scope_name = add_func_scope_types( types=types, func_scope=func_scope, is_unique=True) assert func_scope[types_scope_name] == types # Assert this function registers tuples containing the same types in # different orders to differing objects and thus preserving ordering. types_a = (int, str,) types_b = (str, int,) types_scope_name_a = add_func_scope_types( types=types_a, func_scope=func_scope, is_unique=True) types_scope_name_b = add_func_scope_types( types=types_b, func_scope=func_scope, is_unique=True) assert func_scope[types_scope_name_a] == types_a assert func_scope[types_scope_name_b] == types_b assert func_scope[types_scope_name_a] != func_scope[types_scope_name_b] # ....................{ PASS ~ forwardref }.................... # Assert that this function implicitly reorders tuples containing: # * One or more forward reference proxies. # * One or more standard classes that are *NOT* forward reference proxies. # # Notably, assert that this function reorders all standard classes *BEFORE* # all proxies to reduce the likelihood of raising exceptions during # isinstance()-based type checks whose second arguments are such tuples. types = ( FORWARDREF_ABSOLUTE, FORWARDREF_RELATIVE, str, int, ) types_scope_name = add_func_scope_types( types=types, func_scope=func_scope, is_unique=False) added_types = func_scope[types_scope_name] assert set(added_types[:2]) == {str, int} assert set(added_types[2:]) == { FORWARDREF_ABSOLUTE, FORWARDREF_RELATIVE, } # Repeat the same operation but with the "is_unique=False" parameter. types_scope_name = add_func_scope_types( types=types, func_scope=func_scope, is_unique=True) added_types = func_scope[types_scope_name] expected_types = ( str, int, FORWARDREF_ABSOLUTE, FORWARDREF_RELATIVE, ) assert added_types == expected_types # ....................{ FAIL }.................... # Arbitrary scope to be added to below. func_scope = {} # Assert this function raises the expected exception for non-tuples. with raises(BeartypeDecorHintNonpepException): add_func_scope_types( types='\n'.join(( 'I will arise and go now, and go to Innisfree,', 'And a small cabin build there, of clay and wattles made;', 'Nine bean-rows will I have there, a hive for the honey-bee,', 'And live alone in the bee-loud glade.', )), func_scope=func_scope, ) # Assert this function raises the expected exception for empty tuples. with raises(BeartypeDecorHintNonpepException): add_func_scope_types(types=(), func_scope=func_scope) # Assert this function raises the expected exception for unhashable objects # embedded in an otherwise hashable tuple. with raises(BeartypeDecorHintPep3119Exception): add_func_scope_types( types=( int, str, { 'Had': "I the heaven’s embroidered cloths,", 'Enwrought': "with golden and silver light,", 'The': 'blue and the dim and the dark cloths', 'Of': 'night and light and the half-light,', 'I': 'would spread the cloths under your feet:', 'But': 'I, being poor, have only my dreams;', 'I have': 'spread my dreams under your feet;', 'Tread': 'softly because you tread on my dreams.', }, ), func_scope=func_scope, ) # Assert this function raises the expected exception for tuples containing # one or more PEP 560-compliant classes whose metaclasses define an # __instancecheck__() dunder method to unconditionally raise exceptions. with raises(BeartypeDecorHintPep3119Exception): add_func_scope_types( types=(bool, NonIsinstanceableClass, float), func_scope=func_scope) # ....................{ TESTS ~ expresser : type }.................... def test_express_func_scope_type_ref() -> None: ''' Test the :func:`beartype._check.code.codescope.express_func_scope_type_ref` function. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar import BeartypeDecorHintForwardRefException from beartype.typing import ForwardRef from beartype._check.code.codescope import express_func_scope_type_ref from pytest import raises # ....................{ LOCALS }.................... # Arbitrary scope to be added to below. func_scope = {} # Set of the unqualified classnames referred to by all relative forward # references relative to this scope if any *OR* "None" otherwise (i.e., if # no such references have been expressed relative to this scope yet). forwardrefs_class_basename = None # Fully-qualified classname of a non-existing class. CLASSNAME_QUALIFIED = 'Thy.giant.brood.of.pines.around.thee.clinging' # Unqualified classname of a non-existing class. CLASSNAME_UNQUALIFIED = 'Children_of_elder_time_in_whose_devotion' # Tuple of all PEP-compliant forward references to this fully-qualified # class, including... FORWARDREFS_QUALIFIED = ( # PEP 484-compliant forward reference to this class. ForwardRef(CLASSNAME_QUALIFIED), # PEP 585-compliant forward reference to this class. CLASSNAME_QUALIFIED, ) # Tuple of all PEP-compliant forward references to this unqualified class, # including... FORWARDREFS_UNQUALIFIED = ( # PEP 484-compliant forward reference to this class. ForwardRef(CLASSNAME_UNQUALIFIED), # PEP 585-compliant forward reference to this class. CLASSNAME_UNQUALIFIED, ) # ....................{ PASS }.................... # For each absolute forward reference... for forwardref_qualified in FORWARDREFS_QUALIFIED: # Express a fully-qualified forward reference to a non-existing class. forwardref_expr, forwardrefs_class_basename = ( express_func_scope_type_ref( forwardref=forwardref_qualified, forwardrefs_class_basename=forwardrefs_class_basename, func_scope=func_scope, )) # Assert this set remains empty. assert forwardrefs_class_basename is None # Forward reference proxy added to this scope by the above call. forwardref = func_scope[forwardref_expr] # Assert this proxy encapsulates this forward reference. assert CLASSNAME_QUALIFIED in repr(forwardref) # Assert this function rexpresses the same forward reference. forwardref_expr_again, forwardrefs_class_basename_again = ( express_func_scope_type_ref( forwardref=forwardref_qualified, forwardrefs_class_basename=forwardrefs_class_basename, func_scope=func_scope, )) assert forwardref_expr_again == forwardref_expr assert forwardrefs_class_basename_again is forwardrefs_class_basename # For each relative forward reference... for forwardref_unqualified in FORWARDREFS_UNQUALIFIED: # Express an unqualified forward reference to a non-existing class. forwardref_expr, forwardrefs_class_basename = ( express_func_scope_type_ref( forwardref=forwardref_unqualified, forwardrefs_class_basename=forwardrefs_class_basename, func_scope=func_scope, )) # Assert this expression references this class. assert CLASSNAME_UNQUALIFIED in forwardref_expr # Assert this set now contains only this classname. assert forwardrefs_class_basename == {CLASSNAME_UNQUALIFIED,} # Assert this function rexpresses the same forward reference. forwardref_expr_again, forwardrefs_class_basename_again = ( express_func_scope_type_ref( forwardref=forwardref_unqualified, forwardrefs_class_basename=forwardrefs_class_basename, func_scope=func_scope, )) assert forwardref_expr_again == forwardref_expr assert forwardrefs_class_basename_again == {CLASSNAME_UNQUALIFIED,} # ....................{ FAIL }.................... # Assert this function raises the expected exception for arbitrary objects # that are *NOT* forward references. with raises(BeartypeDecorHintForwardRefException): express_func_scope_type_ref( forwardref=b'The chainless winds still come and ever came', forwardrefs_class_basename=forwardrefs_class_basename, func_scope=func_scope, ) beartype-0.18.5/beartype_test/a00_unit/a60_check/a20_convert/000077500000000000000000000000001461113517100235565ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a60_check/a20_convert/__init__.py000066400000000000000000000000001461113517100256550ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a60_check/a20_convert/test_convcoerce.py000066400000000000000000000115201461113517100273140ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **PEP-agnostic type hint conversion utility unit tests.** This submodule unit tests the public API of the private :mod:`beartype._check.convert.convcoerce` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ coercers }.................... def test_coerce_func_hint_root() -> None: ''' Test the private :func:`beartype._check.convert.convcoerce.coerce_func_hint_root` coercer. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype import BeartypeConf from beartype._check.checkcall import BeartypeCall from beartype._check.convert.convcoerce import coerce_func_hint_root from beartype._data.func.datafuncarg import ARG_NAME_RETURN # ..................{ CALLABLES }.................. def one_legion_of_wild_thoughts() -> str: ''' Arbitrary callable with which to test this coercer. ''' return 'whose wandering wings' # Beartype dataclass describing this callable. bear_call = BeartypeCall() bear_call.reinit(func=one_legion_of_wild_thoughts, conf=BeartypeConf()) # ..................{ CORE }.................. # Assert this coercer preserves an isinstanceable type as is. assert coerce_func_hint_root( hint=str, pith_name=ARG_NAME_RETURN, bear_call=bear_call, exception_prefix='', ) is str # ..................{ PEP 585 }.................. # Note that the PEP 585-specific logic implemented by this coercer is # tested by the test_coerce_hint_any() unit test below. # ..................{ MYPY }.................. # Note that the mypy-specific logic implemented by this coercer is # tested by the "a00_unit.a90_decor.code.nonpep.test_codemypy" submodule. # ..................{ NON-PEP }.................. # Note that the tuple union-specific logic implemented by this coercer is # tested by... something else, presumably? *sigh* def test_coerce_hint_any() -> None: ''' Test the private :func:`beartype._check.convert.convcoerce.coerce_hint_any` coercer. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype._check.convert.convcoerce import coerce_hint_any from beartype._util.py.utilpyversion import ( IS_PYTHON_AT_LEAST_3_10, IS_PYTHON_AT_LEAST_3_9, ) # ..................{ CORE }.................. # Assert this coercer preserves an isinstanceable type as is. assert coerce_hint_any(int) is int # ..................{ PEP 585 }.................. # If the active Python interpreter targets Python >= 3.9 and thus supports # PEP 585... if IS_PYTHON_AT_LEAST_3_9: # Arbitrary PEP 585-compliant type hint. hint_pep585 = list[int] # Assert this coercer preserves the first passed instance of a PEP # 585-compliant type hint as is. assert coerce_hint_any(hint_pep585) is hint_pep585 # Assert this coercer returns the first passed instance of a PEP # 585-compliant type hint when passed a copy of that instance. PEP # 585-compliant type hints are *NOT* self-caching: e.g., # >>> list[int] is list[int] # False assert coerce_hint_any(list[int]) is hint_pep585 # ..................{ PEP 604 }.................. # If the active Python interpreter targets Python >= 3.10 and thus supports # PEP 604... if IS_PYTHON_AT_LEAST_3_10: # Arbitrary PEP 604-compliant union. hint_pep604 = int | str | None # Assert this coercer preserves the first passed instance of a PEP # 604-compliant union as is. assert coerce_hint_any(hint_pep604) is hint_pep604 # Assert this coercer returns the first passed instance of a PEP # 604-compliant type hint when passed a copy of that instance. PEP # 604-compliant type hints are *NOT* self-caching: e.g., # >>> int | str is int | str # False assert coerce_hint_any(int | str | None) is hint_pep604 beartype-0.18.5/beartype_test/a00_unit/a60_check/a20_convert/test_convreduce.py000066400000000000000000000170321461113517100273270ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **PEP-agnostic type hint conversion utility** unit tests. This submodule unit tests the public API of the private :mod:`beartype._check.convert.convreduce` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ reducers }.................... def test_reduce_hint() -> None: ''' Test the private :func:`beartype._check.convert.convreduce.reduce_hint` reducer. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype.roar import ( BeartypeDecorHintNonpepNumpyException, BeartypeDecorHintNonpepNumpyWarning, ) from beartype.vale import IsEqual from beartype._cave._cavefast import NoneType from beartype._check.convert.convreduce import reduce_hint from beartype._conf.confcls import ( BEARTYPE_CONF_DEFAULT, BeartypeConf, ) from beartype._data.hint.datahinttyping import ( Pep484TowerComplex, Pep484TowerFloat, ) from beartype._data.hint.pep.sign.datapepsigns import HintSignAnnotated from beartype._util.hint.pep.proposal.utilpep593 import is_hint_pep593 from beartype._util.hint.pep.utilpepget import get_hint_pep_sign from beartype_test.a00_unit.data.hint.pep.proposal.data_pep484 import ( PEP484_GENERICS_IO, T, T_BOUNDED, T_CONSTRAINED, ) from beartype_test._util.module.pytmodtyping import ( import_typing_attr_or_none_safe) from beartype_test._util.module.pytmodtest import ( is_package_numpy, is_package_numpy_typing_ndarray_deep, ) from dataclasses import InitVar from pytest import raises, warns from typing import Protocol # ..................{ LOCALS }.................. # Keyword arguments to be passed to all calls to reduce_hints() below. kwargs = { 'conf': BEARTYPE_CONF_DEFAULT, } # ..................{ CORE }.................. # Assert this reducer preserves an isinstanceable type as is. assert reduce_hint(hint=int, **kwargs) is int # Assert this reducer reduces "None" to "type(None)". assert reduce_hint(hint=None, **kwargs) is NoneType # ..................{ PEP 484 ~ tower }.................. # Assert this reducer expands the builtin "float" and "complex" types to # their corresponding numeric towers when configured to do so. assert reduce_hint( hint=float, conf=BeartypeConf(is_pep484_tower=True), ) is Pep484TowerFloat assert reduce_hint( hint=complex, conf=BeartypeConf(is_pep484_tower=True), ) is Pep484TowerComplex # Assert this reducer preserves the builtin "float" and "complex" types as # is when configured to disable the implicit numeric tower. assert reduce_hint( hint=float, conf=BeartypeConf(is_pep484_tower=False), ) is float assert reduce_hint( hint=complex, conf=BeartypeConf(is_pep484_tower=False), ) is complex # ..................{ PEP 484 ~ typevar }.................. # Assert this reducer preserves unbounded type variables as is. assert reduce_hint(hint=T, **kwargs) is T # Assert this reducer reduces bounded type variables to their upper bound. assert reduce_hint(hint=T_BOUNDED, **kwargs) is int # Union of all constraints parametrizing a constrained type variable, # reduced from that type variable. typevar_constraints_union = reduce_hint(hint=T_CONSTRAINED, **kwargs) # Assert this union contains all constraints parametrizing this variable. assert str in typevar_constraints_union.__args__ assert bytes in typevar_constraints_union.__args__ # ..................{ PEP 544 }.................. # For each PEP 484-compliant "typing" IO generic superclass... for pep484_generic_io in PEP484_GENERICS_IO: # Equivalent protocol reduced from this generic. pep544_protocol_io = reduce_hint(hint=pep484_generic_io, **kwargs) # Assert this protocol is either... assert ( # A PEP 593-compliant type metahint generalizing a protocol # *OR*... is_hint_pep593(pep544_protocol_io) or # A PEP 544-compliant protocol. issubclass(pep544_protocol_io, Protocol) ) # ..................{ PEP 557 }.................. # Assert this reducer reduces an "InitVar" to its subscripted argument. assert reduce_hint(hint=InitVar[str], **kwargs) is str # ..................{ PEP 593 }.................. # "typing.Annotated" type hint factory imported from either the "typing" or # "typing_extensions" modules if importable *OR* "None" otherwise. Annotated = import_typing_attr_or_none_safe('Annotated') # If the "typing.Annotated" type hint factory is importable, the active # Python interpreter supports PEP 593. In this case... if Annotated is not None: # Assert this reducer reduces a beartype-agnostic metahint to the # lower-level hint it annotates. assert reduce_hint(hint=Annotated[int, 42], **kwargs) is int # Assert this reducer preserves a beartype-specific metahint as is. leaves_when_laid = Annotated[str, IsEqual['In their noonday dreams.']] assert reduce_hint(hint=leaves_when_laid, **kwargs) is leaves_when_laid # ..................{ NUMPY }.................. # If a recent version of NumPy is importable... if is_package_numpy(): # Defer third party imports. from numpy import float64, ndarray from numpy.typing import NDArray # If beartype deeply supports "numpy.typing.NDArray" type hints under # the active Python interpreter... if is_package_numpy_typing_ndarray_deep(): # Beartype validator reduced from such a hint. ndarray_reduced = reduce_hint(hint=NDArray[float64], **kwargs) # Assert this validator is a "typing{_extensions}.Annotated" hint. assert get_hint_pep_sign(ndarray_reduced) is HintSignAnnotated # Assert that reducing a "numpy.typing.NDArray" type hint # erroneously subscripted by an object *NOT* reducible to a data # type raises the expected exception. with raises(BeartypeDecorHintNonpepNumpyException): reduce_hint( hint=NDArray['From_my_wings_are_shaken_the_dews_that_waken'], **kwargs ) # Else, beartype only shallowly supports "numpy.typing.NDArray" type # hints under the active Python interpreter. In this case... else: # Assert this reducer reduces such a hint to the untyped NumPy # array class "numpy.ndarray" with a non-fatal warning. with warns(BeartypeDecorHintNonpepNumpyWarning): assert reduce_hint(hint=NDArray[float64], **kwargs) is ndarray beartype-0.18.5/beartype_test/a00_unit/a60_check/a80_forward/000077500000000000000000000000001461113517100235505ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a60_check/a80_forward/__init__.py000066400000000000000000000000001461113517100256470ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a60_check/a80_forward/reference/000077500000000000000000000000001461113517100255065ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a60_check/a80_forward/reference/__init__.py000066400000000000000000000000001461113517100276050ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a60_check/a80_forward/reference/test_fwdrefmake.py000066400000000000000000000146071461113517100312420ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype decorator **forward reference factory** unit tests. This submodule unit tests the :func:`beartype._check.forward.reference.fwdrefmake` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_make_forwardref_indexable_subtype() -> None: ''' Test the :func:`beartype._check.forward.reference.fwdrefmake.make_forwardref_indexable_subtype` factory. ''' # ....................{ LOCALS }.................... # Defer test-specific imports. from beartype.roar import ( BeartypeCallHintForwardRefException, BeartypeDecorHintForwardRefException, ) from beartype_test.a00_unit.data.check.forward.data_fwdref import ( CLASS_BASENAME, CLASS_NAME, FORWARDREF_ABSOLUTE, FORWARDREF_MODULE_ABSOLUTE, FORWARDREF_MODULE_CLASS, FORWARDREF_RELATIVE, FORWARDREF_RELATIVE_CIRCULAR, MODULE_BASENAME, MODULE_NAME, PACKAGE_NAME, SCOPE_NAME, ) from beartype_test.a00_unit.data.data_type import ( Class, Subclass, ) from pytest import raises # ....................{ LOCALS }.................... # Arbitrary instance of a subclass of that class. obj_subclass = Subclass() # ....................{ PASS ~ attr }.................... # Assert that these proxies have the expected class names. assert FORWARDREF_ABSOLUTE.__name__ == CLASS_BASENAME assert FORWARDREF_RELATIVE.__name__ == CLASS_BASENAME assert FORWARDREF_MODULE_ABSOLUTE.__name__ == MODULE_BASENAME assert FORWARDREF_MODULE_CLASS.__name__ == CLASS_BASENAME # Assert that these proxies have the expected module names. assert FORWARDREF_ABSOLUTE.__module__ == MODULE_NAME assert FORWARDREF_RELATIVE.__module__ == MODULE_NAME assert FORWARDREF_MODULE_ABSOLUTE.__module__ == PACKAGE_NAME assert FORWARDREF_MODULE_CLASS.__module__ == f'{PACKAGE_NAME}.{MODULE_BASENAME}' # Assert that these proxies have the expected hint names. assert FORWARDREF_ABSOLUTE.__name_beartype__ == CLASS_NAME assert FORWARDREF_RELATIVE.__name_beartype__ == CLASS_BASENAME assert FORWARDREF_MODULE_ABSOLUTE.__name_beartype__ == MODULE_NAME assert FORWARDREF_MODULE_CLASS.__name_beartype__ == CLASS_NAME # Assert that these proxies have the expected scope names. assert FORWARDREF_ABSOLUTE.__scope_name_beartype__ == SCOPE_NAME assert FORWARDREF_RELATIVE.__scope_name_beartype__ == MODULE_NAME assert FORWARDREF_MODULE_ABSOLUTE.__scope_name_beartype__ == SCOPE_NAME assert FORWARDREF_MODULE_CLASS.__scope_name_beartype__ == SCOPE_NAME # ....................{ PASS ~ check }.................... # Assert that an arbitrary instance of a subclass of that class is also an # instance of both of these proxies. assert isinstance(obj_subclass, FORWARDREF_ABSOLUTE) assert isinstance(obj_subclass, FORWARDREF_RELATIVE) assert isinstance(obj_subclass, FORWARDREF_MODULE_CLASS) # Assert that subclass is also a subclass of both of these proxies. assert issubclass(Subclass, FORWARDREF_ABSOLUTE) assert issubclass(Subclass, FORWARDREF_RELATIVE) assert issubclass(Subclass, FORWARDREF_MODULE_CLASS) # ....................{ PASS ~ property }.................... # Assert that this property of these forward reference proxies has the # expected values. assert FORWARDREF_ABSOLUTE.__type_beartype__ is Class assert FORWARDREF_RELATIVE.__type_beartype__ is Class assert FORWARDREF_MODULE_CLASS.__type_beartype__ is Class # ....................{ PASS ~ repr }.................... # Machine-readable representation of a forward reference proxy. FORWARDREF_REPR = repr(FORWARDREF_ABSOLUTE) # Tuple of one or more substrings expected to be in this representation. FORWARDREF_REPR_SUBSTRS = ( # Unqualified basename of the type this forward reference proxies. FORWARDREF_ABSOLUTE.__name__, # Machine-readable representations of all class variables of all # unsubscripted forward reference proxies. repr(FORWARDREF_ABSOLUTE.__scope_name_beartype__), repr(FORWARDREF_ABSOLUTE.__name_beartype__), ) # Assert that this representation contains the expected substrings. for fwdref_repr_substr in FORWARDREF_REPR_SUBSTRS: assert fwdref_repr_substr in FORWARDREF_REPR # ....................{ FAIL }.................... # Assert that attempting to access an undefined dunder attribute of a # forward reference proxy raises the expected exception. with raises(AttributeError): FORWARDREF_ABSOLUTE.__the_beating_of_her_heart_was_heard_to_fill__ # Assert that attempting to instantiate a forward reference proxy raises the # expected exception. with raises(BeartypeDecorHintForwardRefException): FORWARDREF_ABSOLUTE() # Assert that attempting to validate a forward reference proxy to a module # as either an instance or subclass of a type raises the expected exception. with raises(BeartypeCallHintForwardRefException): isinstance(obj_subclass, FORWARDREF_MODULE_ABSOLUTE) with raises(BeartypeCallHintForwardRefException): issubclass(Subclass, FORWARDREF_MODULE_ABSOLUTE) # Assert that attempting to test an arbitrary object as an instance of a # circular forward reference proxy raises the expected exception. with raises(BeartypeCallHintForwardRefException): isinstance(obj_subclass, FORWARDREF_RELATIVE_CIRCULAR) # Assert that attempting to test an arbitrary type as a subclass of a # circular forward reference proxy raises the expected exception. with raises(BeartypeCallHintForwardRefException): issubclass(Subclass, FORWARDREF_RELATIVE_CIRCULAR) beartype-0.18.5/beartype_test/a00_unit/a60_check/a90_door/000077500000000000000000000000001461113517100230505ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a60_check/a90_door/test_checkdoor.py000066400000000000000000000510621461113517100264260ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype Decidedly Object-Oriented Runtime-checking (DOOR) type-checking unit tests.** This submodule unit tests the subset of the public API of the public :mod:`beartype.door` subpackage that pertains to type-checking. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype_test._util.mark.pytmark import ignore_warnings # ....................{ TESTS ~ warnings }.................... #FIXME *INCREDIBLY FRAGILE.* This test has unintended ordering issues with other #tests due to memoization. If another test runs first and passes the #is_bearable() tester these type hints and thus emits these warnings, this test #will re-pass these same hints to that tester, *WHICH WILL FAIL TO EMIT THESE #WARNINGS DUE TO MEMOIZATION.* # #Ergo, the only safe means of performing this test is to ensure that this test #internally clears all relevant beartype caches *BEFORE* performing any work. To #the best of our understanding, the only relevant beartype cache is that of our #core beartype._check.code.codemake.make_check_expr() function. Let us consider #how to sanitize this, please. Hmm... Actually, we're pretty sure there are more #than one relevant caches. This is probably extremely non-trivial. The only #other alternative would be for the @callable_cached decorator to append each #cache it creates to a giant global private list somewhere, which this unit test #would then iterate over and clear each item of. Yeah. *sigh* # #Incidentally, this also explains why the iter_hints_piths_meta() closure fails. #Why? Because that closure internally iterates *TWICE* over each hint. Clearly, #the first use of each deprecated hint emits a deprecation warning, which is #successfully caught; the second use then fails to do so, due to memoization. # #For the moment, we simply forcefully define this test *BEFORE* all other tests #calling the is_bearable() tester. This works... for now, but will absolutely #stop working when an earlier run test accidentally calls that tester. If you #are reading this, that probably already happened, huh? I feel your pain. #Really, I do. I did nothing about it... but I feel it, nonetheless. def test_door_is_bearable_warnings(hints_meta) -> None: ''' Test the :class:`beartype.door.is_bearable` tester function with respect to non-fatal warnings emitted by that tester when passed various problematic (e.g., deprecated) type hints. Note that this test *cannot* be folded into the comparable :func:`.test_door_is_bearable` test. Why? Because that test (and almost all other tests testing type hints) iterates over type hints by calling the session-scoped ``iter_hints_piths_meta()`` closure iterator. For unknown reasons, :mod:`pytest` fails to capture deprecation warnings emitted from that iterator. This is almost certainly a :mod:`pytest` issue. However, reporting this issue would require reducing this issue to a minimal reproducible working example -- which appears to be infeasible. In short, this test manually iterates over type hints as an acceptable (albeit annoying) alternative that allows deprecation warnings to be captured. Parameters ---------- hints_meta : List[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintNonpepMetadata] List of PEP-agnostic type hint metadata describing sample PEP-agnostic type hints exercising edge cases in the :mod:`beartype` codebase. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype.door import is_bearable from pytest import warns from warnings import simplefilter # ....................{ SETUP }.................... # Force pytest to temporarily allow deprecation warnings to be caught by the # warns() context manager for the duration of this test. By default, pytest # simply "passes through" all deprecation warnings for subsequent reporting # if tests otherwise successfully pass. Deprecation warnings include: # * "DeprecationWarning". # * "FutureWarning". # * "PendingDeprecationWarning". simplefilter('always') # ..................{ PASS }.................. # For each predefined type hint and associated metadata... for hint_meta in hints_meta: # If it is *NOT* the case that... if not ( # This hint is currently supported *AND*... hint_meta.is_supported and # This is type-checkable against at least one object. hint_meta.piths_meta # Then this hint is ignorable. Silently continue to the next. ): continue # Else, this hint is currently supported. # Type hint to be type-checked. hint = hint_meta.hint # Beartype dataclass configuring this type-check. conf = hint_meta.conf # Object to be type-checked against this hint, arbitrarily selected from # the iterable of all such objects supplied with this hint. By the above # validation, this is guaranteed to be non-empty. pith = hint_meta.piths_meta[0] # If this tester is expected to emit a warning for this hint... if hint_meta.warning_type is not None: # Call this tester under a context manager asserting this tester to # emit the expected warning. with warns(hint_meta.warning_type): is_bearable(pith, hint, conf=conf) # print(f'Deprecated type hint {repr(hint)} warned!') # Else, this tester is expected to emit *NO* warning for this hint. In # this case, call this tester outside of such a context manager. else: is_bearable(pith, hint, conf=conf) # is_bearable(pith, hint, conf=conf) # ....................{ TESTS ~ raisers }.................... # Prevent pytest from capturing and displaying all expected non-fatal # beartype-specific warnings emitted by this test. Urgh! @ignore_warnings(DeprecationWarning) def test_door_die_if_unbearable(iter_hints_piths_meta) -> None: ''' Test the :class:`beartype.door.die_if_unbearable` raiser function. Parameters ---------- iter_hints_piths_meta : Callable[[], Iterable[beartype_test.a00_unit.data.hint.util.data_hintmetautil.HintPithMetadata]] Factory function creating and returning a generator iteratively yielding ``HintPithMetadata`` instances, each describing a sample type hint exercising an edge case in the :mod:`beartype` codebase paired with a related object either satisfying or violating that hint. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.door import die_if_unbearable from beartype.roar import ( BeartypeConfException, BeartypeDecorHintNonpepException, BeartypeDoorHintViolation, ) from beartype._util.text.utiltextrepr import represent_object from beartype_test.a00_unit.data.hint.util.data_hintmetacls import ( HintPithUnsatisfiedMetadata) from pytest import raises # ....................{ PASS }.................... # For each predefined type hint and associated metadata... for hint_pith_meta in iter_hints_piths_meta(): # Type hint to be type-checked. hint = hint_pith_meta.hint_meta.hint # Beartype dataclass configuring this type-check. conf = hint_pith_meta.hint_meta.conf # Object to type-check against this type hint. pith = hint_pith_meta.pith # If this pith violates this hint... if isinstance(hint_pith_meta.pith_meta, HintPithUnsatisfiedMetadata): # Assert this raiser raises the expected exception when passed this # pith and hint. with raises(BeartypeDoorHintViolation) as exception_info: die_if_unbearable(pith, hint, conf=conf) # Exception message raised by this wrapper function. exception_message = str(exception_info.value) # Truncated representation of this pith. pith_repr = represent_object(pith) # Assert that this message contains a truncated representation of # this pith. assert pith_repr in exception_message # Assert that this raiser successfully replaced the temporary # placeholder previously prefixing this message. assert 'die_if_unbearable() value ' in exception_message.lower() assert ' violates type hint ' in exception_message # Else, this raiser satisfies this hint. In this case... else: # Assert this validator raises *NO* exception when passed this pith # and hint. die_if_unbearable(pith, hint, conf=conf) # ....................{ FAIL }.................... # Assert this tester raises the expected exception when passed an invalid # object as the type hint. with raises(BeartypeDecorHintNonpepException): die_if_unbearable( obj='Holds every future leaf and flower; the bound', hint=b'With which from that detested trance they leap;', ) # Assert this tester raises the expected exception when passed an invalid # object as the beartype configuration. with raises(BeartypeConfException): die_if_unbearable( obj='The torpor of the year when feeble dreams', hint=str, conf='Visit the hidden buds, or dreamless sleep', ) # See above for @ignore_warnings() discussion. @ignore_warnings(DeprecationWarning) def test_door_typehint_die_if_unbearable(iter_hints_piths_meta) -> None: ''' Test the :meth:`beartype.door.TypeHint.die_if_unbearable` raiser method. This test intentionally tests only the core functionality of this tester to avoid violating Don't Repeat Yourself (DRY). This tester internally defers to the procedural :class:`beartype.door.die_if_unbearable` tester, already exhaustively tested by preceding unit tests. Parameters ---------- iter_hints_piths_meta : Callable[[], Iterable[beartype_test.a00_unit.data.hint.util.data_hintmetautil.HintPithMetadata]] Factory function creating and returning a generator iteratively yielding ``HintPithMetadata`` instances, each describing a sample type hint exercising an edge case in the :mod:`beartype` codebase paired with a related object either satisfying or violating that hint. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.door import TypeHint from beartype.roar import ( BeartypeDoorHintViolation, BeartypeDoorNonpepException, ) from beartype_test.a00_unit.data.hint.util.data_hintmetacls import ( HintPithUnsatisfiedMetadata) from contextlib import suppress from pytest import raises # ....................{ PASS }.................... # For each predefined unignorable type hint and associated metadata... for hint_pith_meta in iter_hints_piths_meta(): # Type hint to be type-checked. hint = hint_pith_meta.hint_meta.hint # Beartype dataclass configuring this type-check. conf = hint_pith_meta.hint_meta.conf # Object to type-check against this type hint. pith = hint_pith_meta.pith #FIXME: Remove this suppression *AFTER* improving "TypeHint" to support #all currently unsupported type hints. with suppress(BeartypeDoorNonpepException): # Wrapper wrapping this type hint. typehint = TypeHint(hint) # If this pith violates this hint, assert this raiser raises the # expected exception when passed this pith and hint. if isinstance( hint_pith_meta.pith_meta, HintPithUnsatisfiedMetadata): with raises(BeartypeDoorHintViolation): typehint.die_if_unbearable(pith, conf=conf) # Else, this pith satisfies this hint. In this case, assert this # raiser raises *NO* exception when passed this pith and hint. else: typehint.die_if_unbearable(pith, conf=conf) # ....................{ TESTS ~ testers }.................... # See above for @ignore_warnings() discussion. @ignore_warnings(DeprecationWarning) def test_door_is_bearable(iter_hints_piths_meta, hints_ignorable) -> None: ''' Test the :class:`beartype.door.is_bearable` tester function. Parameters ---------- iter_hints_piths_meta : Callable[[], Iterable[beartype_test.a00_unit.data.hint.util.data_hintmetautil.HintPithMetadata]] Factory function creating and returning a generator iteratively yielding ``HintPithMetadata`` instances, each describing a sample type hint exercising an edge case in the :mod:`beartype` codebase paired with a related object either satisfying or violating that hint. hints_ignorable : frozenset Frozen set of ignorable PEP-agnostic type hints. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype.door import is_bearable from beartype.roar import ( BeartypeConfException, BeartypeDecorHintForwardRefException, BeartypeDecorHintNonpepException, ) from beartype_test.a00_unit.data.hint.util.data_hintmetacls import ( HintPithUnsatisfiedMetadata) from pytest import raises # from pytest import raises, warns from warnings import simplefilter # ....................{ SETUP }.................... # # Force pytest to temporarily allow deprecation warnings to be caught by the # # warns() context manager for the duration of this test. By default, pytest # # simply "passes through" all deprecation warnings for subsequent reporting # # if tests otherwise successfully pass. Deprecation warnings include: # # * "DeprecationWarning". # # * "FutureWarning". # # * "PendingDeprecationWarning". # simplefilter('always') # ..................{ PASS ~ ignorable }.................. # Arbitrary object to be tested below. pith = 'The breath and blood of distant lands, for ever' # For each predefined ignorable type hint... for hint_ignorable in hints_ignorable: # Assert this tester returns true when passed this object and this hint. assert is_bearable(pith, hint_ignorable) is True # ..................{ PASS ~ unignorable }.................. # For each predefined unignorable type hint and associated metadata... for hint_pith_meta in iter_hints_piths_meta(): # Metadata describing this hint. hint_meta = hint_pith_meta.hint_meta # Type hint to be type-checked. hint = hint_meta.hint # Beartype dataclass configuring this type-check. conf = hint_meta.conf # Object to type-check against this type hint. pith = hint_pith_meta.pith # True only if this pith is expected to satisfy this hint. is_bearable_expected = not isinstance( hint_pith_meta.pith_meta, HintPithUnsatisfiedMetadata) # True only if this pith actually did satisfy this hint. is_bearable_returned = is_bearable(pith, hint, conf=conf) #FIXME: See test_beartype() for details, please. # is_bearable_returned = None # type: ignore[assignment] # # # If this tester is expected to emit a warning for this hint... # if hint_meta.warning_type is not None: # # Decorate that function under a context manager asserting this # # decoration to emit the expected warning. # with warns(hint_meta.warning_type): # # Boolean returned by this tester when passed these arguments. # is_bearable_returned = is_bearable(pith, hint, conf=conf) # # Else, this tester is expected to emit *NO* warning for this hint. In # # this case... # else: # # Boolean returned by this tester when passed these arguments. # is_bearable_returned = is_bearable(pith, hint, conf=conf) # Assert that this tester returns the expected boolean when passed this # pith and hint. assert is_bearable_returned is is_bearable_expected # ..................{ FAIL ~ args }.................. # Assert this tester raises the expected exception when passed an invalid # object as the type hint. with raises(BeartypeDecorHintNonpepException): is_bearable( obj='Holds every future leaf and flower; the bound', hint=b'With which from that detested trance they leap;', ) # Assert this tester raises the expected exception when passed an invalid # object as the beartype configuration. with raises(BeartypeConfException): is_bearable( obj='The torpor of the year when feeble dreams', hint=str, conf='Visit the hidden buds, or dreamless sleep', ) # ..................{ FAIL ~ refs }.................. class RollsItsLoudWatersToTheOceanWaves(object): ''' Arbitrary class with which to test relative forward references below. ''' pass # Assert this tester raises the expected exception when passed a relative # forward reference as the type hint, which this tester explicitly prohibits # to promote both robustness and efficiency. with raises(BeartypeDecorHintForwardRefException): is_bearable( obj='Breathes its swift vapours to the circling air.', hint='RollsItsLoudWatersToTheOceanWaves', ) # See above for @ignore_warnings() discussion. @ignore_warnings(DeprecationWarning) def test_door_typehint_is_bearable(iter_hints_piths_meta) -> None: ''' Test the :meth:`beartype.door.TypeHint.is_bearable` tester method. This test intentionally tests only the core functionality of this tester to avoid violating Don't Repeat Yourself (DRY). This tester internally defers to the procedural :class:`beartype.door.is_bearable` tester, already exhaustively tested by preceding unit tests. Parameters ---------- iter_hints_piths_meta : Callable[[], Iterable[beartype_test.a00_unit.data.hint.util.data_hintmetautil.HintPithMetadata]] Factory function creating and returning a generator iteratively yielding ``HintPithMetadata`` instances, each describing a sample type hint exercising an edge case in the :mod:`beartype` codebase paired with a related object either satisfying or violating that hint. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.door import TypeHint from beartype.roar import BeartypeDoorNonpepException from beartype_test.a00_unit.data.hint.util.data_hintmetacls import ( HintPithUnsatisfiedMetadata) from contextlib import suppress # ....................{ PASS }.................... # For each predefined unignorable type hint and associated metadata... for hint_pith_meta in iter_hints_piths_meta(): # Type hint to be type-checked. hint = hint_pith_meta.hint_meta.hint # Beartype dataclass configuring this type-check. conf = hint_pith_meta.hint_meta.conf # Object to type-check against this type hint. pith = hint_pith_meta.pith # True only if this pith satisfies this hint. is_bearable_expected = not isinstance( hint_pith_meta.pith_meta, HintPithUnsatisfiedMetadata) #FIXME: Remove this suppression *AFTER* improving "TypeHint" to support #all currently unsupported type hints. with suppress(BeartypeDoorNonpepException): # Assert this tester returns the expected boolean when passed this # pith and hint. assert TypeHint(hint).is_bearable(pith, conf=conf) is ( is_bearable_expected) beartype-0.18.5/beartype_test/a00_unit/a60_check/a90_door/test_checkdoor_pep563.py000066400000000000000000000073721461113517100275350ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype :pep:`563` **unit tests.** This submodule unit tests the public :func:`beartype.peps.resolve_pep563` function, which internally leverages the :mod:`beartype.door` subpackage to validate itself and is thus intentionally deferred to this DOOR-specific test subpackage. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # .....................{ TESTS }.................... def test_resolve_pep563() -> None: ''' Test the :func:`beartype.peps.resolve_pep563` resolver. ''' # .....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.peps import resolve_pep563 from beartype.roar import ( BeartypeCallHintForwardRefException, BeartypeDecorHintForwardRefException, BeartypePep563Exception, ) from beartype_test.a00_unit.data.pep.pep563.data_pep563_resolve import ( ToAvariceOrPride, FrequentWith, their_starry_domes, ) from pytest import raises # .....................{ LOCALS }.................... # Arbitrary instance of the generic accepted by the function called below. numberless_and_immeasurable_halls = ToAvariceOrPride() # Arbitrary instance of the class defining various problematic methods # called below. and_thrones_radiant_with_chrysolite = FrequentWith() # .....................{ PASS }.................... # Assert that this function unsuccessfully raises the expected exception # *BEFORE* resolving all PEP 563-postponed type hints annotating these # callables. with raises(BeartypeDecorHintForwardRefException): their_starry_domes(numberless_and_immeasurable_halls) with raises(BeartypeDecorHintForwardRefException): and_thrones_radiant_with_chrysolite.until_the_doves( numberless_and_immeasurable_halls) with raises(BeartypeDecorHintForwardRefException): and_thrones_radiant_with_chrysolite.crystal_column( 'Nor had that scene of ampler majesty') # Resolve all PEP 563-postponed type hints annotating these callables. resolve_pep563(their_starry_domes) resolve_pep563(FrequentWith.until_the_doves) resolve_pep563(FrequentWith.crystal_column) # Assert that this function successfully accepts and returns this instance. assert their_starry_domes(numberless_and_immeasurable_halls) is ( numberless_and_immeasurable_halls) # Assert that this method successfully accepts and returns this string. assert FrequentWith.until_the_doves(numberless_and_immeasurable_halls) is ( numberless_and_immeasurable_halls) # .....................{ FAIL }.................... # Assert that this resolver raises the expected exception when passed an # uncallable object. with raises(BeartypePep563Exception): resolve_pep563('Mont Blanc yet gleams on high:—the power is there,') # Assert that this method unsuccessfully raises the excepted exception, due # to being annotated by a missing forward reference. with raises(BeartypeCallHintForwardRefException): and_thrones_radiant_with_chrysolite.crystal_column( 'Than gems or gold, the varying roof of heaven') beartype-0.18.5/beartype_test/a00_unit/a60_check/test_checkcall.py000066400000000000000000000050741461113517100247640ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype decorator **dataclass** unit tests. This submodule unit tests the :func:`beartype._check.checkcall` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_beartypecall() -> None: ''' Test the :func:`beartype._check.checkcall.BeartypeCall` dataclass. ''' # Defer test-specific imports. from beartype import BeartypeConf from beartype.roar import BeartypeDecorWrappeeException from beartype._check.checkcall import BeartypeCall from pytest import raises # Arbitrary beartype metadata. bear_data = BeartypeCall() # Assert this metadata to be unhashable. with raises(TypeError): hash(bear_data) # Assert that reinitializing this metadata a non-callable raises the # expected exception. with raises(BeartypeDecorWrappeeException): bear_data.reinit( func='The fields, the lakes, the forests, and the streams,', conf=BeartypeConf(), ) # Assert that reinitializing this metadata with a C-based builtin function # raises the expected exception. with raises(BeartypeDecorWrappeeException): bear_data.reinit(func=iter, conf=BeartypeConf()) # Assert that reinitializing this metadata with an invalid configuration # raises the expected exception. with raises(BeartypeDecorWrappeeException): bear_data.reinit( func=lambda: ..., conf='Ocean, and all the living things that dwell', ) # Assert that reinitializing this metadata with invalid class stacks raises # the expected exception. with raises(BeartypeDecorWrappeeException): bear_data.reinit( func=lambda: ..., conf=BeartypeConf(), cls_stack="Shine in the rushing torrents' restless gleam,", ) with raises(BeartypeDecorWrappeeException): bear_data.reinit( func=lambda: ..., conf=BeartypeConf(), cls_stack=('Which from those secret chasms in tumult welling',), ) beartype-0.18.5/beartype_test/a00_unit/a70_decor/000077500000000000000000000000001461113517100214545ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a70_decor/__init__.py000066400000000000000000000000001461113517100235530ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a70_decor/a00_core/000077500000000000000000000000001461113517100230445ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a70_decor/a00_core/__init__.py000066400000000000000000000000001461113517100251430ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a70_decor/a00_core/test_decormore.py000066400000000000000000000036421461113517100264410ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Unmemoized beartype decorator unit tests.** This submodule unit tests low-level functionality of the private :mod:`beartype._decor.decorcore` submodule *not* already tested by higher-level unit tests defined elsewhere. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_beartype_descriptor_decorator_builtin() -> None: ''' Test the subset of the private :func:`beartype._decor._decornontype.beartype_descriptor_decorator_builtin` decorator *not* already tested by higher-level unit tests defined elsewhere. See Also ---------- ``test_decor_wrappee_type_decorator_builtin()`` Higher-level unit test already exercising *most* of the functionality of this decorator, which this test (sensibly) avoids repeating. ''' # Defer test-specific imports. from beartype import BeartypeConf from beartype.roar import BeartypeDecorWrappeeException from beartype._decor._decornontype import beartype_descriptor_decorator_builtin from pytest import raises # Assert this decorator raises the expected exception when passed an object # that is neither a class, property, *NOR* static method descriptor. with raises(BeartypeDecorWrappeeException): beartype_descriptor_decorator_builtin( descriptor='Music, when soft voices die,', conf=BeartypeConf(), ) beartype-0.18.5/beartype_test/a00_unit/a70_decor/a20_error/000077500000000000000000000000001461113517100232475ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a70_decor/a20_error/__init__.py000066400000000000000000000000001461113517100253460ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a70_decor/a20_error/a90_main/000077500000000000000000000000001461113517100246445ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a70_decor/a20_error/a90_main/__init__.py000066400000000000000000000000001461113517100267430ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a70_decor/a20_error/a90_main/test_errorget.py000066400000000000000000000337051461113517100301160ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype main error-handling unit tests.** This submodule unit tests the public API of the private :mod:`beartype._check.error.errorget` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ pith }.................... def test_get_func_pith_violation() -> None: ''' Test the :func:`beartype._check.error.errorget.get_func_pith_violation` getter. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype import BeartypeConf from beartype.roar import ( BeartypeCallHintParamViolation, BeartypeCallHintReturnViolation, BeartypeDecorHintNonpepException, ) from beartype.roar._roarexc import _BeartypeCallHintPepRaiseException from beartype.typing import ( List, Tuple, Union, ) from beartype._data.func.datafuncarg import ARG_NAME_RETURN from beartype._check.error.errorget import get_func_pith_violation from pytest import raises # ..................{ LOCALS }.................. def forest_unknown( secret_orchard: List[str], achromatic_voice, to_bid_you_farewell: str, amaranth_symbol: 42, ) -> Union[int, Tuple[str, ...]]: ''' Arbitrary callable exercised below. ''' return achromatic_voice # Keyword arguments to be unconditionally passed to all getter calls below. kwargs = dict( func=forest_unknown, conf=BeartypeConf(), ) # ..................{ PASS }.................. # Assert this function returns the expected exception when passed a # parameter annotated by a PEP-compliant type hint failing to shallowly # satisfy the type of that type hint. violation = get_func_pith_violation( pith_name='secret_orchard', pith_value=( 'You are in a forest unknown:', 'The secret orchard.', ), **kwargs ) assert isinstance(violation, BeartypeCallHintParamViolation) # Assert this function returns the expected exception when passed a # parameter annotated by a PEP-compliant type hint failing to deeply satisfy # the type of that type hint. violation = get_func_pith_violation( pith_name='secret_orchard', pith_value=[ b'I am awaiting the sunrise', b'Gazing modestly through the coldest morning', ], **kwargs ) assert isinstance(violation, BeartypeCallHintParamViolation) # Assert this function returns the expected exception when passed another # parameter annotated by a PEP-noncompliant type hint failing to shallowly # satisfy the type of that type hint. violation = get_func_pith_violation( pith_name='to_bid_you_farewell', pith_value=( b'Once it came you lied,' b"Embracing us over autumn's proud treetops." ), **kwargs ) assert isinstance(violation, BeartypeCallHintParamViolation) # Assert this function returns the expected exception when returning a # return value annotated by a PEP-compliant type hint failing to satisfy # that type hint. violation = get_func_pith_violation( pith_name=ARG_NAME_RETURN, pith_value=[ 'Sunbirds leave their dark recesses.', 'Shadows glide the archways.', ], **kwargs ) assert isinstance(violation, BeartypeCallHintReturnViolation) # ..................{ FAIL }.................. # Assert this function raises the expected exception when passed an # unannotated parameter. with raises(_BeartypeCallHintPepRaiseException): get_func_pith_violation( pith_name='achromatic_voice', pith_value=( 'And your voice is vast and achromatic,' 'But still so precious.' ), **kwargs ) # Assert this function raises the expected exception when passed a # parameter annotated by an object that is unsupported as a type hint # (i.e., is neither PEP-compliant nor -noncompliant). with raises(BeartypeDecorHintNonpepException): get_func_pith_violation( pith_name='amaranth_symbol', pith_value=( 'I have kept it,' 'The Amaranth symbol,' 'Hidden inside the golden shrine' 'Until we rejoice in the meadow' 'Of the end.' 'When we both walk the shadows,' 'It will set ablaze and vanish.' ), **kwargs ) # ....................{ TESTS ~ pith : conf }.................... def test_get_func_pith_violation_conf_is_color() -> None: ''' Test the :func:`beartype._check.error.errorget.get_func_pith_violation` getter with respect to the :attr:`beartype.BeartypeConf.is_color` option. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype import BeartypeConf from beartype.typing import ( List, Tuple, Union, ) from beartype._check.error.errorget import get_func_pith_violation from beartype._util.os.utilostty import is_stdout_terminal from beartype._util.text.utiltextansi import is_str_ansi # ..................{ LOCALS }.................. def she_drew_back( a_while: List[str], then_yielding) -> Union[int, Tuple[str, ...]]: ''' Arbitrary callable exercised below. ''' return then_yielding # Keyword arguments to be unconditionally passed to *ALL* calls of the # get_func_pith_violation() getter below. kwargs = dict( func=she_drew_back, pith_name='a_while', pith_value=( 'With frantic gesture and short breathless cry', 'Folded his frame in her dissolving arms.', ), ) # ..................{ PASS }.................. # Violation configured to contain ANSI escape sequences. violation = get_func_pith_violation( conf=BeartypeConf(is_color=True), **kwargs) # Assert this violation message contains ANSI escape sequences. assert is_str_ansi(str(violation)) is True # Violation configured to contain *NO* ANSI escape sequences. violation = get_func_pith_violation( conf=BeartypeConf(is_color=False), **kwargs) # Assert this violation message contains *NO* ANSI escape sequences. assert is_str_ansi(str(violation)) is False # Violation configured to conditionally contain ANSI escape sequences only # when standard output is attached to an interactive terminal. violation = get_func_pith_violation( conf=BeartypeConf(is_color=None), **kwargs) # Assert this violation message contains ANSI escape sequences only when # standard output is attached to an interactive terminal. assert is_str_ansi(str(violation)) is is_stdout_terminal() # ....................{ TESTS ~ pith : conf : violation_* }.................... def test_get_func_pith_violation_conf_violation_types() -> None: ''' Test the :func:`beartype._check.error.errorget.get_func_pith_violation` getter with respect to the :attr:`beartype.BeartypeConf.violation_param_type` and :attr:`beartype.BeartypeConf.violation_return_type` options. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype import BeartypeConf from beartype.typing import ( List, Tuple, Union, ) from beartype._data.func.datafuncarg import ARG_NAME_RETURN from beartype._check.error.errorget import get_func_pith_violation # ..................{ CLASSES }.................. class InvolvedAndSwallowed(Exception): ''' Arbitrary exception subclass. ''' pass # ..................{ LOCALS }.................. def now_blackness( veiled_his: List[str], dizzy_eyes) -> Union[int, Tuple[str, ...]]: ''' Arbitrary callable exercised below. ''' return dizzy_eyes # Keyword arguments to be unconditionally passed to *ALL* calls of the # get_func_pith_violation() getter below. kwargs = dict(func=now_blackness) # ..................{ PASS }.................. # Parameter violation configured to be a non-default exception subclass. param_violation = get_func_pith_violation( conf=BeartypeConf(violation_param_type=InvolvedAndSwallowed), pith_name='veiled_his', pith_value=( 'Now blackness veiled his dizzy eyes, and night', 'Involved and swallowed up the vision; sleep,', ), **kwargs ) # Assert that this violation is the expected non-default exception subclass. assert type(param_violation) is InvolvedAndSwallowed # Return violation configured to be a non-default exception subclass. return_violation = get_func_pith_violation( conf=BeartypeConf(violation_return_type=InvolvedAndSwallowed), pith_name=ARG_NAME_RETURN, pith_value=[ 'Like a dark flood suspended in its course', 'Rolled back its impulse on his vacant brain.', ], **kwargs ) # Assert that this violation is the expected non-default exception subclass. assert type(return_violation) is InvolvedAndSwallowed def test_get_func_pith_violation_conf_violation_verbosity() -> None: ''' Test the :func:`beartype._check.error.errorget.get_func_pith_violation` getter with respect to the :attr:`beartype.BeartypeConf.violation_verbosity` option. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype import ( BeartypeConf, BeartypeViolationVerbosity, ) from beartype.typing import ( List, Tuple, Union, ) from beartype._check.error.errorget import get_func_pith_violation # ..................{ LOCALS }.................. def like_a_dark_flood( suspended_in: List[str], its_course) -> Union[int, Tuple[str, ...]]: ''' Arbitrary callable exercised below. ''' return its_course # Keyword arguments to be unconditionally passed to *ALL* calls of the # get_func_pith_violation() getter below. kwargs = dict( func=like_a_dark_flood, pith_name='suspended_in', pith_value=( 'Roused by the shock he started from his trance—', 'The cold white light of morning, the blue moon', ), ) # ..................{ PASS }.................. # Tuple of all otherwise equivalent violations produced by iteratively # increasing the level of violation verbosity. violations = tuple( # Violation whose message is configured to be this verbose... get_func_pith_violation( conf=BeartypeConf(violation_verbosity=violation_verbosity), **kwargs ) # For each kind of violation verbosity. for violation_verbosity in BeartypeViolationVerbosity ) # Previously iterated violation, defaulting to the minimally verbose (i.e., # maximally terse) violation. violation_prev = violations[0] # For each increasingly verbose violation following the first... for violation in violations[1:]: # Assert that this violation message is more verbose than the last. assert len(str(violation)) > len(str(violation_prev)) # Store the previously iterated violation for subsequent reference. violation_prev = violation # ....................{ TESTS ~ pith }.................... def test_get_hint_object_violation() -> None: ''' Test the :func:`beartype._check.error.errorget.get_hint_object_violation` getter. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype.roar._roarexc import _BeartypeCallHintPepRaiseException from beartype._data.func.datafuncarg import ARG_NAME_RETURN from beartype._check.error.errorget import get_hint_object_violation from beartype._conf.confcls import BEARTYPE_CONF_DEFAULT from pytest import raises # ..................{ LOCALS }.................. # Keyword arguments to be unconditionally passed to all getter calls below. kwargs = dict( obj='Frantic with dizzying anguish, her blind flight', hint=str, conf=BEARTYPE_CONF_DEFAULT, ) # ..................{ FAIL }.................. # Assert that this getter raises the expected exception when passed neither # an exception prefix *NOR* parameter name. with raises(_BeartypeCallHintPepRaiseException): get_hint_object_violation(**kwargs) # Assert that this getter raises the expected exception when passed both an # exception prefix *AND* parameter name. with raises(_BeartypeCallHintPepRaiseException): get_hint_object_violation( exception_prefix="O'er the wide aëry wilderness: thus driven", pith_name=ARG_NAME_RETURN, **kwargs ) beartype-0.18.5/beartype_test/a00_unit/a70_decor/a20_error/a90_main/test_errorplug.py000066400000000000000000000142341461113517100303020ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype error-handling plugin unit tests.** This submodule unit tests the public API of the private :mod:`beartype._check.error.errorget` submodule with respect to :mod:`beartype`-specific plugin APIs (e.g., the ``__instancecheck_str__()`` dunder method). ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_get_func_pith_violation_instancecheck_str() -> None: ''' Test the :func:`beartype._check.error.errorget.get_func_pith_violation` getter with respect to the :mod:`beartype`-specific ``__instancecheck_str__()`` dunder method plugin API. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype import BeartypeConf from beartype.roar import BeartypePlugInstancecheckStrException from beartype.typing import Any from beartype._check.error.errorget import get_func_pith_violation from pytest import raises # ..................{ METACLASSES }.................. class TheMysteryAndMeta(type): ''' Arbitrary metaclass correctly defining the :mod:`beartype`-specific ``__instancecheck_str__()`` dunder method. ''' def __instancecheck_str__(cls, obj: Any) -> str: ''' Correct implementation accepting the expected number of parameters and returning the passed object as is. ''' # Return the passed object as is. return obj class TheMajestyOfMeta(type): ''' Arbitrary metaclass incorrectly defining the :mod:`beartype`-specific ``__instancecheck_str__()`` dunder method to accept an unexpected number of parameters. ''' def __instancecheck_str__( cls, the_joy: Any, the_exultation: Any) -> str: ''' Incorrect implementation accepting an unexpected number of parameters and returning an arbitrary non-empty string. ''' return 'The mystery and the majesty of Earth,' # ..................{ CLASSES }.................. class TheMysteryAnd(object, metaclass=TheMysteryAndMeta): ''' Arbitrary class whose metaclass correctly defines the :mod:`beartype`-specific ``__instancecheck_str__()`` dunder method. ''' pass class TheMajestyOf(object, metaclass=TheMajestyOfMeta): ''' Arbitrary class whose metaclass incorrectly defines the :mod:`beartype`-specific ``__instancecheck_str__()`` dunder method to accept an unexpected number of parameters. ''' pass # ..................{ LOCALS }.................. def of_yesternight( the_sounds_that: TheMysteryAnd, of_earth: TheMajestyOf, ) -> TheMysteryAnd: ''' Arbitrary callable annotated by classes whose metaclasses both correctly and incorrectly defining the :mod:`beartype`-specific ``__instancecheck_str__()`` dunder method. ''' return the_sounds_that # Keyword arguments to be unconditionally passed to *ALL* calls of the # get_func_pith_violation() getter below. kwargs = dict( func=of_yesternight, conf=BeartypeConf(), ) # Arbitrary non-empty string. HIS_WAN_EYES = 'Of yesternight? The sounds that soothed his sleep,' # ..................{ PASS }.................. # Violation exception created and returned by this getter for a hypothetical # call of the function defined above when passed a non-empty string as the # value of the parameter annotated by an arbitrary class whose metaclass # correctly defines __instancecheck_str__(). violation = get_func_pith_violation( pith_name='the_sounds_that', pith_value=HIS_WAN_EYES, **kwargs ) # Message of this violation. violation_str = str(violation) # Assert that this violation contains the passed non-empty string. assert HIS_WAN_EYES in violation_str # ..................{ FAIL }.................. # Assert that this getter raises the expected exception for a hypothetical # call of the function defined above when passed an arbitrary non-string # object as the value of the parameter annotated by an arbitrary class whose # metaclass correctly defines __instancecheck_str__(). with raises(BeartypePlugInstancecheckStrException): get_func_pith_violation( pith_name='the_sounds_that', pith_value=b"As ocean's moon looks on the moon in heaven.", **kwargs ) # Assert that this getter raises the expected exception for a hypothetical # call of the function defined above when passed an empty string as the # value of the parameter annotated by an arbitrary class whose metaclass # correctly defines __instancecheck_str__(). with raises(BeartypePlugInstancecheckStrException): get_func_pith_violation( pith_name='the_sounds_that', pith_value='', **kwargs ) # Assert that this getter raises the expected exception for a hypothetical # call of the function defined above when passed a non-empty string as the # value of the parameter annotated by an arbitrary class whose metaclass # incorrectly defines __instancecheck_str__(). with raises(BeartypePlugInstancecheckStrException): get_func_pith_violation( pith_name='of_earth', pith_value='Gaze on the empty scene as vacantly', **kwargs ) beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/000077500000000000000000000000001461113517100230325ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a00_module/000077500000000000000000000000001461113517100247575ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a00_module/__init__.py000066400000000000000000000000001461113517100270560ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a00_module/test_decor_contextlib.py000066400000000000000000000072721461113517100317270ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype decorator :mod:`contextlib`-specific unit tests. This submodule unit tests the :func:`beartype.beartype` decorator with respect the standard :mod:`contextlib` module. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype_test._util.mark.pytskip import skip_if_python_version_less_than # ....................{ TESTS }.................... @skip_if_python_version_less_than('3.11.0') def test_decor_contextlib_contextmanager() -> None: ''' Test the :func:`beartype.beartype` decorator on :func:`contextlib.contextmanager`-based **context managers** (i.e., generator factory functions decorated by that standard decorator) if the active Python interpreter targets Python >= 3.11 and thus defines the ``co_qualname`` attribute on code objects required to implement this functionality *or* skip this test otherwise. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeCallHintParamViolation from beartype.typing import ( Iterator, Union, ) from contextlib import contextmanager from pytest import raises # ....................{ CONTEXTS }.................... @contextmanager @beartype def and_motions_of( the_forests_and_the_sea: Union[int, float]) -> Iterator[int]: ''' Arbitrary :func:`contextlib.contextmanager`-decorated context manager decorated by :func:`beartype.beartype` in the ideal order. ''' yield int(the_forests_and_the_sea) @beartype @contextmanager def may_modulate_with( murmurs_of_the_air: str) -> Iterator[Union[str, bool]]: ''' Arbitrary :func:`contextlib.contextmanager`-decorated context manager decorated by :func:`beartype.beartype` in a non-ideal order. ''' yield murmurs_of_the_air # ....................{ PASS }.................... # Assert that the ideal context manager when passed a valid parameter # returns the expected return. with and_motions_of(len( 'Of night and day, and the deep heart of man.')) as ( deep_heart_of_man): assert deep_heart_of_man == len( 'Of night and day, and the deep heart of man.') # Assert that the non-ideal context manager when passed a valid parameter # returns the expected return. with may_modulate_with( 'And voice of living beings, and woven hymns') as and_woven_hymns: assert and_woven_hymns == 'And voice of living beings, and woven hymns' # ....................{ FAIL }.................... # Assert that the ideal context manager when passed an invalid parameter # raises the expected exception. with raises(BeartypeCallHintParamViolation): and_motions_of('No human hands with pious reverence reared,') # Assert that the non-ideal context manager when passed an invalid parameter # raises the expected exception. with raises(BeartypeCallHintParamViolation): may_modulate_with(b'There was a Poet whose untimely tomb') beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a00_module/test_decor_functools.py000066400000000000000000000074541461113517100315720ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype decorator :mod:`functools`-specific unit tests. This submodule unit tests the :func:`beartype.beartype` decorator with respect the standard :mod:`functools` module. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_decor_functools_lru_cache() -> None: ''' Test the :func:`beartype.beartype` decorator on :func:`functools.lru_cache`-based **memoized callables** (i.e., pure-Python callables decorated by that standard decorator, which then creates and returns low-level C-based callable objects memoizing those callables). ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from beartype.roar import ( BeartypeCallHintParamViolation, BeartypeDecorWrappeeException, ) from beartype._util.py.utilpyversion import IS_PYTHON_3_8 from functools import lru_cache from pytest import raises # ....................{ IDEAL }.................... @lru_cache(maxsize=3) @beartype def increment_int_goodly(n: int) -> int: ''' Arbitrary :func:`functools.lru_cache`-memoized callable decorated by :func:`beartype.beartype` in the ideal order. ''' return n + 1 # Assert that the ideal memoized callable when passed a valid parameter # returns the expected return. assert increment_int_goodly(42) == 43 # Assert that the ideal memoized callable when passed an invalid parameter # raises the expected exception. with raises(BeartypeCallHintParamViolation): increment_int_goodly('The lone couch of his everlasting sleep:—') # ....................{ NON-IDEAL }.................... # If the active Python interpreter targets Python 3.8, then @beartype fails # to support the edge case of non-ideal decoration ordering. In this case, # assert that @beartype raises the expected exception. if IS_PYTHON_3_8: with raises(BeartypeDecorWrappeeException): @beartype @lru_cache(maxsize=3) def increment_int_badly(n: int) -> int: ''' Arbitrary :func:`functools.lru_cache`-memoized callable decorated by :func:`beartype.beartype` in a non-ideal order. ''' return n + 1 # Else, the active Python interpreter targets Python >= 3.9. In this case, # @beartype supports the edge case of non-ideal decoration ordering. else: @beartype @lru_cache(maxsize=3) def increment_int_badly(n: int) -> int: ''' Arbitrary :func:`functools.lru_cache`-memoized callable decorated by :func:`beartype.beartype` in a non-ideal order. ''' return n + 1 # Assert that the non-ideal memoized callable when passed a valid parameter # returns the expected return. assert increment_int_badly(24) == 25 # Assert that the non-ideal memoized callable when passed an invalid # parameter raises the expected exception. with raises(BeartypeCallHintParamViolation): increment_int_badly('Gentle, and brave, and generous,—no lorn bard') beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a00_module/test_decor_mypy.py000066400000000000000000000136241461113517100305500ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator mypy-compliant type hint unit tests.** This submodule unit tests the :func:`beartype.beartype` decorator with respect to :mod:`mypy`-compliant type hints (i.e., type hints that are *not* explicitly PEP-compliant but are effectively PEP-compliant due to being accepted by :mod:`mypy`, the *de facto* type-hinting standard). ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_decor_mypy_notimplemented() -> None: ''' Test the :func:`beartype.beartype` decorator against :mod:`mypy` compliant usage of the :data:`NotImplemented` singleton, which is contextually permissible *only* as an unsubscripted return annotation of binary dunder methods. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeCallHintReturnViolation from beartype_test._util.pytroar import raises_uncached # ..................{ LOCALS }.................. # Without this, the forward reference in the return type of # TheCloud.__add__ (below) will fail because bear desperately yearns to # find it at the module level. And who are we to be so cruel as to hide the # object of her desires any more deeply than that? global TheCloud # ..................{ CLASSES }.................. class TheCloud: ''' Arbitrary class declaring a method exercising this test. ''' @beartype def __eq__(self, other: object) -> bool: ''' Arbitrary binary dunder method correctly returning the ``NotImplemented`` singleton. ''' # If the passed object is an instance of the same class, return # true only if that object is exactly this object. if isinstance(other, TheCloud): return self is other # Else, return the "NotImplemented" singleton. return NotImplemented @beartype def __add__(self, other: object) -> "TheCloud": ''' Another arbitrary binary dunder method correctly returning the ``NotImplemented`` singleton. ''' # Create a new cloud when trying to add two clouds, # because...erm...math! No, wait. Because this is an entirely # contrived example for testing purposes and neither intends nor # offers any commentary about the subtle tensions between # uniqueness, individualism, connectedness, and community. Such # matters are left for quiet contemplation by the reader. if isinstance(other, TheCloud): return TheCloud() # Apparently these clouds do not enjoy the company of non-clouds. # While such isolationist tendencies may sadden us, we nonetheless # respect each cloud's autonomy. return NotImplemented @beartype def is_equal(self, other: object) -> bool: ''' Arbitrary non-dunder method erroneously returning the ``NotImplemented`` singleton. ''' # If the passed object is an instance of the same class, return # true only if that object is exactly this object. if isinstance(other, TheCloud): return self is other # Else, return the "NotImplemented" singleton. return NotImplemented # Pair of instances of this class. the_seas = TheCloud() the_streams = TheCloud() # ..................{ ASSERTS }.................. # Assert each instance compares equal to itself. assert the_seas == the_seas assert the_streams == the_streams # Assert each instance compares unequal to the other instance. assert the_seas != the_streams assert the_streams != the_seas # Assert each instance compares unequal to objects of different classes. assert the_seas != 'I bring fresh showers for the thirsting flowers,' assert the_streams != 'From the seas and the streams;' # Assert explicit methods of each instance also compares equal to itself. assert the_seas.is_equal(the_seas) assert the_streams.is_equal(the_streams) # Assert explicit methods of each instance also compares unequal to the # other instance. assert not the_seas.is_equal(the_streams) assert not the_streams.is_equal(the_seas) # Assert the special case in @beartype-generated wrappers implicitly # type-checking binary dunder methods annotated as, e.g., returning "bool" to # instead effectively return "Union[bool, type(NotImplemented)]" does *NOT* # apply to standard methods -- even those with the exact same method body. with raises_uncached(BeartypeCallHintReturnViolation): the_seas.is_equal('I bear light shade for the leaves when laid') # Assert we can "__add__" two cloud instances... assert isinstance(the_seas + the_streams, TheCloud) # ...but not a cloud instance with an instance of another class *AND* that # the attempt results in a "TypeError" (the intended side effect of # returning "NotImplemented" from a binary dunder method). with raises_uncached(TypeError): the_seas + 'I bear light shade for the leaves when laid' beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a00_module/test_decor_nptyping.py000066400000000000000000000155061461113517100314230ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator PEP-noncompliant** :mod:`nptyping` **type hint unit tests.** This submodule unit tests the :func:`beartype.beartype` decorator with respect to **PEP-noncompliant** :mod:`nptyping` **type hints** (i.e., :mod:`nptyping`-specific annotations *not* compliant with annotation-centric PEPs). ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype_test._util.mark.pytskip import ( skip_if_python_version_less_than, skip_unless_package, ) # ....................{ TESTS }.................... # If the active Python interpreter targets Python < 3.10.0, this interpreter # fails to support PEP 604-compliant new unions (e.g., "int | str") and thus the # entire point of this unit test. In this case, skip this test. @skip_if_python_version_less_than('3.10.0') @skip_unless_package('nptyping') def test_decor_nptyping() -> None: ''' Test that the :func:`beartype.beartype` decorator rejects *all* :mod:`nptyping` type hints with the expected exception. :mod:`nptyping` type hints violate Python typing standards in various ways. Notably, :mod:`nptyping` type hint factories dynamically generate unique classes that nonetheless share the same fully-qualified names, complicating caching in the :func:`beartype.beartype` decorator. Whereas this test suite automates testing of PEP-compliant type hints via the :mod:`beartype_test.a00_unit.data` subpackage, :mod:`nptyping` type hints are fundamentally non-standard and thus *cannot* be automated in this standard manner. These hints can *only* be tested with a non-standard workflow implemented by this unit test. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. # # Note that nptyping requires NumPy. Ergo, NumPy is safely importable here. from beartype import beartype from beartype.roar import BeartypeDecorHintPep604Exception from numpy import ( # array, # float64, int64, sum as numpy_sum, ) from nptyping import ( Float64, Int64, NDArray, Shape, ) from pytest import raises # ....................{ FUNCTIONS }.................... with raises(BeartypeDecorHintPep604Exception): @beartype def suspended_he_that_task( but_ever_gazed: NDArray[Shape['N, N'], Float64] | None = None, and_gazed: NDArray[Shape['*' ], Int64] | None = None, ) -> int64 | None: ''' Arbitrary callable annotated by two :pep:`604`-compliant new unions of distinct :mod:`nptyping` type hints and arbitrary other types. This callable exercises a `prominent edge case`_. .. _prominent edge case: https://github.com/beartype/beartype/issues/304 ''' # Bend it like Bender. return None if and_gazed is None else numpy_sum(and_gazed) #FIXME: Uncomment this if @ramonhagenaars ever resurrects "nptyping". Until #that utopia, this is optimistically preserved for posterity. # ''' # Test that the :func:`beartype.beartype` decorator successfully type-checks # callables annotated by :mod:`nptyping` type hints. # # :mod:`nptyping` type hints violate Python typing standards in various ways. # Notably, :mod:`nptyping` type hint factories dynamically generate unique # classes that nonetheless share the same fully-qualified names, complicating # caching in the :func:`beartype.beartype` decorator. # # Whereas this test suite automates testing of PEP-compliant type hints via # the :mod:`beartype_test.a00_unit.data` subpackage, :mod:`nptyping` type # hints are fundamentally non-standard and thus *cannot* be automated in this # standard manner. These hints can *only* be tested with a non-standard # workflow implemented by this unit test. # ''' # # # ....................{ IMPORTS }.................... # # Defer test-specific imports. # # # # Note that nptyping requires NumPy. Ergo, NumPy is safely importable here. # from beartype import beartype # from beartype.roar import BeartypeCallHintParamViolation # from numpy import ( # array, # float64, # int64, # sum as numpy_sum, # ) # from nptyping import ( # Float64, # Int64, # NDArray, # Shape, # ) # from pytest import raises # # # ....................{ LOCALS }.................... # # Arbitrary NumPy arrays satisfied by "nptyping" type hints defined below. # flashed_like_strong_inspiration = array( # [[1., 2.], [3., 4.]], dtype=float64) # till_meaning_on_his_vacant_mind = array( # [1, 2, 3, 4, 5, 6], dtype=int64) # # # ....................{ FUNCTIONS }.................... # @beartype # def suspended_he_that_task( # but_ever_gazed: NDArray[Shape['N, N'], Float64] | None = None, # and_gazed: NDArray[Shape['*' ], Int64] | None = None, # ) -> int64 | None: # ''' # Arbitrary callable annotated by two :pep:`604`-compliant new unions of # distinct :mod:`nptyping` type hints and arbitrary other types. # # This callable exercises a `prominent edge case`_. # # .. _prominent edge case: # https://github.com/beartype/beartype/issues/304 # ''' # # # Bend it like Bender. # return None if and_gazed is None else numpy_sum(and_gazed) # # # ....................{ PASS }.................... # # Assert that this callable returns the expected value when passed NumPy # # arrays satisfying the "nptyping" type hints annotating this callable. # assert suspended_he_that_task( # but_ever_gazed=flashed_like_strong_inspiration, # and_gazed=till_meaning_on_his_vacant_mind, # ) == 21 # # # ....................{ FAIL }.................... # # Assert that this callable raises the expected exception when passed NumPy # # arrays violating the "nptyping" type hints annotating this callable. # with raises(BeartypeCallHintParamViolation): # suspended_he_that_task(till_meaning_on_his_vacant_mind) beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a00_module/test_decor_pandera.py000066400000000000000000000325441461113517100311660ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator PEP-noncompliant Pandera type hint unit tests.** This submodule unit tests the :func:`beartype.beartype` decorator with respect to **PEP-noncompliant Pandera type hints** (i.e., :mod:`pandera.typing`-specific annotations *not* compliant with annotation-centric PEPs). ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype_test._util.mark.pytskip import skip_unless_package # ....................{ TESTS }.................... @skip_unless_package('pandera') def test_decor_pandera() -> None: ''' Test that the :func:`beartype.beartype` decorator conditionally ignores *all* Pandera type hints. Pandera type hints violate Python typing standards and thus require use of Pandera-specific functionality to type-check. This includes the non-standard :func:`pandera.check_types` decorator, which performs ad-hoc PEP-noncompliant runtime type-checking of ad-hoc PEP-noncompliant Pandera type hints annotating the decorated callable. Since type-checking Pandera type hints *de facto* requires usage of this decorator, the PEP-compliant :func:`beartype.beartype` decorator *cannot* natively type-check Pandera type hints. Instead, :func:`beartype.beartype` can only: * Detect that Pandera type hints are type-checked by a prior application of :func:`pandera.check_types` on the decorated callable. * If so, ignore *all* Pandera type hints. * Else, raise an exception. Whereas this test suite automates testing of PEP-compliant type hints via the :mod:`beartype_test.a00_unit.data` subpackage, Pandera type hints are fundamentally non-standard and thus *cannot* be automated in this standard manner. These hints can *only* be tested with a non-standard workflow implemented by this unit test. See Also ---------- https://pandera.readthedocs.io/en/stable/dataframe_models.html Official introduction to the :func:`pandera.check_types` decorator. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. # # Note that Pandera requires Pandas, which requires NumPy. Ergo, both Pandas # and NumPy are guaranteed to be safely importable here. from beartype import beartype from beartype.roar import ( BeartypeCallHintParamViolation, BeartypeCallHintReturnViolation, ) from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_10 from pandas import ( DataFrame, to_datetime, ) from pandera import ( # Column, DataFrameModel, # Index, check_types, ) from pandera.dtypes import ( Int64, String, Timestamp, ) from pandera.errors import SchemaError from pandera.typing import ( DataFrame as PanderaDataFrame, Series, ) from pytest import raises # ....................{ LOCALS }.................... # Pandas data frame containing arbitrary sample data satisfying the # "PanderaModel" defined below. pandas_dataframe_good = DataFrame({ 'Hexspeak': ( 0xCAFED00D, 0xCAFEBABE, 0x1337BABE, ), 'OdeToTheWestWind': ( 'Angels of rain and lightning: there are spread', 'On the blue surface of thine aery surge,', 'Like the bright hair uplifted from the head', ), 'PercyByssheShelley': to_datetime(( '1792-08-04', '1822-07-08', '1851-02-01', )), }) # Pandas data frame containing arbitrary sample data violating the # "PanderaModel" defined below. pandas_dataframe_bad = DataFrame() # ....................{ CLASSES }.................... class PanderaModel(DataFrameModel): ''' Pandera model validating the ``pandas_dataframe_good`` defined above. ''' Hexspeak: Series[Int64] OdeToTheWestWind: Series[String] PercyByssheShelley: Series[Timestamp] #FIXME: Preserved for debuggability. Uncomment as needed, please. # hint = PanderaDataFrame[PanderaModel] # hint_origin = hint.__origin__ # # print(f'\nrepr(hint): {repr(hint)}') # # print(f'\ndir(hint): {dir(hint)}') # # print(f'\ntype(hint): {type(hint)}') # # from pandera.typing.pandas import DataFrame as NestedPanderaDataFrame # # print(f'\nis hint_origin a pandera dataframe? {hint_origin is NestedPanderaDataFrame}') # # print(f'\nrepr(hint_origin): {repr(hint_origin)}') # # print(f'\ndir(hint_origin): {dir(hint_origin)}') # print(f'\nmro(hint_origin): {hint_origin.__mro__}') # # print(f'\ntype(hint_origin): {type(hint_origin)}') # print(f'\nhint_origin.__orig_bases__: {hint_origin.__orig_bases__}') # print(f'is pandas dataframe a pandera dataframe? {isinstance(pandas_dataframe_good, hint_origin)}') # print(f'is pandas dataframe an orig dataframe? {isinstance(pandas_dataframe_good, hint_origin.__orig_bases__)}') # # print(f'is pandas dataframe a pandera dataframe? {isinstance(pandas_dataframe_good, NestedPanderaDataFrame)}') # # print(f'is pandas dataframe an orig dataframe? {isinstance(pandas_dataframe_good, NestedPanderaDataFrame.__orig_bases__)}') # from beartype import BeartypeConf # @beartype(conf=BeartypeConf(is_debug=True)) # ....................{ FUNCTIONS }.................... @beartype @check_types def to_the_zeniths_height( dataframe: PanderaDataFrame[PanderaModel], of_some_fierce_maenad: str, ) -> str: ''' Arbitrary callable decorated first by :mod:`beartype` and then by :mod:`pandera`, accepting a parameter annotated by a Pandera type hint *and* a parameter annotated by a non-Pandera PEP-compliant type hint. This callable exercises that: * The :func:`pandera.check_types` decorator type-checks the parameter annotated by the Pandera type hint. * The :func:`beartype.beartype` decorator type-checks the parameter annotated by the non-Pandera type hint. ''' # Return this string parameter appended by an arbitrary constant. return f'{of_some_fierce_maenad}, even from the dim verge' # print(f'PanderaDataFrame[pandera_schema]: {repr(PanderaDataFrame[pandera_schema])}') @check_types @beartype def all_thy_congregated_might( dataframe: PanderaDataFrame[PanderaModel], of_some_fierce_maenad: str, ) -> str: ''' Arbitrary callable decorated first by :mod:`pandera` and then by :mod:`beartype`, accepting a parameter annotated by a Pandera type hint *and* a parameter annotated by a non-Pandera PEP-compliant type hint. This callable exercises that order of decoration is insignificant. ''' # Return this string parameter appended by an arbitrary constant. return f'{of_some_fierce_maenad}, even from the dim verge' @beartype def from_whose_solid_atmosphere( dataframe: PanderaDataFrame[PanderaModel], of_some_fierce_maenad: str, ) -> str: ''' Arbitrary callable decorated by :mod:`beartype` but *not* by :mod:`pandera`, accepting a parameter annotated by a Pandera type hint *and* a parameter annotated by a non-Pandera PEP-compliant type hint. This callable exercises that the :func:`beartype.beartype` decorator at least shallowly type-checks all passed parameters regardless of whether this callable is also decorated by the :func:`pandera.check_types` decorator. ''' # Return this string parameter appended by an arbitrary constant. return f'{of_some_fierce_maenad}, even from the dim verge' # Tuple of all functions defined above decorated by @pandera.check_types. TEST_FUNCS_PANDERA = ( to_the_zeniths_height, all_thy_congregated_might, ) # Tuple of all functions defined above. TEST_FUNCS = TEST_FUNCS_PANDERA + ( from_whose_solid_atmosphere, ) # ....................{ ASSERTS }.................... # For each function defined above to be tested... for test_func in TEST_FUNCS: # ....................{ PASS }.................... # Assert that calling this function with valid parameters returns the # expected value. assert test_func( dataframe=pandas_dataframe_good, of_some_fierce_maenad='Of some fierce Maenad', ) == 'Of some fierce Maenad, even from the dim verge' # ....................{ FAIL }.................... # Assert that calling this function with an invalid parameter managed by # @beartype raises the expected @beartype exception. with raises(BeartypeCallHintParamViolation): test_func( dataframe=pandas_dataframe_good, of_some_fierce_maenad=b'The locks of the approaching storm.', ) # Assert that calling this function with an invalid parameter shallowly # type-checked by @beartype raises an... exception. # # Currently, Pandera currently fails to perform similar shallow # type-checking. Instead, Pandera silently ignores parameters that are # *NOT* of the expected type. Since this behaviour is both bizarre and # erroneous, future releases of Pandera are likely to at least shallowly # type-check parameters annotated by Pandera type hints. For forward # compatibility, we avoid asserting the exact type of this exception. # with raises(Exception): with raises(BeartypeCallHintParamViolation): test_func( dataframe='Black rain, and fire, and hail will burst: oh hear!', of_some_fierce_maenad=b'The locks of the approaching storm.', ) # ....................{ ASSERTS ~ pandera }.................... # For each function defined above decorated by @pandera.check_types... for test_func_pandera in TEST_FUNCS_PANDERA: # ....................{ FAIL }.................... # Assert that calling this function with an invalid parameter managed by # Pandera raises the expected Pandera exception. with raises(SchemaError): test_func_pandera( dataframe=pandas_dataframe_bad, of_some_fierce_maenad='Of some fierce Maenad', ) # ....................{ MORE }.................... # Functions defined above suffice for the trivial case of type-checking a # data frame passed as a parameter. Let's generalize that with an additional # function also type-checking a series returned as a value. # If the active Python interpreter supports Python >= 3.10 and thus the "|" # type union operator utilized below... if IS_PYTHON_AT_LEAST_3_10: @beartype @check_types def convert_dataframe_column_to_series( dataframe: PanderaDataFrame[PanderaModel], column_name_or_index: str | int, ) -> Series[Int64 | String | Timestamp]: ''' Convert the column of the passed pandas data frame (identified by the passed column name or index) into a pandas series. ''' # Return either... return ( # If the caller passed a column name, the non-series column with # this name. # # Note that columns are *NOT* series; ergo, this return value # intentionally violates this return type hint. Doing so enables # us to assert that @beartype correctly type-checks return # violations involving pandera type hints. dataframe.loc # dataframe.loc[:,column_name_or_index] if isinstance(column_name_or_index, str) else # Else, the caller passed a column index. In this case, the # series converted from the column with this name. # # Note that this return value intentionally satisfies this hint. dataframe.iloc[:,column_name_or_index] ) # Assert that calling this function with valid parameters but returning # an invalid value raises the expected exception. with raises(BeartypeCallHintReturnViolation): convert_dataframe_column_to_series( dataframe=pandas_dataframe_good, column_name_or_index='Hexspeak') # Assert that calling this function with valid parameters and returning # a valid value returns the expected object. pandas_series = convert_dataframe_column_to_series( dataframe=pandas_dataframe_good, column_name_or_index=0) assert len(pandas_series) == 3 beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a80_nonpep/000077500000000000000000000000001461113517100250015ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a80_nonpep/__init__.py000066400000000000000000000000001461113517100271000ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a80_nonpep/test_codenonpep.py000066400000000000000000000200611461113517100305430ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator PEP-noncompliant type hint unit tests.** This submodule unit tests the :func:`beartype.beartype` decorator with respect to **PEP-noncompliant type hints** (i.e., :mod:`beartype`-specific annotations *not* compliant with annotation-centric PEPs). ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TODO }.................... #FIXME: Define new unit tests exercising: #* Python >= 3.8-specific keyword-only parameters. # ....................{ TESTS ~ pass : param : kind }.................... def test_nonpep_param_kind_positional_or_keyword_pass() -> None: ''' Test successful usage of the :func:`beartype.beartype` decorator for a function call passed non-variadic positional and/or keyword parameters annotated with PEP-noncompliant type hints. ''' # Defer test-specific imports. from beartype import beartype # Decorated callable to be exercised. @beartype def slaanesh(daemonette: str, keeper_of_secrets: str) -> str: return daemonette + keeper_of_secrets # Assert that calling this callable with both positional and keyword # arguments returns the expected return value. assert slaanesh( 'Seeker of Decadence', keeper_of_secrets="N'Kari") == ( "Seeker of DecadenceN'Kari") def test_nonpep_param_kind_variadic_and_keyword_only_pass() -> None: ''' Test successful usage of the :func:`beartype.beartype` decorator for a function call passed variadic positional parameters followed by a keyword-only parameter, all annotated with PEP-noncompliant type hints. ''' # Defer test-specific imports. from beartype import beartype # Decorated callable to be exercised. @beartype def changer_of_ways( sky_shark: str, *dark_chronology: int, chaos_spawn: str) -> str: return ( sky_shark + str(dark_chronology[0]) + str(dark_chronology[-1]) + chaos_spawn ) # Assert that calling this callable with variadic positional parameters # followed by a keyword-only parameter returns the expected return value. assert changer_of_ways( 'Screamers', 0, 1, 15, 25, chaos_spawn="Mith'an'driarkh") == ( "Screamers025Mith'an'driarkh") def test_nonpep_param_kind_variadic_fail() -> None: ''' Test unsuccessful usage of the :func:`beartype.beartype` decorator for a function call passed variadic positional parameters annotated with PEP-noncompliant type hints. ''' # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeCallHintParamViolation from pytest import raises # Decorated callable to be exercised. @beartype def imperium_of_man( space_marines: str, *ceaseless_years: int, primarch: str) -> str: return space_marines + str(ceaseless_years[1]) + primarch # Assert that calling this callable with invalid variadic positional # parameters raises the expected exception. with raises(BeartypeCallHintParamViolation): imperium_of_man( 'Legiones Astartes', 30, 31, 36, 'M41', primarch='Leman Russ') # ....................{ TESTS ~ pass : param : type }.................... def test_nonpep_pass_param_tuple() -> None: ''' Test type-checking for a function call successfully passed a parameter annotated with a PEP-noncompliant tuple union. ''' # Import this decorator. from beartype import beartype # Function to be type-checked. For completeness, test both an actual class # *AND* a forward reference to an actual class in this tuple annotation. @beartype def genestealer(tyranid: str, hive_fleet: (str, 'builtins.int')) -> str: return tyranid + str(hive_fleet) # Call this function with each of the two types listed in the above tuple. assert genestealer('Norn-Queen', 'Behemoth') == 'Norn-QueenBehemoth' assert genestealer('Carnifex', 0xDEADBEEF) == 'Carnifex3735928559' def test_nonpep_pass_param_custom() -> None: ''' Test type-checking for a function call successfully passed a parameter annotated as a user-defined class. ''' # Import this decorator. from beartype import beartype # User-defined type. class CustomTestStr(str): pass # Function to be type-checked. @beartype def hrud(gugann: str, delphic_plague: CustomTestStr) -> str: return gugann + delphic_plague # Call this function with each of the above type. assert hrud( 'Troglydium hruddi', delphic_plague=CustomTestStr('Delphic Sink')) == ( 'Troglydium hruddiDelphic Sink') # ....................{ TESTS ~ fail : param : call }.................... def test_nonpep_fail_param_call_tuple() -> None: ''' Test type-checking for a function call unsuccessfully passed a parameter annotated with a PEP-noncompliant tuple union. ''' # Import this decorator. from beartype import beartype from beartype.roar import BeartypeCallHintParamViolation from pytest import raises # Annotated function to be type-checked. @beartype def eldar(isha: str, asuryan: (str, int)) -> str: return isha + asuryan # Call this function with an invalid type and assert the expected # exception. with raises(BeartypeCallHintParamViolation): eldar('Mother of the Eldar', 100.100) # ....................{ TESTS ~ fail : param : hint }.................... def test_nonpep_param_hint_invalid_fail() -> None: ''' Test type-checking for a function with a parameter annotated with a type hint that is neither PEP-compliant *nor* PEP-noncompliant. ''' # Import this decorator. from beartype import beartype from beartype.roar import BeartypeDecorHintNonpepException from beartype_test._util.pytroar import raises_uncached # Assert that type-checking a function with an integer parameter annotation # raises the expected exception. with raises_uncached(BeartypeDecorHintNonpepException): @beartype def nurgle(nurgling: str, great_unclean_one: 0x8BADF00D) -> str: return nurgling + str(great_unclean_one) # ....................{ TESTS ~ fail : return }.................... def test_nonpep_fail_return_call() -> None: ''' Test type-checking for a function call unsuccessfully returning a value annotated with a PEP-noncompliant type hint. ''' # Import this decorator. from beartype import beartype from beartype.roar import BeartypeCallHintReturnViolation from pytest import raises # Annotated function to be type-checked. @beartype def necron(star_god: str, old_one: str) -> str: return 60e6 # Call this function and assert the expected exception. with raises(BeartypeCallHintReturnViolation): necron("C'tan", 'Elder Thing') def test_nonpep_fail_return_hint_nonpep() -> None: ''' Test type-checking for a function with a return value unsuccessfully annotated with a type hint that is neither PEP-compliant *nor* PEP-noncompliant. ''' # Import this decorator. from beartype import beartype from beartype.roar import BeartypeDecorHintNonpepException from beartype_test._util.pytroar import raises_uncached # Assert the expected exception from attempting to type-check a function # with a return annotation that is *NOT* a supported type. with raises_uncached(BeartypeDecorHintNonpepException): @beartype def tzeentch(disc: str, lord_of_change: str) -> 0xB16B00B5: return len(disc + lord_of_change) beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a90_pep/000077500000000000000000000000001461113517100242675ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a90_pep/__init__.py000066400000000000000000000000001461113517100263660ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a90_pep/pep484/000077500000000000000000000000001461113517100253135ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a90_pep/pep484/__init__.py000066400000000000000000000000001461113517100274120ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a90_pep/pep484/test_pep484.py000066400000000000000000000274201461113517100277550ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype decorator :pep:`484`-compliant type hint unit tests. This submodule unit tests the :func:`beartype.beartype` decorator with respect to :pep:`484`-compliant type hints. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS ~ decor : no_type_check }.................... def test_decor_pep484_no_type_check() -> None: ''' Test the :func:`beartype.beartype` decorator against all edge cases of the :pep:`484`-compliant :attr:`typing.no_type_check` decorator. ''' # Defer test-specific imports. from beartype import beartype from beartype.typing import ( Union, no_type_check, ) # Callable decorated by @typing.no_type_check whose otherwise PEP-compliant # type hints *SHOULD* be subsequently ignored by @beartype. @no_type_check def of_beechen_green(and_shadows_numberless: Union[int, str]) -> str: return and_shadows_numberless # The same callable additionally decorated by @beartype. of_beechen_green_beartyped = beartype(of_beechen_green) # Assert these two callables to be the same, implying @beartype silently # reduced to a noop by returning this callable undecorated. assert of_beechen_green is of_beechen_green_beartyped # ....................{ TESTS ~ hint : noreturn }.................... def test_decor_pep484_hint_noreturn() -> None: ''' Test the :func:`beartype.beartype` decorator on synchronous callables against all edge cases of the :pep:`484`-compliant :attr:`typing.NoReturn` type hint, which is valid *only* as an unsubscripted return annotation. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype import beartype from beartype.roar import ( BeartypeCallHintViolation, BeartypeDecorHintPep484Exception, ) from beartype.typing import ( NoReturn, Union, ) from beartype_test._util.pytroar import raises_uncached # ..................{ LOCALS }.................. # Exception guaranteed to be raised *ONLY* by the mending_wall() function. class BeforeIBuiltAWallIdAskToKnow(Exception): pass # Synchronous callable unconditionally raising an exception correctly # annotating its return as "NoReturn". @beartype def mending_wall() -> NoReturn: raise BeforeIBuiltAWallIdAskToKnow( "Something there is that doesn't love a wall,") # Callable explicitly returning a value incorrectly annotating its return # as "NoReturn". @beartype def frozen_ground_swell() -> NoReturn: return 'That sends the frozen-ground-swell under it,' # Callable implicitly returning a value incorrectly annotating its return # as "NoReturn". @beartype def we_do_not_need_the_wall() -> NoReturn: 'There where it is we do not need the wall:' # ..................{ PASS }.................. # Assert this callable raises the expected exception when called. with raises_uncached(BeforeIBuiltAWallIdAskToKnow): mending_wall() # Assert this callable raises the expected exception when called. with raises_uncached(BeartypeCallHintViolation): frozen_ground_swell() # Assert this callable raises the expected exception when called. with raises_uncached(BeartypeCallHintViolation): we_do_not_need_the_wall() # ..................{ FAIL }.................. # Assert this decorator raises the expected exception when decorating a # synchronous callable returning a value incorrectly annotating its return # as "NoReturn". with raises_uncached(BeartypeDecorHintPep484Exception): @beartype def upper_boulders(in_the_sun: NoReturn): return 'And spills the upper boulders in the sun;' # Assert this decorator raises the expected exception when decorating a # synchronous callable returning a value annotating a parameter as a # supported PEP 484-compliant type hint incorrectly subscripted by # "NoReturn". with raises_uncached(BeartypeDecorHintPep484Exception): @beartype def makes_gaps(abreast: Union[str, NoReturn]): return 'And makes gaps even two can pass abreast.' async def test_decor_pep484_hint_noreturn_async() -> None: ''' Test the :func:`beartype.beartype` decorator on asynchronous callables against all edge cases of the :pep:`484`-compliant :attr:`typing.NoReturn` type hint, which is valid *only* as an unsubscripted return annotation. ''' # Defer test-specific imports. from beartype import beartype from beartype.typing import NoReturn # Asynchronous coroutine unconditionally raising an exception correctly # annotating its return as "NoReturn". @beartype async def work_of_hunters(another_thing) -> NoReturn: raise ValueError('The work of hunters is another thing:') # ....................{ TESTS ~ hint : sequence }.................... def test_decor_pep484_namedtuple() -> None: ''' Test the :func:`beartype.beartype` decorator against all edge cases of instances of user-defined subclasses of the :pep:`484`-compliant :attr:`typing.NamedTuple` superclass, which are instances rather than types and thus invalid as actual type hints. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeCallHintParamViolation from typing import ( NamedTuple, Optional, ) from pytest import raises # ..................{ LOCALS }.................. @beartype class WindsContend(NamedTuple): ''' Arbitrary named tuple type-checked by :func:`beartype.beartype`. ''' starbeams: Optional[str] ''' Standard instance variable passed by the :class:`NamedTuple` metaclass to the the implicit ``__new__()`` method synthesized for this subclass. ''' # Arbitrary instance of this named tuple exercising all edge cases. dart_through_them = WindsContend( starbeams='Or the star-beams dart through them. Winds contend') # ..................{ PASS }.................. # Assert this dataclass defines the expected attributes. assert dart_through_them.starbeams == ( 'Or the star-beams dart through them. Winds contend') # ..................{ FAIL }.................. # Assert that attempting to instantiate an instance of this dataclass with a # parameter violating the corresponding type hint annotating the field of # the same name raises the expected exception. with raises(BeartypeCallHintParamViolation): WindsContend(starbeams=0xBABECAFE) def test_decor_pep484_hint_sequence_args_1_cached() -> None: ''' Test that a `subtle issue `__ of the :func:`beartype.beartype` decorator with respect to metadata describing **PEP-compliant standard sequence hints** (e.g., :attr:`typing.List`) cached via memoization across calls to that decorator has been resolved and *not* regressed. Note that more general-purpose :pep:`484` unit tests *should* already exercise this issue, but that this issue was sufficiently dire to warrant special-purposed testing exercising this exact issue. .. _issue #5: https://github.com/beartype/beartype/issues/5 ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from beartype.typing import Union # ....................{ CALLABLES }.................... # Callable annotated by an arbitrary PEP 484 standard sequence type hint. @beartype def fern_hill(prince_of_the_apple_towns: Union[int, str]) -> str: return prince_of_the_apple_towns # A different callable annotated by the same hint and another arbitrary # non-"typing" type hint. @beartype def apple_boughs( famous_among_the_barns: Union[int, str], first_spinning_place: str ) -> str: return famous_among_the_barns + first_spinning_place # ....................{ PASS }.................... # Validate that these callables behave as expected. assert fern_hill( 'Now as I was young and easy under the apple boughs' 'About the lilting house and happy as the grass was green,' ' The night above the dingle starry,' ' Time let me hail and climb' ' Golden in the heydays of his eyes,' 'And honoured among wagons I was prince of the apple towns' 'And once below a time I lordly had the trees and leaves' ' Trail with daisies and barley' ' Down the rivers of the windfall light. ' ).startswith('Now as I was young and easy under the apple boughs') assert apple_boughs(( 'And as I was green and carefree, famous among the barns' 'About the happy yard and singing as the farm was home,' ' In the sun that is young once only,' ' Time let me play and be' ' Golden in the mercy of his means,' 'And green and golden I was huntsman and herdsman, the calves' 'Sang to my horn, the foxes on the hills barked clear and cold,' ' And the sabbath rang slowly' ' In the pebbles of the holy streams.' ), 'All the sun long it was running, it was lovely, the hay').startswith( 'And as I was green and carefree, famous among the barns') # ....................{ TESTS ~ hint : invalid }.................... #FIXME: Excise us up. We're unconvinced that arbitrarily prohibiting *ANY* #isinstanceable classes from being used as type hints is a useful approach -- #even if those classes are "typing" attributes typically intended to be #instantiated when used as type hints. Nobody's here to tell anybody they can't #do something that otherwise appears sensible. # #When we next read this comment, refactor our test suite as follows: #* Remove this test. #* Remove the # "beartype_test.a00_unit.data.hint.pep.data_pep.HINTS_PEP_INVALID_TYPE_NONGENERIC" # frozen set entirely. # def test_pep484_hint_invalid_types_nongeneric() -> None: # ''' # Test the :func:`beartype.beartype` decorator against **invalid non-generic # classes** (i.e., classes declared by the :mod:`typing` module used to # instantiate PEP-compliant type hints but themselves invalid as # PEP-compliant type hints). # ''' # # # Defer test-specific imports. # from beartype import beartype # from beartype.roar import BeartypeDecorHintPepSignException # from beartype_test.a00_unit.data.hint.pep.data_pep import ( # HINTS_PEP_INVALID_TYPE_NONGENERIC) # # # Assert that decorating a callable annotated by a non-generic class raises # # the expected exception. # for type_nongeneric in HINTS_PEP_INVALID_TYPE_NONGENERIC: # with raises_uncached(BeartypeDecorHintPepSignException): # @beartype # def childe_roland(to_the_dark_tower_came: type_nongeneric) -> ( # type_nongeneric): # raise to_the_dark_tower_came beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a90_pep/pep484/test_pep484ref.py000066400000000000000000000345651461113517100304620ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator forward reference unit tests.** This submodule unit tests the :func:`beartype.beartype` decorator with respect to both PEP-compliant and -noncompliant **forward reference type hints** (i.e., strings whose values are the names of classes and tuples of classes that typically have yet to be defined). ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_pep484_ref_data() -> None: ''' Test successful usage of the :func:`beartype.beartype` decorator with respect to both PEP-compliant and -noncompliant forward references by importing an external data module declaring these references *before* the user-defined classes referred to by these references. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype.roar import BeartypeCallHintReturnViolation from beartype_test.a00_unit.data.hint.data_hintref import ( BeforeTheHurricane, TheDarkestEveningOfTheYear, WithSluggishSurge, a_little_shallop, but_i_have_promises, its_fields_of_snow, of_easy_wind, stopping_by_woods_on, the_dry_leaf, the_woods_are_lovely, winding_among_the_springs, # between_the_woods_and_frozen_lake, ) from pytest import raises # ..................{ LOCALS }.................. # Objects passed below to exercise forward references. MILES_TO_GO = TheDarkestEveningOfTheYear('And miles to go before I sleep') WOODS = TheDarkestEveningOfTheYear('The woods are lovely, dark and deep,') LAKE = TheDarkestEveningOfTheYear('Between the woods and frozen lake') KNOW = TheDarkestEveningOfTheYear('Whose woods these are I think I know.') WITH_BURNING_SMOKE = [ TheDarkestEveningOfTheYear('With burning smoke, or where bitumen lakes'), TheDarkestEveningOfTheYear('On black bare pointed islets ever beat'), ] RUGGED_AND_DARK = WithSluggishSurge() # ..................{ PASS }.................. # Assert these forward-referencing callables return the expected values. assert a_little_shallop(WITH_BURNING_SMOKE) is WITH_BURNING_SMOKE assert but_i_have_promises(MILES_TO_GO) is MILES_TO_GO assert of_easy_wind(WOODS) is WOODS assert stopping_by_woods_on(LAKE) is LAKE assert the_woods_are_lovely(KNOW) is KNOW assert its_fields_of_snow(WITH_BURNING_SMOKE) is WITH_BURNING_SMOKE[0] assert the_dry_leaf(TheDarkestEveningOfTheYear) is ( TheDarkestEveningOfTheYear) assert RUGGED_AND_DARK.or_where_the_secret_caves() is RUGGED_AND_DARK assert winding_among_the_springs(RUGGED_AND_DARK) is RUGGED_AND_DARK # ..................{ FAIL }.................. # Assert that calling a method violating its return annotated as a 2-tuple # of type variables whose bounds are expressed as PEP-compliant relative # forward references to the same class raises the expected violation. with raises(BeartypeCallHintReturnViolation): BeforeTheHurricane().in_a_silver_vision_floats() #FIXME: Disabled until we decide whether we want to bother trying to #resolve nested forward references or not. # # ..................{ NESTED }.................. # # 3-tuple of closures and classes nested in this callable. # (to_stop_without, to_watch_his_woods, WhoseWoodsTheseAreIThinkIKnow) = ( # between_the_woods_and_frozen_lake()) # # # Objects passed below to exercise nested forward references. # MY_LITTLE_HORSE = WhoseWoodsTheseAreIThinkIKnow( # 'My little horse must think it queer') # STOP = WhoseWoodsTheseAreIThinkIKnow('To stop without a farmhouse near') # # # Assert these forward-referencing closures return the expected values. # assert to_stop_without(MY_LITTLE_HORSE) == MY_LITTLE_HORSE # assert to_watch_his_woods(STOP) == STOP # ....................{ TESTS ~ pass }.................... def test_pep484_ref_arg_pass() -> None: ''' Test successful usage of the :func:`beartype.beartype` decorator for a callable passed a parameter annotated with a PEP-noncompliant fully-qualified forward reference referencing an existing attribute of an external module. ''' # ..................{ IMPORTS }.................. # Import this decorator. from beartype import beartype # ..................{ LOCALS }.................. # Dates between which the Sisters of Battle must have been established. ESTABLISHMENT_DATE_MIN = 36000 ESTABLISHMENT_DATE_MAX = 37000 # ..................{ FUNCTIONS }.................. # Function to be type-checked. @beartype def sisters_of_battle( leader: str, establishment: 'random.Random') -> int: return establishment.randint( ESTABLISHMENT_DATE_MIN, ESTABLISHMENT_DATE_MAX) # Import the stdlib module referenced above *AFTER* that forward reference. from random import Random # ..................{ PASS }.................. # Call this function with an instance of the type named above. assert sisters_of_battle('Abbess Sanctorum', Random()) in range( ESTABLISHMENT_DATE_MIN, ESTABLISHMENT_DATE_MAX + 1) # ....................{ TESTS ~ fail }.................... def test_pep484_ref_decor_fail() -> None: ''' Test unsuccessful decorator-time usage of the :func:`beartype.beartype` decorator with respect to both PEP-compliant and -noncompliant forward references. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeDecorHintForwardRefException from beartype_test._util.pytroar import raises_uncached # ..................{ FAIL }.................. #FIXME: Uncomment if and when a future Python release unconditionally #enables some variant of PEP 563... yet again. # from beartype.roar import ( # BeartypeDecorHintForwardRefException, # BeartypePep563Exception, # ) # from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_10 # # # Type of exception raised by @beartype when decorating callables annotated # # by syntactically invalid forward reference type hints. Due to ambiguities # # in PEP 563 unconditionally enabled under Python >= 3.10, @beartype is # # unable to reliably differentiate forward references from non-forward # # references and thus treats the former as the latter here. # exception_cls = ( # BeartypePep563Exception # if IS_PYTHON_AT_LEAST_3_10 else # BeartypeDecorHintForwardRefException # ) # Type of exception raised by @beartype when decorating callables annotated # by syntactically invalid forward reference type hints. exception_cls = BeartypeDecorHintForwardRefException # Assert @beartype raises the expected exception when decorating a callable # annotated by a syntactically invalid forward reference type hint. with raises_uncached(exception_cls): @beartype def linnets_wings(evening_full: ( "There midnight’s all a glimmer, and noon a purple glow,")): return evening_full # Assert @beartype raises the expected exception when decorating a callable # annotated by a mildly syntactically invalid forward reference type hint. with raises_uncached(exception_cls): @beartype def deep_hearts_core(i_hear_it: ( 'While.I.stand.on.the.roadway.or.on.the.pavements.0grey')): return i_hear_it def test_pep484_ref_call_fail() -> None: ''' Test unsuccessful call-time usage of the :func:`beartype.beartype` decorator with respect to both PEP-compliant and -noncompliant forward references. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeCallHintForwardRefException from beartype.typing import Union from beartype_test._util.pytroar import raises_uncached # ..................{ FAIL }.................. # Decorated callable annotated by a PEP-noncompliant fully-qualified # forward reference referring to a non-existent type. TwoForwardRefsDivergedInAYellowWood = ( 'beartype_test.TwoRoadsDivergedInAYellowWood') @beartype def the_road(not_taken: TwoForwardRefsDivergedInAYellowWood) -> ( TwoForwardRefsDivergedInAYellowWood): return not_taken # Assert calling this callable raises the expected exception. with raises_uncached(BeartypeCallHintForwardRefException): the_road('Two roads diverged in a wood, and I—') # Decorated callable annotated by a PEP-noncompliant tuple containing # standard types and a fully-qualified forward reference referring to a # non-existent type. AndBothForwardRefsThatMorningEquallyLay = ( complex, TwoForwardRefsDivergedInAYellowWood, bool) @beartype def in_leaves_no_step( had_trodden_black: AndBothForwardRefsThatMorningEquallyLay) -> ( AndBothForwardRefsThatMorningEquallyLay): return had_trodden_black # Assert calling this callable raises the expected exception. with raises_uncached(BeartypeCallHintForwardRefException): in_leaves_no_step('I took the one less traveled by,') # Decorated callable annotated by a PEP-compliant unnested unqualified # forward reference referring to a non-existent type. @beartype def yet_knowing_how_way( leads_on_to_way: 'OhIKeptTheFirstForAnotherDay') -> ( 'OhIKeptTheFirstForAnotherDay'): return leads_on_to_way # Assert calling this callable raises the expected exception. with raises_uncached(BeartypeCallHintForwardRefException): yet_knowing_how_way('And that has made all the difference.') # Decorated callable annotated by a PEP-compliant unnested unqualified # forward reference referring to a non-existent type. IShallBeTellingThisForwardRefWithASigh = Union[ complex, 'IShallBeTellingThisWithASigh', bytes] @beartype def somewhere_ages( and_ages_hence: IShallBeTellingThisForwardRefWithASigh) -> ( IShallBeTellingThisForwardRefWithASigh): return and_ages_hence # Assert calling this callable raises the expected exception. with raises_uncached(BeartypeCallHintForwardRefException): somewhere_ages('I doubted if I should ever come back.') def test_pep484_ref_call_arg_fail() -> None: ''' Test unsuccessful call-time usage of the :func:`beartype.beartype` decorator for callables passed parameters annotated with PEP-noncompliant fully-qualified forward references referencing module attributes exercising erroneous edge cases. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype import beartype from beartype.roar import ( BeartypeCallHintForwardRefException, BeartypeCallHintParamViolation, ) from beartype_test._util.pytroar import raises_uncached # ..................{ LOCALS }.................. # Dates between which the Black Legion must have been established. ESTABLISHMENT_DATE_MIN = 30000 ESTABLISHMENT_DATE_MAX = 31000 # ..................{ FUNCTIONS }.................. @beartype def black_legion(primarch: str, establishment: 'random.Random') -> int: ''' Arbitrary callable annotated with a forward reference type hint referencing an existing class of an importable module. ''' return establishment.randint( ESTABLISHMENT_DATE_MIN, ESTABLISHMENT_DATE_MAX) @beartype def eye_of_terror( ocularis_terribus: str, # While highly unlikely that a top-level module with this name will # ever exist, the possibility cannot be discounted. Since there appears # to be no syntactically valid module name prohibited from existing, # this is probably the best we can do. segmentum_obscurus: '__rand0m__.Warp', ) -> str: ''' Arbitrary callable annotated with a forward reference type hint referencing a non-existent attribute of a non-existent module. ''' return ocularis_terribus + segmentum_obscurus # Callable with a forward reference type hint referencing a missing # attribute of an importable module. @beartype def navigator( astronomicon: str, # While highly unlikely that a top-level module attribute with this # name will ever exist, the possibility cannot be discounted. Since # there appears to be no syntactically valid module attribute name # prohibited from existing, this is probably the best we can do. navis_nobilite: 'random.Psych1c__L1ght__', ) -> str: ''' Arbitrary callable annotated with a forward reference type hint referencing a non-existent attribute of an importable module. ''' return astronomicon + navis_nobilite # ..................{ FAIL }.................. # Assert call these callables raise the expected exceptions. with raises_uncached(BeartypeCallHintParamViolation): black_legion('Horus', 'Abaddon the Despoiler') with raises_uncached(BeartypeCallHintForwardRefException): eye_of_terror('Perturabo', 'Crone Worlds') with raises_uncached(BeartypeCallHintForwardRefException): navigator('Homo navigo', 'Kartr Hollis') beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a90_pep/test_decorpep3119.py000066400000000000000000000123231461113517100300200ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype :pep:`3119` unit tests. This submodule unit tests :pep:`3119` support implemented in the :func:`beartype.beartype` decorator. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_decor_pep3119() -> None: ''' Test :pep:`3119` support implemented in the :func:`beartype.beartype` decorator -- particularly with respect to **partially initialized metaclasses** (i.e., metaclasses with ``__instancecheck__()` and/or ``__subclasscheck__()`` dunder methods that are *not* safely callable at the early decoration time that :func:`beartype.beartype` attempts to call those methods). ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeDecorHintPep3119Exception from beartype.typing import ( Tuple, Type, ) from pytest import raises # Intentionally import from the "typing" module to allow the definition of # an uncheckable protocol below. from typing import Protocol # ....................{ CLASSES }.................... class Pep3119Metaclass(type): ''' Arbitrary :pep:`3119`-compliant metaclass guaranteed to be only partially initialized at the early decoration time that :func:`beartype.beartype` attempts to call the :meth:`__instancecheck__` and :meth:`__subclasscheck__` dunder methods defined below. ''' def __instancecheck__(cls: type, other: object) -> bool: return each_like_a_corpse_within_its_grave() def __subclasscheck__(cls: type, other: type) -> bool: return each_like_a_corpse_within_its_grave() class Pep3119Class(metaclass=Pep3119Metaclass): ''' Arbitrary class whose metaclass is a :pep:`3119`-compliant metaclass guaranteed to be only partially initialized. ''' class NonisinstanceableProtocol(Protocol): ''' Arbitrary **non-isinstanceable protocol** (i.e., protocol *not* decorated by the :func:`typing.runtime_checkable` decorator and hence *not* checkable at runtime, as doing so raises a :exc:`TypeError` from the ``__instancecheck__`` dunder method of the metaclass of this protocol). ''' pass # ....................{ FUNCTIONS }.................... # Implicitly assert that decorating a function annotated by a partially # initialized PEP 3119-compliant metaclass does *NOT* raise an exception. @beartype def winged_seeds( # Implicitly invoke the __instancecheck__() method defined above. where_they_lie_cold_and_low: Pep3119Class, # Implicitly invoke the __subclasscheck__() method defined above. driving_sweet_buds_like_flocks_to_feed_in_air: Type[Pep3119Class], ) -> Tuple[Pep3119Class, Type[Pep3119Class]]: ''' Arbitrary function annotated as accepting a parameter that is an instance of an arbitrary class whose metaclass is a :pep:`3119`-compliant metaclass guaranteed to be only partially initialized at this early decoration time. ''' return ( where_they_lie_cold_and_low, driving_sweet_buds_like_flocks_to_feed_in_air, ) def each_like_a_corpse_within_its_grave() -> bool: ''' Arbitrary boolean function. ''' return True # ....................{ PASS }.................... # Arbitrary instance of this class. thine_azure_sister_of_the_Spring_shall_blow = Pep3119Class() # Assert that calling the decorated function defined above succeeds. assert winged_seeds( thine_azure_sister_of_the_Spring_shall_blow, Pep3119Class) == ( thine_azure_sister_of_the_Spring_shall_blow, Pep3119Class) # ....................{ FAIL }.................... # Assert that decorating a function annotated as accepting a parameter whose # value is an instance of a non-isinstanceable class raises the expected # exception. with raises(BeartypeDecorHintPep3119Exception): @beartype def her_clarion_over_the_dreaming_earth( and_lie: NonisinstanceableProtocol) -> None: pass # Assert that decorating a function annotated as accepting a parameter whose # value is a non-issubclassable class raises the expected exception. with raises(BeartypeDecorHintPep3119Exception): @beartype def with_living_hues_and_odours_plain( and_hill: Type[NonisinstanceableProtocol]) -> None: pass beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a90_pep/test_decorpep435663.py000066400000000000000000000061601461113517100301770ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator** :pep:`435`- and :pep:`663`-compliant **enumeration unit tests**. This submodule unit tests :pep:`435` and :pep:`663` support implemented in the :func:`beartype.beartype` decorator. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype_test._util.mark.pytskip import skip_if_python_version_less_than # ....................{ TESTS }.................... def test_decor_pep435() -> None: ''' Test :pep:`435` (i.e., :class:`enum.Enum`) support implemented in the :func:`beartype.beartype` decorator. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from enum import ( Enum, auto, ) # ....................{ ENUMERATIONS }.................... # Assert that @beartype properly decorates PEP 435-compliant enumerations. @beartype class BreathedOverHisDarkFate(Enum): ''' Arbitrary :obj:`enum.auto`-based enumeration decorated by the :func:`beartype.beartype` decorator. ''' one_melodious_sigh = auto() he_lived_he_died_he_sung_in_solitude = auto() @beartype class StrangersHaveWeptToHear(str, Enum): ''' Arbitrary string-based enumeration decorated by the :func:`beartype.beartype` decorator. Note that this type intentionally subclasses the immutable :class:`str` type -- exercising an edge case in decoration of immutable types. ''' his_passionate_notes = 'And virgins' as_unknown_he_passed = 'have pined' @skip_if_python_version_less_than('3.11.0') def test_decor_pep663() -> None: ''' Test :pep:`663` (i.e., :class:`enum.StrEnum`) support implemented in the :func:`beartype.beartype` decorator. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from enum import StrEnum # ....................{ ENUMERATIONS }.................... # Assert that @beartype properly decorates PEP 663-compliant enumerations. @beartype class AndWastedForFondLove(StrEnum): ''' Arbitrary string-based enumeration decorated by the :func:`beartype.beartype` decorator. Note that this enumeration intentionally subclasses the immutable :class:`.StrEnum` superclass -- exercising an edge case in decoration of immutable types. ''' of_his_wild_eyes = 'The fire of' those_soft_orbs = 'has ceased to burn,' beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a90_pep/test_decorpep484585.py000066400000000000000000000471341461113517100302140ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype decorator :pep:`484`- and :pep:`585`-compliant unit tests. This submodule unit tests :pep:`484` and :pep:`585` support implemented in the :func:`beartype.beartype` decorator. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype.roar import BeartypeDecorHintPep585DeprecationWarning from beartype_test._util.mark.pytmark import ignore_warnings # ....................{ TESTS ~ decor : async }.................... # Prevent pytest from capturing and displaying all expected non-fatal # beartype-specific warnings emitted by the @beartype decorator below. Urgh! @ignore_warnings(BeartypeDecorHintPep585DeprecationWarning) async def test_decor_async_coroutine() -> None: ''' Test decorating coroutines with the :func:`beartype.beartype` decorator. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from asyncio import sleep from beartype import beartype from beartype.roar import BeartypeDecorHintPep585Exception from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_9 from beartype_test._util.pytroar import raises_uncached from collections.abc import Coroutine as Pep585Coroutine from typing import Union, Coroutine as Pep484Coroutine # ....................{ LOCALS }.................... # Decorated coroutine whose return is annotated with an arbitrary # PEP-compliant type hint. @beartype async def control_the_car( said_the: Union[str, int], biggest_greenest_bat: Union[str, float], ) -> Union[str, float]: await sleep(0) return said_the + biggest_greenest_bat # Decorated coroutine whose return is annotated with a PEP 484-compliant # coroutine type hint. @beartype async def universal_love( said_the: Union[str, int], cactus_person: Union[str, float]) -> ( Pep484Coroutine[None, None, Union[str, float]]): await sleep(0) return said_the + cactus_person # ....................{ PASS }.................... # Assert awaiting this coroutine returns the expected value. assert await control_the_car( 'I saw the big green bat bat a green big eye. ', 'Suddenly I knew I had gone too far.') == ( 'I saw the big green bat bat a green big eye. ' 'Suddenly I knew I had gone too far.') # Assert awaiting this coroutine returns the expected value. assert await universal_love( 'The sea was made of strontium; ', 'the beach was made of rye.') == ( 'The sea was made of strontium; the beach was made of rye.') # ....................{ VERSION }.................... # If the active Python interpreter targets Python >= 3.9 and thus supports # PEP 585... if IS_PYTHON_AT_LEAST_3_9: # ....................{ LOCALS }.................... # Decorated coroutine whose return is annotated with a PEP # 585-compliant coroutine type hint. @beartype async def transcendent_joy( said_the: Union[str, float], big_green_bat: Union[str, int]) -> ( Pep585Coroutine[None, None, Union[str, float]]): await sleep(0) return said_the + big_green_bat # ....................{ PASS }.................... # Assert awaiting this coroutine returns the expected value. assert await transcendent_joy( 'A thousand stars of sertraline ', 'whirled round quetiapine moons' ) == 'A thousand stars of sertraline whirled round quetiapine moons' # ....................{ FAIL }.................... # Assert @beartype raises the expected exception when decorating a # coroutine whose return is annotated with a PEP 585-compliant # coroutine type hint *NOT* subscripted by exactly three child hints. with raises_uncached(BeartypeDecorHintPep585Exception): @beartype async def with_each_planck_moment_ever_fit() -> ( Pep585Coroutine['to be eternally enjoyed']): await sleep(0) return 'Time will decay us but time can be left blank' # Prevent pytest from capturing and displaying all expected non-fatal # beartype-specific warnings emitted by the @beartype decorator below. Hurk! @ignore_warnings(BeartypeDecorHintPep585DeprecationWarning) async def test_decor_async_generator() -> None: ''' Test decorating asynchronous generators with the :func:`beartype.beartype` decorator. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from asyncio import sleep from beartype import beartype from beartype.roar import BeartypeDecorHintPep484585Exception from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_9 from beartype_test._util.pytroar import raises_uncached from beartype.typing import ( AsyncGenerator as AsyncGeneratorUnsubscripted, ) from collections.abc import ( AsyncGenerator as Pep585AsyncGenerator, AsyncIterable as Pep585AsyncIterable, AsyncIterator as Pep585AsyncIterator, ) from typing import ( Union, AsyncGenerator as Pep484AsyncGenerator, AsyncIterable as Pep484AsyncIterable, AsyncIterator as Pep484AsyncIterator, ) # ....................{ LOCALS }.................... #FIXME: Refactor this unwieldy and unmaintainable DRY violation by iterating #over a tuple of all return type hints to be tested: e.g., # RETURN_HINTS = ( # AsyncGeneratorUnsubscripted, # Pep484AsyncGenerator[Union[str, float], None], # ) # # for return_hint in RETURN_HINTS: # @beartype # async def some_kind_of_spiritual_thing( # said_the: Union[str, int], # bigger_greener_bat: Union[str, float] # ) -> return_hint: # await sleep(0) # yield said_the + bigger_greener_bat # Decorated asynchronous generators whose returns are annotated with PEP # 484-compliant "AsyncGenerator[...]", "AsyncIterable[...]", and # "AsyncIterator[...]" type hints (respectively). @beartype async def some_kind_of_spiritual_thing( said_the: Union[str, int], bigger_greener_bat: Union[str, float]) -> ( AsyncGeneratorUnsubscripted): await sleep(0) yield said_the + bigger_greener_bat @beartype async def not_splitting_numbers( said_the: Union[str, int], bigger_greener_bat: Union[str, float]) -> ( Pep484AsyncGenerator[Union[str, float], None]): await sleep(0) yield said_the + bigger_greener_bat @beartype async def chaos_never_comes_from_the_ministry_of_chaos( said_the: Union[str, int], bigger_greener_bat: Union[str, float]) -> ( Pep484AsyncIterable[Union[str, float]]): await sleep(0) yield said_the + bigger_greener_bat @beartype async def nor_void_from_the_ministry_of_void( said_the: Union[str, int], bigger_greener_bat: Union[str, float]) -> ( Pep484AsyncIterator[Union[str, float]]): await sleep(0) yield said_the + bigger_greener_bat # ....................{ PASS }.................... # Assert awaiting these asynchronous generators return the expected values. # Unlike synchronous generators, asynchronous generators are *NOT* actually # iterators and thus have *NO* clean analogue to the iter() and next() # builtins. The closest approximation is this rather unclean hack: # await not_splitting_number.__anext__() # See also this relevant StackOvelflow post: # https://stackoverflow.com/a/42561322/2809027 async for some_kind_of_spiritual in some_kind_of_spiritual_thing( 'I should be trying to do some kind of spiritual thing ', 'involving radical acceptance and enlightenment and such.', ): assert some_kind_of_spiritual == ( 'I should be trying to do some kind of spiritual thing ' 'involving radical acceptance and enlightenment and such.' ) async for not_splitting_number in not_splitting_numbers( 'the sand sizzled sharp like cooking oil that hissed and sang and ', 'threatened to boil the octahedral dunes.', ): assert not_splitting_number == ( 'the sand sizzled sharp like cooking oil that hissed and sang and ' 'threatened to boil the octahedral dunes.' ) async for chaos in chaos_never_comes_from_the_ministry_of_chaos( 'The force of the blast went rattling past the bat and the beach, ', 'disturbing each,' ): assert chaos == ( 'The force of the blast went rattling past the bat and the beach, ' 'disturbing each,' ) async for void in nor_void_from_the_ministry_of_void( 'then made its way to a nearby bay of upside-down trees ', 'with their roots in the breeze and their branches underground.' ): assert void == ( 'then made its way to a nearby bay of upside-down trees ' 'with their roots in the breeze and their branches underground.' ) # ....................{ FAIL }.................... # Assert this decorator raises the expected exception when decorating an # asynchronous generator annotating its return as anything *EXCEPT* # "AsyncGenerator[...]", "AsyncIterable[...]", and "AsyncIterator[...]". with raises_uncached(BeartypeDecorHintPep484585Exception): @beartype async def upside_down_trees( roots_in_the_breeze: str, branches_underground: str) -> str: await sleep(0) yield roots_in_the_breeze + branches_underground # ....................{ VERSION }.................... # If the active Python interpreter targets Python >= 3.9 and thus supports # PEP 585... if IS_PYTHON_AT_LEAST_3_9: # ....................{ LOCALS }.................... # Decorated asynchronous generators whose returns are annotated with # PEP 585-compliant "AsyncGenerator[...]", "AsyncIterable[...]", and # "AsyncIterator[...]" type hints (respectively). @beartype async def but_joining_mind( said_the: Union[str, float], bigger_greener_bat: Union[str, int], ) -> Pep585AsyncGenerator[Union[str, float], None]: await sleep(0) yield said_the + bigger_greener_bat @beartype async def lovers_do_not_love_to_increase( said_the: Union[str, float], bigger_greener_bat: Union[str, int], ) -> Pep585AsyncIterable[Union[str, float]]: await sleep(0) yield said_the + bigger_greener_bat async def the_amount_of_love_in_the_world( said_the: Union[str, float], bigger_greener_bat: Union[str, int], ) -> Pep585AsyncIterator[Union[str, float]]: await sleep(0) yield said_the + bigger_greener_bat # ....................{ PASS }.................... # Assert awaiting these asynchronous generators return the expected # values. async for but_joining_time in but_joining_mind( 'A meteorite of pure delight ', 'struck the sea without a sound.'): assert but_joining_time == ( 'A meteorite of pure delight struck the sea without a sound.') async for the_mind_that_thrills in lovers_do_not_love_to_increase( 'The sea turned hot ', 'and geysers shot up from the floor below.' ): assert the_mind_that_thrills == ( 'The sea turned hot and geysers shot up from the floor below.') async for the_face_of_the_beloved in the_amount_of_love_in_the_world( 'First one of wine, then one of brine, ', 'then one more yet of turpentine, and we three stared at the show.' ): assert the_face_of_the_beloved == ( 'First one of wine, ' 'then one of brine, ' 'then one more yet of turpentine, ' 'and we three stared at the show.' ) # ....................{ TESTS ~ decor : sync }.................... # Prevent pytest from capturing and displaying all expected non-fatal # beartype-specific warnings emitted by the @beartype decorator below. Blargh! @ignore_warnings(BeartypeDecorHintPep585DeprecationWarning) def test_decor_sync_generator() -> None: ''' Test decorating synchronous generators with the :func:`beartype.beartype` decorator. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeDecorHintPep484585Exception from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_9 from beartype_test._util.pytroar import raises_uncached from beartype.typing import ( Generator as GeneratorUnsubscripted, ) from collections.abc import ( Generator as Pep585Generator, Iterable as Pep585Iterable, Iterator as Pep585Iterator, ) from typing import ( Union, Generator as Pep484Generator, Iterable as Pep484Iterable, Iterator as Pep484Iterator, ) # ....................{ LOCALS }.................... #FIXME: Refactor this unwieldy and unmaintainable DRY violation by iterating #over a tuple of all return type hints to be tested: e.g., # RETURN_HINTS = ( # GeneratorUnsubscripted, # Pep484Generator[Union[str, float], None], # ) # # for return_hint in RETURN_HINTS: # @beartype # def western_logocentric_stuff( # said_the: Union[str, int], # bigger_greener_bat: Union[str, float] # ) -> return_hint: # await sleep(0) # yield said_the + bigger_greener_bat # Decorated synchronous generators whose returns are annotated with PEP # 484-compliant "Generator[...]", "Iterable[...]", and "Iterator[...]" type # hints (respectively). @beartype def western_logocentric_stuff( said_the: Union[str, int], bigger_greener_bat: Union[str, float]) -> ( GeneratorUnsubscripted): yield said_the + bigger_greener_bat @beartype def not_facts_or_factors_or_factories( said_the: Union[str, int], bigger_greener_bat: Union[str, float]) -> ( Pep484Generator[Union[str, float], None, None]): yield said_the + bigger_greener_bat @beartype def not_to_seek( said_the: Union[str, int], bigger_greener_bat: Union[str, float]) -> ( Pep484Iterable[Union[str, float]]): yield said_the + bigger_greener_bat @beartype def not_to_follow( said_the: Union[str, int], bigger_greener_bat: Union[str, float]) -> ( Pep484Iterator[Union[str, float]]): yield said_the + bigger_greener_bat # ....................{ PASS }.................... # Assert awaiting these synchronous generators yield the expected values # when iterated. assert next(western_logocentric_stuff( 'all my Western logocentric stuff ', 'about factoring numbers', )) == 'all my Western logocentric stuff about factoring numbers' assert next(not_facts_or_factors_or_factories( 'The watery sun began to run ', 'and it fell on the ground as rain.', )) == 'The watery sun began to run and it fell on the ground as rain.' assert next(not_to_seek( 'At the sound of that, ', 'the big green bat started rotating in place.', )) == ( 'At the sound of that, the big green bat started rotating in place.') assert next(not_to_follow( 'On its other side was a bigger greener bat, ', 'with an ancient, wrinkled face.' )) == ( 'On its other side was a bigger greener bat, ' 'with an ancient, wrinkled face.' ) # ....................{ FAIL }.................... # Assert this decorator raises the expected exception when decorating a # synchronous generator annotating its return as anything *EXCEPT* # "Generator[...]", "Iterable[...]", and "Iterator[...]". with raises_uncached(BeartypeDecorHintPep484585Exception): @beartype def GET_OUT_OF_THE_CAR( FOR_THE_LOVE_OF_GOD: str, FACTOR_THE_NUMBER: str) -> str: yield FOR_THE_LOVE_OF_GOD + FACTOR_THE_NUMBER # ....................{ VERSION }.................... # If the active Python interpreter targets Python >= 3.9 and thus supports # PEP 585... if IS_PYTHON_AT_LEAST_3_9: # ....................{ LOCALS }.................... # Decorated synchronous generators whose returns are annotated with PEP # 585-compliant "Generator[...]", "Iterable[...]", and "Iterator[...]" # type hints (respectively). @beartype def contact_with_the_abstract_attractor( said_the: Union[str, float], bigger_greener_bat: Union[str, int], ) -> Pep585Generator[Union[str, float], None, None]: yield said_the + bigger_greener_bat @beartype def but_to_jump_forth_into_the_deep( said_the: Union[str, float], bigger_greener_bat: Union[str, int], ) -> Pep585Iterable[Union[str, float], None, None]: yield said_the + bigger_greener_bat @beartype def not_to_grind_or_to_bind_or_to_seek( said_the: Union[str, float], bigger_greener_bat: Union[str, int], ) -> Pep585Iterator[Union[str, float], None, None]: yield said_the + bigger_greener_bat # ....................{ PASS }.................... # Assert these synchronous generators yield the expected values when # iterated. assert next(contact_with_the_abstract_attractor( 'Then tree and beast all fled due east and ', 'the moon and stars shot south.', )) == ( 'Then tree and beast all fled due east and ' 'the moon and stars shot south.' ) assert next(but_to_jump_forth_into_the_deep( 'The big green bat started to turn around ', 'what was neither its x, y, or z axis,' )) == ( 'The big green bat started to turn around ' 'what was neither its x, y, or z axis,' ) assert next(not_to_grind_or_to_bind_or_to_seek( 'slowly rotating to reveal what was undoubtedly the biggest, ', 'greenest bat that I had ever seen, a bat bigger and greener ' 'than which it was impossible to conceive.' )) == ( 'slowly rotating to reveal what was undoubtedly the biggest, ' 'greenest bat that I had ever seen, a bat bigger and greener ' 'than which it was impossible to conceive.' ) beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a90_pep/test_decorpep544.py000066400000000000000000000216241461113517100277430ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype** :pep:`544` **unit tests.** This submodule unit tests :pep:`544` support implemented in the :func:`beartype.beartype` decorator. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_decor_pep544() -> None: ''' Test :pep:`544` support implemented in the :func:`beartype.beartype` decorator. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from abc import abstractmethod from beartype import beartype from beartype.roar import BeartypeDecorHintPep3119Exception from pytest import raises from typing import Protocol, runtime_checkable # ....................{ PROTOCOLS }.................... @runtime_checkable class Easter1916(Protocol): ''' User-defined runtime protocol declaring arbitrary methods. ''' def i_have_met_them_at_close_of_day(self) -> str: return 'Coming with vivid faces' @abstractmethod def from_counter_or_desk_among_grey(self) -> str: pass class Easter1916Structural(object): ''' User-defined class structurally (i.e., implicitly) satisfying *without* explicitly subclassing this user-defined protocol. ''' def i_have_met_them_at_close_of_day(self) -> str: return 'Eighteenth-century houses.' def from_counter_or_desk_among_grey(self) -> str: return 'I have passed with a nod of the head' class TwoTrees(Protocol): ''' User-defined protocol declaring arbitrary methods, but intentionally *not* decorated by the :func:`typing.runtime_checkable` decorator and thus unusable at runtime by :mod:`beartype`. ''' def holy_tree(self) -> str: return 'Beloved, gaze in thine own heart,' @abstractmethod def bitter_glass(self) -> str: pass # ....................{ FUNCTIONS }.................... # @beartype-decorated callable annotated by this user-defined protocol. @beartype def or_polite_meaningless_words(lingered_awhile: Easter1916) -> str: return ( lingered_awhile.i_have_met_them_at_close_of_day() + lingered_awhile.from_counter_or_desk_among_grey() ) # ....................{ PASS }.................... # Assert this callable returns the expected string when passed this # user-defined class structurally satisfying this protocol. assert or_polite_meaningless_words(Easter1916Structural()) == ( 'Eighteenth-century houses.' 'I have passed with a nod of the head' ) # ....................{ FAIL }.................... # @beartype-decorated callable annotated by this user-defined protocol. with raises(BeartypeDecorHintPep3119Exception): @beartype def times_of_old(god_slept: TwoTrees) -> str: return god_slept.holy_tree() + god_slept.bitter_glass() # ....................{ TESTS ~ protocol }.................... def test_decor_pep544_hint_subprotocol_elision() -> None: ''' Test that type-checking wrappers generated by the :func:`beartype.beartype` decorator for callables annotated by one or more **subprotocols** (i.e., :pep:`544`-compliant subclasses subclassing one or more :pep:`544`-compliant superclasses themselves directly subclassing the root :class:`typing.Protocol` superclass) are optimized to perform only single :func:`isinstance` checks against those subprotocols rather than multiple :func:`isinstance` checks against both those subprotocols and one or more superclasses of those subprotocols. This unit test guards against non-trivial performance regressions that previously crippled the third-party :mod:`numerary` package, whose caching algorithm requires that :mod:`beartype` be optimized in this way. See Also ---------- This relevant issue: https://github.com/beartype/beartype/issues/76 ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from abc import abstractmethod from beartype import beartype from typing import ( Protocol, runtime_checkable, ) # Private metaclass of the root "typing.Protocol" superclass, accessed # without violating privacy encapsulation. _ProtocolMeta = type(Protocol) # ..................{ METACLASSES }.................. class UndesirableProtocolMeta(_ProtocolMeta): ''' Undesirable :class:`typing.Protocol`-compliant metaclass additionally recording when classes with this metaclass are passed as the second parameter to the :func:`isinstance` builtin, enabling subsequent logic to validate this is *not* happening as expected. ''' def __instancecheck__(cls, obj: object) -> bool: ''' Unconditionally return ``False``. Additionally, this dunder method unconditionally records that this method has been called by setting an instance variable of the passed object set only by this method. ''' # Set an instance variable of this object set only by this method. obj.is_checked_by_undesirable_protocol_meta = True # Print a string to standard output as an additional sanity check. print("Save when the eagle brings some hunter's bone,") # Unconditionally return false. return False class DesirableProtocolMeta(UndesirableProtocolMeta): ''' Desirable :class:`typing.Protocol`-compliant metaclass. ''' def __instancecheck__(cls, obj: object) -> bool: ''' Unconditionally return ``True``. ''' # Unconditionally return true. return True # ..................{ PROTOCOLS }.................. @runtime_checkable class PileAroundIt( Protocol, metaclass=UndesirableProtocolMeta, ): ''' Arbitrary protocol whose metaclass is undesirable. ''' @abstractmethod def ice_and_rock(self, broad_vales_between: str) -> str: pass @runtime_checkable class OfFrozenFloods( PileAroundIt, Protocol, # <-- Subprotocols *MUST* explicitly relist this. /facepalm/ metaclass=DesirableProtocolMeta, ): ''' Arbitrary subprotocol of the protocol declared above whose metaclass has been replaced with a more desirable metaclass. ''' @abstractmethod def and_wind(self, among_the_accumulated_steeps: str) -> str: pass # ..................{ STRUCTURAL }.................. class ADesertPeopledBy(object): ''' Arbitrary concrete class structurally satisfying *without* subclassing the subprotocol declared above. ''' def and_wind(self, among_the_accumulated_steeps: str) -> str: return among_the_accumulated_steeps + 'how hideously' def ice_and_rock(self, broad_vales_between: str) -> str: return broad_vales_between + 'unfathomable deeps,' # Arbitrary instance of this class. the_storms_alone = ADesertPeopledBy() # ..................{ BEARTYPE }.................. @beartype def blue_as_the_overhanging_heaven(that_spread: OfFrozenFloods) -> str: ''' Arbitrary callable annotated by the subprotocol declared above. ''' return that_spread.ice_and_rock('Of frozen floods, ') # Assert this callable returns the expected string. assert blue_as_the_overhanging_heaven(that_spread=the_storms_alone) == ( 'Of frozen floods, unfathomable deeps,') # Critically, assert the instance variable *ONLY* defined by the # "UndesirableProtocolMeta" metaclass to remain undefined, implying the # DesirableProtocolMeta.__instancecheck__() rather than # UndesirableProtocolMeta.__instancecheck__() dunder method to have been # implicitly called by the single isinstance() call in the body of the # type-checking wrapper generated by @beartype above. assert not hasattr( the_storms_alone, 'is_checked_by_undesirable_protocol_meta') beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a90_pep/test_decorpep557.py000066400000000000000000000101151461113517100277400ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype decorator :pep:`577` unit tests. This submodule unit tests :pep:`577` support implemented in the :func:`beartype.beartype` decorator. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_decor_pep577() -> None: ''' Test :pep:`557` support implemented in the :func:`beartype.beartype` decorator if the active Python interpreter targets Python >= 3.8 *or* skip otherwise. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeCallHintParamViolation from beartype.typing import ( ClassVar, Optional, ) from dataclasses import ( InitVar, dataclass, field, ) from pytest import raises # ..................{ LOCALS }.................. @beartype @dataclass class SoSolemnSoSerene(object): ''' Arbitrary dataclass type-checked by :func:`beartype.beartype`. ''' # Non-field instance variable passed by the @dataclass decorator to # both this __init__() method *AND* the explicit __post_init__() method # defined below. # # Note this mandatory parameter *MUST* be declared before all optional # parameters due to inscrutable issues in the @dataclass decorator. # Notably, @dataclass refuses to correctly reorder parameters. *sigh* but_for_such_faith: InitVar[str] # Standard instance variable passed by the @dataclass decorator to the # the implicit __init__() method synthesized for this class. that_man_may_be: Optional[str] = None # Uninitialized instance variable *NOT* passed by the @dataclass # decorator to this __init__() method. thou_hast_a_voice: int = field(default=0xBABE, init=False) # Class variable ignored by the @dataclass decorator and thus *NOT* # passed to this __init__() method. with_nature_reconciled: ClassVar[bool] = True @beartype def __post_init__(self, but_for_such_faith: str) -> None: ''' :func:`dataclasses.dataclass`-specific dunder method implicitly called by the :meth:`__init__` method synthesized for this class, explicitly type-checked by :func:`beartype.beartype` for testing. ''' if self.that_man_may_be is None: self.that_man_may_be = but_for_such_faith # Arbitrary instance of this dataclass exercising all edge cases. great_mountain = SoSolemnSoSerene( but_for_such_faith='So solemn, so serene, that man may be,') # ..................{ PASS }.................. # Assert this dataclass defines the expected attributes. assert great_mountain.that_man_may_be == ( 'So solemn, so serene, that man may be,') assert great_mountain.thou_hast_a_voice == 0xBABE assert great_mountain.with_nature_reconciled == True # Assert this dataclass defines *NO* unexpected attributes. assert not hasattr(great_mountain, 'but_for_such_faith') # ..................{ FAIL }.................. # Assert that attempting to instantiate an instance of this dataclass with a # parameter violating the corresponding type hint annotating the field of # the same name raises the expected exception. with raises(BeartypeCallHintParamViolation): SoSolemnSoSerene(but_for_such_faith=0xBEEFBABE) beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a90_pep/test_decorpep563.py000066400000000000000000000501321461113517100277400ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype decorator :pep:`563` unit tests. This submodule unit tests :pep:`563` support implemented in the :func:`beartype.beartype` decorator. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. # WARNING: The "from __future__ import annotations" import appears to be subtly # broken under Python >= 3.10, where performing that import prevents the # "f_locals" attribute of stack frames from capturing locals defined by parent # callables and accessed *ONLY* in type hints annotating one or more parameters # of nested callables contained in those parent callables. Oddly, this breakage # does *NOT* extend to type hints annotating returns of those nested callables # -- only parameters. Ergo, we can only conclude this to be a subtle bug. We # should probably issue an upstream CPython report. For now, conditionally # disable *ALL* PEP 563-specific tests importing from another module containing # an "from __future__ import annotations" import. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype_test._util.mark.pytskip import ( # skip, skip_if_pypy, skip_if_python_version_less_than, skip_if_python_version_greater_than_or_equal_to, ) # .....................{ TESTS ~ club }.................... def test_pep563_class_self_reference_reloaded() -> None: ''' Test module-scoped :pep:`563` support implemented in the :func:`beartype.beartype` decorator. This test exercises multiple issues, including: * `An issue `__ concerning a :pep:`563`-postponed circular reference to a class currently being declared from a method of that class decorated by :func:`beartype.beartype`. See the :meth:`beartype_test.a00_unit.data.pep.pep563.data_pep563_club.Chameleon.like_a_dream` docstring for further details. * `An issue `__ concerning a :pep:`563`-postponed circular reference to a class currently being declared embedded in a parent type hint of a method of that class decorated by :func:`beartype.beartype`. See the :meth:`beartype_test.a00_unit.data.pep.pep563.data_pep563_club.Chameleon.when_we_cling` docstring for further details. .. _issue #49: https://github.com/beartype/beartype/issues/49 .. _issue #152: https://github.com/beartype/beartype/issues/152 ''' # .....................{ IMPORTS }.................... # Defer test-specific imports. # # Note that the "data_pep563_club" submodule is intentionally imported as an # attribute rather than importing the requisite attributes from that # submodule. Why? Because the entire intention of this test is to exercise # reloading of @beartype-decorated callables annotated with circular # references under PEP 563. from beartype_test.a00_unit.data.pep.pep563 import data_pep563_club from importlib import reload # .....................{ PASS }.................... # Assert that a @beartype-decorated class method whose circular return # annotation is postponed under PEP 563 returns the expected value. assert data_pep563_club.Chameleon.like_my_dreams().colors == ( data_pep563_club.COLORS) # Assert that a @beartype-decorated class method whose circular return # annotation is embedded in a parent type hint postponed under PEP 563 # returns the expected value. assert data_pep563_club.Chameleon.when_we_cling().colors == ( data_pep563_club.CLING) # Intentionally reload this submodule into the same attribute. print('Reloading submodule "data_pep563_club"...') data_pep563_club = reload(data_pep563_club) print('Reloaded submodule "data_pep563_club".') # Re-assert that a @beartype-decorated class method whose circular return # annotation is postponed under PEP 563 returns the expected value. # # Note that this method continues to behave as expected despite the # submodule defining this class (and thus this method) being reloaded above. # Why? Because the return annotating this method actually reduces to a # relative forward reference dynamically resolved at method call time. assert data_pep563_club.Chameleon.like_my_dreams().colors == ( data_pep563_club.COLORS) #FIXME: This assertion currently fails, which is almost certainly a bug #pertaining to internal caching of code generated by @beartype... maybe? #importlib.reload() is well-known to have harmful interactions with various #low-level Python machinery. Perhaps this is simply one of them. Let's leave #this unresolved until someone angrily complains about this. \o/ # assert data_pep563_club.Chameleon.when_we_cling().colors == ( # data_pep563_club.CLING) def test_pep563_class_self_reference_override() -> None: ''' Test module-scoped :pep:`563` support implemented in the :func:`beartype.beartype` decorator. This test exercises an edge case in which a :pep:`563`-postponed circular reference to a class currently being declared from a method of that class decorated by :func:`beartype.beartype` overrides and thus conflicts with a previously declared global with the same name as that class in the module declaring that class. ''' # Defer test-specific imports. from beartype_test.a00_unit.data.pep.pep563.data_pep563_club import ( DREAMS, Karma) # Assert that a @beartype-decorated class method whose circular return # annotation is postponed under PEP 563 to a string of the same name as a # previously declared global of the module declaring that class returns the # expected value. assert Karma.if_your_colors().dreams == DREAMS # ....................{ TESTS ~ scope }.................... def test_pep563_module() -> None: ''' Test module-scoped :pep:`563` support implemented in the :func:`beartype.beartype` decorator. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeDecorHintPep604Exception from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_10 from beartype_test.a00_unit.data.pep.pep563.data_pep563_poem import ( get_minecraft_end_txt, get_minecraft_end_txt_pep604, get_minecraft_end_txt_stanza, ) from pytest import raises # ....................{ LOCALS }.................... # Dictionary of these callables' annotations, localized to enable debugging # in the likely event of unit test failure. *sigh* GET_MINECRAFT_END_TXT_ANNOTATIONS = get_minecraft_end_txt.__annotations__ GET_MINECRAFT_END_TXT_STANZA_ANNOTATIONS = ( get_minecraft_end_txt_stanza.__annotations__) # ....................{ ASSERTS }.................... # Assert that all annotations of a callable *NOT* decorated by @beartype # are postponed under PEP 563 as expected. assert all( isinstance(param_hint, str) for arg_name, param_hint in ( GET_MINECRAFT_END_TXT_ANNOTATIONS.items()) ) # Assert that *NO* annotations of a @beartype-decorated callable are # postponed, as @beartype implicitly resolves all annotations. assert all( not isinstance(param_hint, str) for arg_name, param_hint in ( GET_MINECRAFT_END_TXT_STANZA_ANNOTATIONS.items()) ) # Assert that a @beartype-decorated callable works under PEP 563. assert get_minecraft_end_txt_stanza( player_name='Notch', stanza_index=33) == 'Notch. Player of games.' # Test that @beartype silently accepts callables with one or more # non-postponed annotations under PEP 563, a technically non-erroneous edge # case that needlessly complicates code life. # # Manually resolve all postponed annotations on a callable. get_minecraft_end_txt.__annotations__ = { arg_name: eval(param_hint, get_minecraft_end_txt.__globals__) for arg_name, param_hint in ( get_minecraft_end_txt.__annotations__.items()) } # Manually decorate this callable with @beartype. get_minecraft_end_txt_typed = beartype(get_minecraft_end_txt) # Assert that this callable works under PEP 563. assert isinstance(get_minecraft_end_txt_typed(player_name='Notch'), str) # ....................{ ASSERTS ~ pep 604 }.................... # If the active Python interpreter targets Python >= 3.10 and thus supports # PEP 604... if IS_PYTHON_AT_LEAST_3_10: # Manually decorate a PEP 604-compliant callable with @beartype. get_minecraft_end_txt_typed = beartype(get_minecraft_end_txt_pep604) # Assert that this callable works under PEP 563. assert isinstance(get_minecraft_end_txt_typed(player_name='Notch'), str) # Else, the active Python interpreter targets Python < 3.10 and thus fails # to support PEP 604. In this case.. else: # Assert that attempting to manually decorate a PEP 604-compliant # callable with @beartype raises the expected exception. with raises(BeartypeDecorHintPep604Exception): beartype(get_minecraft_end_txt_pep604) def test_pep563_class() -> None: ''' Test class-scoped :pep:`563` support implemented in the :func:`beartype.beartype` decorator. ''' # Defer test-specific imports. from beartype_test.a00_unit.data.pep.pep563.data_pep563_poem import ( MinecraftEndTxtUnscrambler) # Assert that instantiating a class with a @beartype-decorated __init__() # method declaring a @beartype-decorated method closure works. minecraft_end_txt_unscrambler = MinecraftEndTxtUnscrambler( unscrambling='dream') # Assert that that __init__() method declared that method closure. get_minecraft_end_txt_unscrambled_stanza = ( minecraft_end_txt_unscrambler.get_minecraft_end_txt_unscrambled_stanza) assert callable(get_minecraft_end_txt_unscrambled_stanza) # Assert that this method closure works under PEP 563. minecraft_end_txt_unscrambled_stanza = ( get_minecraft_end_txt_unscrambled_stanza( minecraft_end_txt_unscrambler, is_stanza_last=True)) assert isinstance(minecraft_end_txt_unscrambled_stanza, str) assert minecraft_end_txt_unscrambled_stanza.count('dream') == 5 def test_pep563_closure_nonnested() -> None: ''' Test non-nested closure-scoped :pep:`563` support implemented in the :func:`beartype.beartype` decorator. ''' # Defer test-specific imports. from beartype_test.a00_unit.data.pep.pep563.data_pep563_poem import ( get_minecraft_end_txt_closure) # Assert that declaring a @beartype-decorated closure works under PEP 563. get_minecraft_end_txt_substr = get_minecraft_end_txt_closure( player_name='Julian Gough') assert callable(get_minecraft_end_txt_substr) # Assert that this closure works under PEP 563. minecraft_end_txt_substr = get_minecraft_end_txt_substr('player') assert isinstance(minecraft_end_txt_substr, list) assert 'You are the player.' in minecraft_end_txt_substr #FIXME: *REPORT AN UPSTREAM ISSUE WITH THE PYPY TRACKER AT:* # https://foss.heptapod.net/pypy/pypy/-/issues # #So, what's going on here? What's going on here is that PyPy appears to be #slightly broken with respect to the "FrameType.f_locals" dictionary. Whereas #CPython provides the actual dictionary of lexically scoped locals required by #the current frame, PyPy only dynamically computes that dictionary on-the-fly. #This dynamic computation is known to fail cross-thread but was assumed to be #fully compliant with CPython expectations when running in the same thread. # #This does *NOT* appear to be the case. Specifically, the "IntLike" local #variable declared by the top-level get_minecraft_end_txt_closure_factory() #function is: #* Correctly accessible to the lowest-level # get_minecraft_end_txt_closure_inner() closure with respect to actual # interpretation of statements and thus declaration of closures by PyPy. #* Incorrectly omitted from the "FrameType.f_locals" dictionary of the frame # object for the mid-level get_minecraft_end_txt_closure_outer() closure # declaring the lowest-level get_minecraft_end_txt_closure_inner() closure. # What's bizarre is that that dictionary correctly includes the "player_name" # parameter accepted by the top-level get_minecraft_end_txt_closure_factory() # function. # #Clearly, what's happening here is that PyPy developers failed to add local #variables of lexical scopes declared by distant parent callables (i.e., #callables that are *NOT* the direct parent of the lowest-level closure in #question) when those local variables are *ONLY* accessed in annotations. #Moreover, when those local variables are accessed outside annotations (as #with the "player_name" parameter) in the body of the lowest-level closure, #those variables are correctly added to the "FrameType.f_locals" dictionary. #Ergo, this is an annotation-specific issue in the internal algorithm PyPy #uses to dynamically construct that dictionary on-the-fly. Admittedly, this was #an edge case that basically didn't matter until PEP 563 landed -- at which #point this edge case *REALLY* mattered. #FIXME: CPython is subtly broken with respect to "from __future__ import #annotations" imports under Python >= 3.10. Until resolved, disable this. @skip_if_pypy() @skip_if_python_version_greater_than_or_equal_to('3.10.0') def test_pep563_closure_nested() -> None: ''' Test nested closure-scoped :pep:`563` support implemented in the :func:`beartype.beartype` decorator. ''' # Defer test-specific imports. from beartype_test.a00_unit.data.pep.pep563.data_pep563_poem import ( get_minecraft_end_txt_closure_factory) # Assert that declaring a @beartype-decorated closure factory works under # PEP 563. get_minecraft_end_txt_closure_outer = ( get_minecraft_end_txt_closure_factory(player_name='Markus Persson')) assert callable(get_minecraft_end_txt_closure_outer) # Assert that declaring a @beartype-decorated closure declared by a # @beartype-decorated closure factory works under PEP 563. get_minecraft_end_txt_closure_inner = get_minecraft_end_txt_closure_outer( stanza_len_min=65) assert callable(get_minecraft_end_txt_closure_inner) # Assert that this closure works under PEP 563. minecraft_end_txt_inner = get_minecraft_end_txt_closure_inner( stanza_len_max=65, substr='thought') assert isinstance(minecraft_end_txt_inner, list) assert len(minecraft_end_txt_inner) == 1 assert minecraft_end_txt_inner[0] == ( 'It is reading our thoughts as though they were words on a screen.') # .....................{ TESTS ~ pep : 484 }.................... def test_pep563_hint_pep484_namedtuple() -> None: ''' Test module-scoped :pep:`563` support implemented in the :func:`beartype.beartype` decorator with respect to :pep:`484`-compliant :obj:`typing.NamedTuple` subclasses decorated by this decorator. ''' # .....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar import BeartypeCallHintParamViolation from beartype_test.a00_unit.data.pep.pep563.pep484.data_pep563_pep484 import ( LeadOnlyTo) from pytest import raises # .....................{ PASS }.................... # Assert that instantiating this subclass with valid fields creates and # returns an object of the expected type exposing the same fields. while_deaths_blue_vault = LeadOnlyTo(0xCAFEFACE) assert while_deaths_blue_vault.a_black_and_watery_depth == 0xCAFEFACE # .....................{ FAIL }.................... # Assert that instantiating this subclass with one or more invalid fields # raises the expected exception. with raises(BeartypeCallHintParamViolation): LeadOnlyTo("While death's blue vault, with loathliest vapours hung,") def test_pep563_hint_pep484_noreturn() -> None: ''' Test module-scoped :pep:`563` support implemented in the :func:`beartype.beartype` decorator with respect to callables with returns annotated by the :pep:`484`-compliant :obj:`typing.NoReturn` type hint. ''' # Defer test-specific imports. from beartype_test.a00_unit.data.pep.pep563.data_pep563_club import ( HeWouldLingerLong, to_love_and_wonder, ) from pytest import raises # Assert that a @beartype-decorated function "typing.NoReturn" return # annotation is postponed under PEP 563 to a string raises the expected # exception. with raises(HeWouldLingerLong): to_love_and_wonder() # .....................{ TESTS ~ pep : 604 }.................... @skip_if_python_version_less_than('3.10.0') def test_pep563_hint_pep604() -> None: ''' Test module-scoped :pep:`563` support implemented in the :func:`beartype.beartype` decorator with respect to :pep:`604`-compliant unions annotating :pep:`557`-compliant :obj:`dataclasses.dataclass`-decorated subclasses also decorated by this decorator if the active Python interpreter targets Python >= 3.10 and thus supports :pep:`604`. ''' # .....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar import BeartypeCallHintParamViolation from beartype_test.a00_unit.data.pep.pep563.pep604.data_pep563_pep604 import ( FrameMoreAttuned, WithVoiceFarSweeter, ) from pytest import raises # .....................{ LOCALS }.................... # Arbitrary instance of a problematic dataclass subclass annotated by a # problematic PEP 604-compliant union that had yet to be defined at the time # that subclass was defined. wasting_these_surpassing_powers = WithVoiceFarSweeter( than_thy_dying_notes=FrameMoreAttuned()) # wasting_these_surpassing_powers = WithVoiceFarSweeter() # .....................{ PASS }.................... # Assert that this instance exposes the expected fields. isinstance( wasting_these_surpassing_powers.than_thy_dying_notes, FrameMoreAttuned) # .....................{ FAIL }.................... # Assert that instantiating this subclass with one or more invalid fields # raises the expected exception. with raises(BeartypeCallHintParamViolation): WithVoiceFarSweeter(than_thy_dying_notes=( 'In the deaf air, to the blind earth, and heaven')) # ....................{ TESTS ~ limit }.................... #FIXME: Hilariously, we can't even unit test whether the #beartype._decor._pep.pep563._die_if_hint_repr_exceeds_child_limit() function #behaves as expected. See commentary in the #"beartype_test.a00_unit.data.pep.pep563.data_pep563_poem" submodule for all the appalling details. # @skip_if_python_version_less_than('3.7.0') # def test_die_if_hint_repr_exceeds_child_limit() -> None: # ''' # Test the private # :func:`beartype._decor._pep.pep563._die_if_hint_repr_exceeds_child_limit` # function if the active Python interpreter targets at least Python 3.7.0 # (i.e., the first major Python version to support PEP 563) *or* skip # otherwise. # ''' # # # Defer test-specific imports. # from beartype import beartype # from beartype.roar import BeartypeDecorHintPepException # from beartype_test.a00_unit.data.pep.pep563.data_pep563_poem import player_was_love # # # Assert @beartype raises the expected exception when decorating a # # callable violating this limit. # with raises(BeartypeDecorHintPepException): # beartype(player_was_love) beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a90_pep/test_decorpep585.py000066400000000000000000000075011461113517100277460ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype decorator :pep:`585`-compliant unit tests. This submodule unit tests the subset of :pep:`585` support that does *not* also apply to :pep:`484` support implemented in the :func:`beartype.beartype` decorator. See Also -------- :mod:`beartype_test.a00_unit.a70_decor.a40_code.a90_pep.test_decorpep484585` Submodule unit testing the subset of :pep:`484` and :pep:`585` support that generically applies to both (rather than merely :pep:`585`). ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_decor_pep585_hint_nested_type_redefine() -> None: ''' Test decorating a user-defined class with the :func:`beartype.beartype` decorator where (in order): #. That class is subsequently nested in a :pep:`585`-compliant type hint annotating a user-defined function. #. That class and that function are both redefined in the same way, simulating a **hot reload** (i.e., external reload of the hypothetical user-defined module defining that class and that function). See Also -------- https://github.com/beartype/beartype/issues/288 User-reported issue underlying this test case. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from beartype.typing import List # ....................{ LOCALS ~ first }.................... # Define a @beartype-decorated class and function to be redefined below. @beartype class HearMyPlea(object): ''' Arbitrary :func:`beartype.beartype`-decorated class, redefined below. ''' pass @beartype def odin_save_us(thor_preserve_us: List[HearMyPlea]) -> List[HearMyPlea]: ''' Arbitrary :func:`beartype.beartype`-decorated function annotated by a :pep:`585`-compliant type hint nesting an arbitrary :func:`beartype.beartype`-decorated class, both redefined below. ''' return thor_preserve_us # Arbitrary list containing an instance of this class. freyja_succour_us = [HearMyPlea(),] # ....................{ ASSERTS ~ first }.................... # Assert that this function returns the passed list as is. assert odin_save_us(freyja_succour_us) is freyja_succour_us # ....................{ LOCALS ~ second }.................... # Redefine the same @beartype-decorated class and function. @beartype class HearMyPlea(object): ''' Arbitrary :func:`beartype.beartype`-decorated class, redefined above. ''' pass @beartype def odin_save_us(thor_preserve_us: List[HearMyPlea]) -> List[HearMyPlea]: ''' Arbitrary :func:`beartype.beartype`-decorated function annotated by a :pep:`585`-compliant type hint nesting an arbitrary :func:`beartype.beartype`-decorated class, both redefined above. ''' return thor_preserve_us # Arbitrary list containing an instance of this class. freyja_succour_us = [HearMyPlea(),] # ....................{ ASSERTS ~ second }.................... # Assert that this function returns the passed list as is. assert odin_save_us(freyja_succour_us) is freyja_succour_us beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a90_pep/test_decorpep591.py000066400000000000000000000065151461113517100277470ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype :pep:`591` unit tests. This submodule unit tests :pep:`591` support implemented in the :func:`beartype.beartype` decorator. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_decor_pep591() -> None: ''' Test :pep:`591` support implemented in the :func:`beartype.beartype` decorator. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeCallHintParamViolation from beartype.typing import Final from dataclasses import dataclass from pytest import raises # ..................{ LOCALS }.................. @beartype @dataclass class WildSpirit(object): ''' Arbitrary dataclass type-checked by :func:`beartype.beartype`. ''' which_art_moving_everywhere: Final[str] = ( 'Destroyer and preserver; hear, oh hear!') ''' Field annotated by the :obj:`typing.Final` type hint factory subscripted by an arbitrary child type hint, passed by the :func:`.dataclass` decorator to :meth:`.__init__`. ''' heaven_and_ocean: Final = ( b'Shook from the tangled boughs of Heaven and Ocean,') ''' Field annotated by the unsubscripted :obj:`typing.Final` type hint factory, passed by the :func:`.dataclass` decorator to :meth:`.__init__`. ''' # Arbitrary instance of this dataclass exercising all edge cases. destroyer_and_preserver = WildSpirit() # ..................{ PASS }.................. # Assert this dataclass defines the expected attributes. assert destroyer_and_preserver.which_art_moving_everywhere == ( 'Destroyer and preserver; hear, oh hear!') assert destroyer_and_preserver.heaven_and_ocean == ( b'Shook from the tangled boughs of Heaven and Ocean,') #FIXME: Refactor this to test that redefinition fails *AFTER* @beartype #fully supports PEP 591. *sigh* # Assert that attempting to redefine this final field currently silently # succeeds, despite technically violating PEP 591. destroyer_and_preserver.which_art_moving_everywhere = ( "Loose clouds like earth's decaying leaves are shed,") # ..................{ FAIL }.................. # Assert that attempting to instantiate an instance of this dataclass with a # parameter violating the corresponding type hint annotating the field of # the same name raises the expected exception. with raises(BeartypeCallHintParamViolation): WildSpirit(which_art_moving_everywhere=( b"Thou on whose stream, mid the steep sky's commotion,")) beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a90_pep/test_decorpep593.py000066400000000000000000000044751461113517100277540ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype :pep:`593` unit tests. This submodule unit tests :pep:`593` support implemented in the :func:`beartype.beartype` decorator. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_decor_pep593() -> None: ''' Test :pep:`593` support implemented in the :func:`beartype.beartype` decorator. Notably, this test exercises unsuccessful usage of the public :mod:`beartype.vale.Is` class when used to type hint callables decorated by the :func:`beartype.beartype` decorator. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeDecorHintPep593Exception from beartype.vale import Is from beartype._util.api.utilapityping import get_typing_attrs from pytest import raises # ..................{ FAIL }.................. # For the "Annotated" type hint factory exported by each typing module... for Annotated in get_typing_attrs('Annotated'): # Assert that @beartype raises the expected exception when decorating a # callable annotated by a type metahint whose first argument is a # beartype-specific data validator and whose second argument is a # beartype-agnostic object. with raises(BeartypeDecorHintPep593Exception): @beartype def volleyed_and_thundered( flashed_all_their_sabres_bare: Annotated[ str, Is[lambda text: bool('Flashed as they turned in air')], 'Sabring the gunners there,', ] ) -> str: return flashed_all_their_sabres_bare + 'Charging an army, while' beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a90_pep/test_decorpep613.py000066400000000000000000000052751461113517100277440ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator** :pep:`613`-compliant **unit tests**. This submodule unit tests :pep:`613` support implemented in the :func:`beartype.beartype` decorator. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype_test._util.mark.pytskip import skip_if_python_version_less_than # ....................{ TESTS }.................... @skip_if_python_version_less_than('3.10.0') def test_decor_pep613() -> None: ''' Test :pep:`613` support implemented in the :func:`beartype.beartype` decorator if the active Python interpreter targets Python >= 3.10 *or* skip otherwise. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeDecorHintPep613DeprecationWarning from beartype.typing import TypeAlias from pytest import warns from warnings import simplefilter # ....................{ MAIN }.................... # Force pytest to temporarily allow deprecation warnings to be caught by the # warns() context manager for the duration of this test. By default, pytest # simply "passes through" all deprecation warnings for subsequent reporting # if tests otherwise successfully pass. Deprecation warnings include: # * "DeprecationWarning". # * "FutureWarning". # * "PendingDeprecationWarning". simplefilter('always') # ....................{ CALLABLES }.................... with warns(BeartypeDecorHintPep613DeprecationWarning): @beartype def and_shook_him() -> TypeAlias: ''' Arbitrary callable annotated by the deprecated :pep:`613`-compliant :obj:`typing.TypeAlias` type hint singleton, which is shallowly ignorable and thus satisfied by *all* possible objects. ''' return 'And shook him from his rest, and led him forth' # ....................{ ASSERTS }.................... # Assert that calling a PEP 613-annotated callable succeeds as expected. assert and_shook_him() == 'And shook him from his rest, and led him forth' beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a90_pep/test_decorpep647.py000066400000000000000000000067011461113517100277460ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator** :pep:`647`-compliant **unit tests**. This submodule unit tests :pep:`647` support implemented in the :func:`beartype.beartype` decorator. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype_test._util.mark.pytskip import skip_if_python_version_less_than # ....................{ TESTS }.................... @skip_if_python_version_less_than('3.10.0') def test_decor_pep647() -> None: ''' Test :pep:`647` support implemented in the :func:`beartype.beartype` decorator if the active Python interpreter targets Python >= 3.10 *or* skip otherwise. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from beartype.roar import ( BeartypeCallHintReturnViolation, BeartypeDecorHintPep647Exception, ) from beartype.typing import ( List, TypeGuard, Tuple, ) from pytest import raises # ....................{ FUNCTIONS }.................... @beartype def is_typeguard_slow(lst: List[object]) -> TypeGuard[List[str]]: ''' :pep:`647`-compliant type guard returning :data:`True` only if the passed list contains only string items via the standard linear runtime type-check implemented manually. *leisurely barfs into adjacent bucket* ''' return all(isinstance(item, str) for item in lst) @beartype def is_typeguard_bad(tup: Tuple[object, ...]) -> TypeGuard[Tuple[int, ...]]: ''' :pep:`647`-compliant type guard erroneously (presumably, accidentally) violating :pep:`647` by returning a non-boolean object. ''' return "Lull'd by the coil of his crystalline streams," # ....................{ PASS }.................... # Assert that the above function passed lists both satisfying and violating # this type guard returns the expected booleans. assert is_typeguard_slow( ['Thou', 'who', 'didst', 'waken', 'from', 'his', 'summer', 'dreams'] ) is True assert is_typeguard_slow( ['Thou', 'who', 'didst', 'waken', 'from', 'his', 'summer', b'dreams'] ) is False # ....................{ FAIL }.................... # Assert that @beartype raises the expected exception when decorating a # callable accepting one or more parameters erroneously annotated by a PEP # 647-compliant type guard. with raises(BeartypeDecorHintPep647Exception): @beartype def the_blue_mediterranean(where_he_lay: TypeGuard[str]) -> bool: return where_he_lay # Assert that @beartype raises the expected violation when calling a # callable erroneously violating a PEP 647-compliant type guard. with raises(BeartypeCallHintReturnViolation): is_typeguard_bad(('Beside a', 'pumice isle', 'in', "Baiae's bay,",)) beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a90_pep/test_decorpep673.py000066400000000000000000000140061461113517100277420ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator** :pep:`673`-compliant :obj:`typing.Self` **unit tests**. This submodule unit tests :pep:`673` support implemented in the :func:`beartype.beartype` decorator. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype_test._util.mark.pytskip import skip_if_python_version_less_than # ....................{ TESTS }.................... @skip_if_python_version_less_than('3.11.0') def test_decor_pep673() -> None: ''' Test :pep:`673` support implemented in the :func:`beartype.beartype` decorator if the active Python interpreter targets Python >= 3.11 *or* skip otherwise. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from beartype.door import ( die_if_unbearable, is_bearable, ) from beartype.roar import ( BeartypeCallHintReturnViolation, BeartypeDecorHintPep673Exception, ) from beartype.typing import ( List, Self, ) from pytest import raises # ....................{ CLASSES }.................... @beartype class GoodClassIsGood(object): ''' Arbitrary class decorated by :func:`.beartype`. ''' # ...................{ DUNDERS }.................... def __add__(self: Self, other: object) -> 'Self': ''' Arbitrary dunder method annotated by one or more :pep:`673`-compliant self type hints such that the return type hint is the stringified representation of a self type hint. This edge case effectively exercises the intersection of: * :pep:`563` (i.e., ``from __future__ import annotations``). * Mypy-based **implicit dunder return expansion,** an automatic type hint transformation applied by mypy (and presumably other static type-checkers) in which mypy expands all type hints annotating the returns of standardized dunder methods matching the form ``{type}`` to ``typing.Union[{type}, types.NotImplementedType]``. This edge case exercises this issue: https://github.com/beartype/beartype/issues/367 ''' # One-liners in the 21st-and-a-half century! return ( other if isinstance(other, GoodClassIsGood) else NotImplemented) # ...................{ METHODS }.................... def wonderful_method_is_wonderful(self: Self, count: int) -> List[Self]: ''' Arbitrary method annotated by one or more :pep:`673`-compliant self type hints, guaranteed to *not* violate runtime type-checking. ''' return [self] * count def horrible_method_is_horrible(self: Self) -> Self: ''' Arbitrary method annotated by one or more :pep:`673`-compliant self type hints, guaranteed to violate runtime type-checking. ''' return 'Cleave themselves into chasms, while far below' # Do not taunt Super Happy Fun Instance. super_happy_fun_instance = GoodClassIsGood() super_sad_unfun_instance = GoodClassIsGood() # ....................{ PASS }.................... # Assert that a dunder method of a @beartype-decorated class satisfying a # PEP 673-compliant self type hint returns the expected object. assert super_happy_fun_instance + super_sad_unfun_instance is ( super_sad_unfun_instance) with raises(TypeError): super_happy_fun_instance + 'Do not taunt Super Happy Fun Instance.' # Assert that a method of a @beartype-decorated class satisfying a PEP # 673-compliant self type hint returns the expected object. avoid_prolonged_exposure = ( super_happy_fun_instance.wonderful_method_is_wonderful(count=42)) assert len(avoid_prolonged_exposure) == 42 assert avoid_prolonged_exposure[ 0] is super_happy_fun_instance assert avoid_prolonged_exposure[-1] is super_happy_fun_instance # Assert that @beartype raises the expected violation when calling a method # of a @beartype-decorated class erroneously violating a PEP 673-compliant # self type hint. with raises(BeartypeCallHintReturnViolation): super_happy_fun_instance.horrible_method_is_horrible() # ....................{ FAIL }.................... # Assert that @beartype raises the expected exception when decorating # non-class objects accepting one or more parameters erroneously annotated # by a PEP 673-compliant self type hint. Self type hints are valid *ONLY* # inside classes decorated by @beartype. with raises(BeartypeDecorHintPep673Exception): @beartype def terribad_function_is_terribad(self: Self) -> Self: return self with raises(BeartypeDecorHintPep673Exception): class BadClassIsBad(object): @beartype def awful_method_is_awful(self: Self) -> Self: return self # Assert that statement-level runtime type-checkers raise the expected # exceptions when passed PEP 673-compliant self type hints. with raises(BeartypeDecorHintPep673Exception): die_if_unbearable( 'So sweet, the sense faints picturing them! Thou', Self) with raises(BeartypeDecorHintPep673Exception): is_bearable( "For whose path the Atlantic's level powers", Self) beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/a90_pep/test_decorpep695.py000066400000000000000000000027201461113517100277460ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator** :pep:`695`-compliant :obj:`typing.Self` **unit tests**. This submodule unit tests :pep:`695` support implemented in the :func:`beartype.beartype` decorator. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype_test._util.mark.pytskip import skip_if_python_version_less_than # ....................{ TESTS }.................... @skip_if_python_version_less_than('3.12.0') def test_decor_pep695() -> None: ''' Test :pep:`695` support implemented in the :func:`beartype.beartype` decorator if the active Python interpreter targets Python >= 3.12 *or* skip otherwise. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. # # Note that merely importing this data submodule suffices to fully exercise # this test. #FIXME: Uncomment once worky, please. *sigh* # from beartype_test.a00_unit.data.pep import data_pep695 beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/test_codeargkind.py000066400000000000000000000404121461113517100267160ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator parameter kind** unit tests. This submodule unit tests the :func:`beartype.beartype` decorator with respect all explicitly supported **parameter kinds** (e.g., keyword-only, positional-only, variadic positional, variadic keyword). ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype_test._util.mark.pytskip import skip # ....................{ TESTS ~ name }.................... def test_decor_arg_name_fail() -> None: ''' Test unsuccessful usage of the :func:`beartype.beartype` decorator for callables accepting one or more **decorator-reserved parameters** (i.e., parameters whose names are reserved for internal use by this decorator). ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeDecorParamNameException from pytest import raises # ....................{ RAISES }.................... # Assert that decorating a callable accepting a reserved parameter name # raises the expected exception. with raises(BeartypeDecorParamNameException): @beartype def jokaero(weaponsmith: str, __beartype_func: str) -> str: return weaponsmith + __beartype_func # ....................{ TESTS ~ flexible }.................... def test_decor_arg_kind_flex() -> None: ''' Test the :func:`beartype.beartype` decorator on a callable passed two **flexible parameters** (i.e., non-variadic positional or keyword parameters) annotated with PEP-compliant type hints. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from typing import Union # ....................{ CALLABLES }.................... @beartype def special_plan( finally_gone: Union[list, str], finally_done: str, ) -> Union[bool, int, str]: ''' Decorated callable to be exercised. ''' return ''.join(finally_gone) + finally_done # ....................{ PASSES }.................... # Assert this callable returns the expected value. assert special_plan( ['When everyone ', 'you have ever loved ', 'is finally gone,'], finally_done=( 'When everything you have ever wanted is finally done with,'), ) == ( 'When everyone you have ever loved is finally gone,' + 'When everything you have ever wanted is finally done with,' ) @skip('Currently broken due to known issues in decoration-time type-checking.') def test_decor_arg_kind_flex_optional() -> None: ''' Test the :func:`beartype.beartype` decorator on a callable passed an optional flexible parameter whose default value violates the type hint annotating that parameter. Since :func:`beartype.beartype` type-checks default values at decoration time (rather than call time as is the common case for all other parameter types), this extremely common case warrants exhaustive testing. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from beartype.roar import ( BeartypeDecorHintParamDefaultForwardRefWarning, BeartypeDecorHintParamDefaultViolation, ) from beartype_test._util.pytroar import raises_uncached from pytest import warns # ....................{ PASS }.................... # Assert that decorating a callable passed an optional flexible parameter # annotated by a forward reference that *CANNOT* be resolved at decoration # time emits the expected warning (rather than raising an exception). # # Note that passing nothing to warns() enables undocumented edge-case # behaviour in which warns() captures warnings issued by this block # *WITHOUT* validating the types of these warnings. See also: # https://docs.pytest.org/en/4.6.x/warnings.html#recwarn with warns() as warnings_info: @beartype def startled_by( # Optional parameter whose default value satisfies its type hint. his_own_thoughts: str = ( 'There was no fair fiend near him, not a sight'), # Optional parameter whose default value violates its type hint, # which is a forward reference that *CANNOT* be resolved at # decoration time. he_looked_around: 'ThereWasNoFairFiendNearHimNotASight' = ( 'Or sound of awe but in his own deep mind.'), ) -> str: ''' Decorated callable to be exercised. ''' return his_own_thoughts + str(he_looked_around) # Assert that the above decoration emitted exactly one warning. assert len(warnings_info) == 1 # Warning emitted by the above decoration. warning_info = warnings_info[0] # Assert that this warning is of the expected type. assert warning_info.category is ( BeartypeDecorHintParamDefaultForwardRefWarning) # Warning message. warning_message = str(warning_info.message) # Assert that this message contains the names of this function, the # optional parameter whose default value is uncheckable, and the # unresolvable forward reference causing this warning. assert 'startled_by' in warning_message assert 'he_looked_around' in warning_message assert 'ThereWasNoFairFiendNearHimNotASight' in warning_message # ....................{ FAIL }.................... # Assert that decorating a callable passed an optional flexible parameter # whose default value violates the type hint annotating that parameter # raises the expected exception. with raises_uncached(BeartypeDecorHintParamDefaultViolation) as ( exception_info): @beartype def with_doubtful_smile( # Optional parameter whose default value satisfies its type hint. mocking_its_own: str = ( 'With doubtful smile mocking its own strange charms.'), # Optional parameter whose default value violates its type hint. strange_charms: bytes = ( 'Startled by his own thoughts he looked around.'), ) -> str: ''' Decorated callable to be exercised. ''' return mocking_its_own + str(strange_charms) # Exception message raised by the above decoration of this function. exception_message = str(exception_info.value) # Assert that this message contains the names of both this function *AND* # the optional parameter whose default value violates its type hint. assert 'with_doubtful_smile' in exception_message assert 'strange_charms' in exception_message def test_decor_arg_kind_flex_varkw() -> None: ''' Test the :func:`beartype.beartype` decorator on a callable passed a mandatory flexible parameter, an optional flexible parameter, and a variadic keyword parameter, all annotated with PEP-compliant type hints. This test exercises a recent failure in our pre-0.10.0 release cycle: https://github.com/beartype/beartype/issues/78 ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from typing import Union # ....................{ CALLABLES }.................... @beartype def one_needs_to_have_a_plan( someone_said_who_was_turned: Union[bool, str], away_into_the_shadows: Union[float, str] = 'and who i had believed', **was_sleeping_or_dead, ) -> Union[list, str]: ''' Decorated callable to be exercised. ''' return ( someone_said_who_was_turned + '\n' + away_into_the_shadows + '\n' + '\n'.join(was_sleeping_or_dead.values()) ) # ....................{ PASS }.................... # Assert this callable returns the expected value. assert one_needs_to_have_a_plan( someone_said_who_was_turned='imagine he said', all_the_flesh_that_is_eaten='the teeth tearing into it', ) == '\n'.join(( 'imagine he said', 'and who i had believed', 'the teeth tearing into it', )) # ....................{ TESTS ~ keyword }.................... def test_decor_arg_kind_kw_unknown_fail() -> None: ''' Test unsuccessful usage of the :func:`beartype.beartype` decorator for wrapper functions passed unrecognized keyword parameters. ''' # Defer test-specific imports. from beartype import beartype from pytest import raises # Decorated callable to be exercised. @beartype def tau(kroot: str, vespid: str) -> str: return kroot + vespid # Assert that calling this callable with an unrecognized keyword parameter # raises the expected exception. with raises(TypeError) as exception: tau(kroot='Greater Good', nicassar='Dhow') # Assert that this exception's message is that raised by the Python # interpreter on calling the decorated callable rather than that raised by # the wrapper function on type-checking that callable. This message is # currently stable across Python versions and thus robustly testable. assert str(exception.value).endswith( "tau() got an unexpected keyword argument 'nicassar'") # ....................{ TESTS ~ keyword-only }.................... # Keyword-only keywords require PEP 3102 compliance, which has thankfully been # available since Python >= 3.0. def test_decor_arg_kind_kwonly_mixed() -> None: ''' Test the :func:`beartype.beartype` decorator on a callable passed one optional keyword-only parameter and one mandatory keyword-only parameter (in that non-standard and quite counter-intuitive order), each annotated with PEP-compliant type hints. ''' # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeCallHintViolation from beartype.typing import Union from beartype_test._util.pytroar import raises_uncached # Decorated callable to be exercised. @beartype def my_own_special_plan( *, i_listened_to_these_words: Union[dict, str] = ( 'and yet I did not wonder'), if_this_creature: Union[set, str], ) -> Union[tuple, str]: return i_listened_to_these_words + '\n' + if_this_creature # Assert this function returns the expected value. assert my_own_special_plan( if_this_creature='whom I had thought sleeping or') == '\n'.join(( 'and yet I did not wonder', 'whom I had thought sleeping or')) # Assert that calling this callable with invalid parameters raises the # expected exception. with raises_uncached(BeartypeCallHintViolation): my_own_special_plan( if_this_creature=b'dead would ever approach his vision') def test_decor_arg_kind_flex_varpos_kwonly() -> None: ''' Test the :func:`beartype.beartype` decorator on a callable passed a flexible parameter, a variadic positional parameter, and a keyword-only parameter, all annotated with PEP-compliant type hints. ''' # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeCallHintViolation from beartype.typing import Union from beartype_test._util.pytroar import raises_uncached # Decorated callable to be exercised. @beartype def tongue_tasting_its_savour( turned_away_into_the_shadows: Union[dict, str], *all_the_flesh_that_is_eaten: Union[frozenset, str], teeth_tearing_into_it: Union[set, str] ) -> Union[tuple, str]: return ( turned_away_into_the_shadows + '\n' + '\n'.join(all_the_flesh_that_is_eaten) + '\n' + teeth_tearing_into_it ) # Assert this callable returns the expected value. assert tongue_tasting_its_savour( 'When all of your nightmares are for a time obscured', 'As by a shining brainless beacon', 'Or a blinding eclipse of the many terrible shapes of this world,', 'When you are calm and joyful', 'And finally entirely alone,', 'Then in a great new darkness', teeth_tearing_into_it='You will finally execute your special plan', ) == '\n'.join(( 'When all of your nightmares are for a time obscured', 'As by a shining brainless beacon', 'Or a blinding eclipse of the many terrible shapes of this world,', 'When you are calm and joyful', 'And finally entirely alone,', 'Then in a great new darkness', 'You will finally execute your special plan', )) # Assert that calling this callable with invalid parameters raises the # expected exception. with raises_uncached(BeartypeCallHintViolation): tongue_tasting_its_savour( 'One needs to have a plan, someone said', 'Who was turned away into the shadows', 'And who I had believed was sleeping or dead', ['Imagine, he said, all the flesh that is eaten',], 'The teeth tearing into it', 'The tongue tasting its savour', teeth_tearing_into_it='And the hunger for that taste') # ....................{ TESTS ~ pep 570 }.................... # Positional-only keywords require PEP 570 compliance and thus Python >= 3.8. def test_decor_arg_kind_posonly() -> None: ''' Test the :func:`beartype.beartype` decorator on a callable passed two positional-only parameters annotated with PEP-compliant type hints. ''' # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeCallHintViolation from beartype_test.a00_unit.data.pep.data_pep570 import pep570_posonly from beartype_test._util.pytroar import raises_uncached # Wrapper function type-checking this unchecked function. the_taste_and_the_hunger = beartype(pep570_posonly) # Assert this function returns the expected value. assert the_taste_and_the_hunger( 'Take away everything as it is') == '\n'.join(( 'Take away everything as it is', 'and the tongue')) # Assert that calling this callable with invalid parameters raises the # expected exception. with raises_uncached(BeartypeCallHintViolation): the_taste_and_the_hunger(b'That was my plan') def test_decor_arg_kind_posonly_flex_varpos_kwonly() -> None: ''' Test the :func:`beartype.beartype` decorator on a callable passed a mandatory positional-only parameter, optional positional-only parameter, flexible parameter, variadic positional parameter, and keyword-only parameter, all annotated with PEP-compliant type hints. ''' # Defer test-specific imports. from beartype import beartype from beartype_test.a00_unit.data.pep.data_pep570 import ( pep570_posonly_flex_varpos_kwonly) # Wrapper function type-checking this unchecked function. shining_brainless_beacon = beartype(pep570_posonly_flex_varpos_kwonly) # Assert this function returns the expected value. assert shining_brainless_beacon( 'When all of your nightmares are for a time obscured', 'When you are calm and joyful', 'And finally entirely alone,', 'Then in a great new darkness', your_special_plan='You will finally execute your special plan', ) == '\n'.join(( 'When all of your nightmares are for a time obscured', 'When you are calm and joyful', 'And finally entirely alone,', 'Then in a great new darkness', 'You will finally execute your special plan', )) beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/test_codenoop.py000066400000000000000000000235501461113517100262560ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator noop** unit tests. This submodule unit tests edge cases of the :func:`beartype.beartype` decorator efficiently reducing to a noop. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_decor_noop_python_optimized(monkeypatch: 'pytest.MonkeyPatch') -> None: ''' Test that the :func:`beartype.beartype` decorator efficiently reduces to a noop for **optimized Python processes** (i.e., optimized *after* process invocation time, typically by the user setting the ``${PYTHONOPTIMIZED}`` environment variable to a non-zero integer from an interactive REPL). Parameters ---------- monkeypatch : MonkeyPatch :mod:`pytest` fixture allowing various state associated with the active Python process to be temporarily changed for the duration of this test. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeCallHintParamViolation from pytest import raises # ....................{ CALLABLES }.................... def and_what_am_I(that_I: str, should_linger_here: str) -> str: ''' Arbitrary annotated function. ''' return that_I + should_linger_here # ....................{ ASSERTS ~ unoptimized }.................... # This function decorated by @beartype. and_what_am_I_typed = beartype(and_what_am_I) # Assert that @beartype actually decorated this function rather than # reducing to a noop. assert and_what_am_I_typed is not and_what_am_I # Assert that @beartype decorated this function with type-checking by # raising the expected exception when passed invalid parameters. with raises(BeartypeCallHintParamViolation): and_what_am_I_typed(b'With voice far sweeter', b'than thy dying notes,') # ....................{ ASSERTS ~ optimized }.................... # Temporarily set the ${PYTHONOPTIMIZE} environment variable governing # Python optimization to an arbitrary positive integer. monkeypatch.setenv('PYTHONOPTIMIZE', '1') # This function decorated by @beartype yet again. and_what_am_I_typed = beartype(and_what_am_I) # Assert that @beartype efficiently reduces to a noop (i.e., the identity # decorator) when the current Python process is "optimized." assert and_what_am_I_typed is and_what_am_I # ....................{ TESTS ~ sync }.................... def test_decor_noop_unhinted_sync() -> None: ''' Test that the :func:`beartype.beartype` decorator efficiently reduces to a noop on **unannotated synchronous callables** (i.e., callables with *no* annotations declared with the ``def`` rather than ``async def`` keyword). ''' # Defer test-specific imports. from beartype import beartype def khorne(gork, mork): ''' Undecorated unannotated function. ''' return gork + mork # Decorated unannotated function. khorne_typed = beartype(khorne) # Assert that @beartype efficiently reduces to a noop (i.e., the identity # decorator) when decorating this function. assert khorne_typed is khorne # Call this function and assert the expected return value. assert khorne_typed('WAAAGH!', '!HGAAAW') == 'WAAAGH!!HGAAAW' def test_decor_noop_redecorated_sync() -> None: ''' Test that the :func:`beartype.beartype` decorator efficiently reduces to a noop on **synchronous wrappers** (i.e., wrapper functions declared with the ``def`` rather than ``async def`` keyword) generated by prior calls to this decorator. ''' # Defer test-specific imports. from beartype import beartype @beartype def xenos(interex: str, diasporex: str): ''' Arbitrary function. ''' return None if interex and diasporex else interex + diasporex # Assert that attempting to redecorate this function yields the same # wrapper generated by the above decoration. assert xenos is beartype(xenos) # Call this function and assert no value to be returned. assert xenos('Luna Wolves', diasporex='Iron Hands Legion') is None # ....................{ TESTS ~ async }.................... async def test_decor_noop_unhinted_async() -> None: ''' Test that the :func:`beartype.beartype` decorator efficiently reduces to a noop on **unannotated asynchronous callables** (i.e., callables with *no* annotations declared with the ``async def`` rather than ``def`` keywords). ''' # Defer test-specific imports. from asyncio import sleep from beartype import beartype # Undecorated unannotated coroutine. async def kaela(mensha, khaine): await sleep(0) return mensha + khaine # Decorated unannotated function. kaela_typed = beartype(kaela) # Assert that @beartype efficiently reduces to a noop (i.e., the identity # decorator) when decorating this coroutine. assert kaela_typed is kaela # Call this coroutine and assert the expected return value. assert await kaela_typed('Blood Runs...', 'Anger Rises') == ( 'Blood Runs...Anger Rises') async def test_decor_noop_redecorated_async() -> None: ''' Test that the :func:`beartype.beartype` decorator efficiently reduces to a noop on **asynchronous wrappers** (i.e., wrapper functions declared with the ``async def`` rather than ``def`` keywords) generated by prior calls to this decorator. ''' # Defer test-specific imports. from asyncio import sleep from beartype import beartype # Arbitrary coroutine. @beartype async def isha(kurnous: str, asuryan: str): await sleep(0) return None if kurnous and asuryan else kurnous + asuryan # Assert that attempting to redecorate this coroutine yields the same # wrapper generated by the above decoration. assert isha is beartype(isha) # Call this coroutine and assert no value to be returned. assert await isha( 'God of the hunt', asuryan='ruler of the Aeldari pantheon') is None # ....................{ TESTS ~ ignorable }.................... def test_decor_noop_hint_ignorable_iter(hints_ignorable) -> None: ''' Test that the :func:`beartype.beartype` decorator efficiently reduces to a noop on callables annotated with only ignorable type hints in a manner generically exercising non-trivial edge cases with iteration. Parameters ---------- hints_ignorable : frozenset Frozen set of ignorable PEP-agnostic type hints. ''' # Defer test-specific imports. from beartype import beartype # Assert that @beartype efficiently reduces to a noop when passed only type # hints known to be ignorable. for hint_ignorable in hints_ignorable: def revenant_scout_titan( antecedent: hint_ignorable, preposition: hint_ignorable, succedent: hint_ignorable, ) -> hint_ignorable: return antecedent + preposition + succedent # Decorated annotated functions with ignorable type hints. revenant_scout_titan_typed = beartype(revenant_scout_titan) # Assert these functions efficiently reduces to their untyped variants. assert revenant_scout_titan_typed is revenant_scout_titan # Assert these functions return the expected values. assert revenant_scout_titan_typed( 'Hearts Armoured', ' for ', 'Battle') == ( 'Hearts Armoured for Battle') def test_decor_noop_hint_ignorable_order() -> None: ''' Test that the :func:`beartype.beartype` decorator efficiently reduces to a noop on callables annotated with only ignorable type hints in a manner specifically exercising non-trivial edge cases with respect to ordering that would be pragmatically infeasible to exercise with to generic iteration in the :func:`test_decor_noop_hint_ignorable_iter` test. ''' # Defer test-specific imports. from beartype import beartype from beartype._cave._cavefast import AnyType from typing import Any # Undecorated annotated function with ignorable type hints. def gork(stompa: AnyType, gargant: object) -> Any: return stompa + gargant # Undecorated annotated function with ignorable type hints (in the reverse # direction of the previously defined function). Since low-level decorator # code necessarily handles parameters differently from return values *AND* # since the return value for any function may be annotated with at most one # type hint, exercising all edge cases necessitates two or more functions. def mork(gargant: Any, stompa: object) -> AnyType: return stompa + gargant # Decorated annotated functions with ignorable type hints. gork_typed = beartype(gork) mork_typed = beartype(mork) # Assert that @beartype efficiently reduces to a noop (i.e., the identity # decorator) when decorating these functions. assert gork_typed is gork assert mork_typed is mork # Assert these functions return the expected values. assert gork_typed('Goff Klawstompa: ', 'Mega-Gargant') == ( 'Goff Klawstompa: Mega-Gargant') assert mork_typed('Killa Kan', "Big Mek's Stompa: ") == ( "Big Mek's Stompa: Killa Kan") beartype-0.18.5/beartype_test/a00_unit/a70_decor/a40_code/test_codetype.py000066400000000000000000000265611461113517100262710ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator noop** unit tests. This submodule unit tests edge cases of the :func:`beartype.beartype` decorator efficiently reducing to a noop. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_decor_type() -> None: ''' Test decoration of user-defined classes by the :func:`beartype.beartype` decorator. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeCallHintParamViolation from pytest import raises # ....................{ LOCALS }.................... # Intentionally redecorate a class twice by the @beartype decorator, # exercising an edge case in class decoration. @beartype @beartype class FromHisInnocuousHand(object): ''' Arbitrary class defining one or more arbitrary annotated methods. ''' def his_bloodless_food(self, lured_by_the_gentle_meaning: str) -> int: ''' Arbitrary annotated method. ''' return len(lured_by_the_gentle_meaning) # Arbitrary instance of this class. of_his_looks = FromHisInnocuousHand() # ....................{ PASS }.................... # Assert that this method returns the expected length of the passed string. assert of_his_looks.his_bloodless_food( 'Lured by the gentle meaning of his looks,') == 41 # Assert that the __sizeof__() dunder method internally monkey-patched by # the @beartype decorator returns the expected size. of_his_looks_size_new = of_his_looks.__sizeof__() of_his_looks_size_old = of_his_looks.__sizeof__.__wrapped__(of_his_looks) assert isinstance(of_his_looks_size_new, int) assert of_his_looks_size_new >= 0 assert of_his_looks_size_new == of_his_looks_size_old # ....................{ FAIL }.................... # Assert that this method raises the expected exception when passed a # invalid parameter. with raises(BeartypeCallHintParamViolation): of_his_looks.his_bloodless_food( ('And the wild antelope,', "that starts whene'er")) def test_decor_type_nested() -> None: ''' Test decoration of user-defined classes nested inside user-defined classes by the :func:`beartype.beartype` decorator. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeCallHintParamViolation from pytest import raises # ....................{ LOCALS }.................... class ForHisCouch(object): ''' Arbitrary class *not* decorated by :func:`beartype.beartype`. ''' def and_stole(self, from_duties_and_repose: str) -> int: ''' Arbitrary annotated method. ''' return len(from_duties_and_repose) # Intentionally redecorate a class twice by the @beartype decorator, # exercising an edge case in class decoration. @beartype @beartype class TheThrillingSecretsOf(object): ''' Arbitrary parent class decorated by :func:`beartype.beartype` containing an arbitrary nested child class *not* decorated by :func:`beartype.beartype`, exercising that the decoration of this parent class implicitly decorates this child class. ''' # ....................{ CLASS VARIABLES }.................... from_her_fathers_tent: type = object ''' Class variable whose value is the root :class:`object` superclass. This variable exercises an edge cases in :func:`beartype.beartype`. Previously, :func:`beartype.beartype` erroneously conflated class variables whose values are types with nested classes by decorating the former as if they were the latter. In the best case, doing so decorated external classes *not* intended to be decorated; in the worst case, doing so provoked infinite recursion. This variable exercises that worst case. Notably: * :class:`enum.Enum` subclasses as follows: #. *All* :class:`enum.Enum` subclasses define a private ``_member_type_`` attribute whose value is the :class:`object` superclass, which :func:`beartype.beartype` then decorated. #. However, the :class:`object` superclass defines the ``__class__`` dunder attribute whose value is the :class:`type` superclass, which :func:`beartype.beartype` then decorated. #. However, the :class:`type` superclass defines the ``__base__`` dunder attribute whose value is the :class:`object` superclass, which :func:`beartype.beartype` then decorated. #. **INFINITE FRIGGIN' RECURSION.** Anarchy today. ''' and_spread_her_matting: type = type ''' Class variable whose value is the root :class:`type` superclass, exercising the same edge case as the prior class variable. ''' to_tend_his_steps: type = ForHisCouch ''' Class variable whose value is a user-defined class that is neither the root :class:`object` nor :class:`type` superclasses. This variable exercises the aforementioned best case: namely, that :func:`beartype.beartype` no longer erroneously decorates external classes *not* intended to be decorated when those classes are the values of class variables. ''' # ....................{ METHODS }.................... def of_the_birth(self, of_time: str) -> int: ''' Arbitrary annotated method. ''' return len(of_time) # ....................{ NESTED }.................... class MeanwhileAMaiden(object): ''' Arbitrary nested child class *not* decorated by :func:`beartype.beartype`. ''' def brought_his_feed(self, her_daily_portion: str) -> int: ''' Arbitrary annotated method. ''' return len(her_daily_portion) # Arbitrary instance of these classes. enamoured = ForHisCouch() yet_not_daring = TheThrillingSecretsOf() for_deep_awe = TheThrillingSecretsOf.MeanwhileAMaiden() # ....................{ PASS }.................... # Assert that these methods passed arbitrary parameters return the expected # values from those parameters. assert enamoured.and_stole( 'To speak her love:—and watched his nightly sleep,') == 49 assert yet_not_daring.of_the_birth( 'Sleepless herself, to gaze upon his lips') == 40 assert for_deep_awe.brought_his_feed( 'Parted in slumber, whence the regular breath') == 44 # ....................{ FAIL }.................... # Assert that this undecorated method raises a standard exception rather # than a beartype-specific violation when passed an invalid parameter. with raises(TypeError): enamoured.and_stole(True) # Assert that these methods all raise the expected exception when passed # invalid parameters. with raises(BeartypeCallHintParamViolation): yet_not_daring.of_the_birth( b'Of innocent dreams arose: then, when red morn') with raises(BeartypeCallHintParamViolation): for_deep_awe.brought_his_feed(len( 'Made paler the pale moon, to her cold home')) def test_decor_subtype() -> None: ''' Test decoration of user-defined subclasses of user-defined superclasses by the :func:`beartype.beartype` decorator. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeCallHintParamViolation from pytest import raises # ....................{ LOCALS }.................... # Intentionally redecorate a class twice by the @beartype decorator, # exercising an edge case in class decoration. @beartype @beartype class WhereStoodJerusalem(object): ''' Arbitrary superclass. ''' def __init__(self, the_fallen_towers: str) -> None: ''' Arbitrary constructor accepting an arbitrary annotated parameter. ''' self._the_fallen_towers = the_fallen_towers def of_babylon(self, the_eternal_pyramids: str) -> int: ''' Arbitrary annotated method. ''' return len(self._the_fallen_towers + the_eternal_pyramids) @beartype @beartype class MemphisAndThebes(WhereStoodJerusalem): ''' Arbitrary subclass of the superclass defined above, intentionally: * Overriding *all* methods defined by that superclass. * Defining a new subclass-specific method. ''' def __init__(self, the_fallen_towers: str) -> None: self._the_fallen_towers_len = len(the_fallen_towers) def of_babylon(self, the_eternal_pyramids: str) -> int: return ( self._the_fallen_towers_len * len(the_eternal_pyramids)) def and_whatsoever_of_strange( self, sculptured_on_alabaster_obelisk: str) -> float: ''' Arbitrary subclass-specific annotated method. ''' return ( self._the_fallen_towers_len / len(sculptured_on_alabaster_obelisk) ) # Arbitrary instance of this subclass. or_jasper_tomb = MemphisAndThebes('Or jasper tomb, or mutilated sphynx,') # ....................{ PASS }.................... # Assert that these methods passed arbitrary parameters return the expected # values from those parameters. assert or_jasper_tomb.of_babylon( 'Of more than man, where marble daemons watch') == 1584 assert or_jasper_tomb.and_whatsoever_of_strange( "The Zodiac's brazen mystery, and dead men") == 0.8780487804878049 # ....................{ FAIL }.................... # Assert that these methods all raise the expected exception when passed # invalid parameters. with raises(BeartypeCallHintParamViolation): MemphisAndThebes(b'Dark Aethiopia in her desert hills') with raises(BeartypeCallHintParamViolation): or_jasper_tomb.of_babylon(( 'Conceals.', 'Among the ruined temples there,')) with raises(BeartypeCallHintParamViolation): or_jasper_tomb.and_whatsoever_of_strange(len( 'Stupendous columns, and wild images')) beartype-0.18.5/beartype_test/a00_unit/a70_decor/a60_main/000077500000000000000000000000001461113517100230465ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a70_decor/a60_main/test_beartype.py000066400000000000000000000344241461113517100263010ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator type hint code generation unit tests.** This submodule unit tests the :func:`beartype.beartype` decorator with respect to type-checking code dynamically generated by the :mod:`beartype._decor.wrap.wrapmain` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # from beartype.roar import BeartypeDecorHintPep585DeprecationWarning from beartype_test._util.mark.pytmark import ignore_warnings # ....................{ TESTS ~ decorator }.................... # Prevent pytest from capturing and displaying all expected non-fatal # beartype-specific warnings emitted by the @beartype decorator below. Urgh! # @ignore_warnings(BeartypeDecorHintPep585DeprecationWarning) @ignore_warnings(DeprecationWarning) def test_beartype(iter_hints_piths_meta) -> None: ''' Test the :func:`beartype.beartype` decorator with respect to type-checking code dynamically generated by the :mod:`beartype._decor.wrap.wrapmain` submodule. This unit test effectively acts as a functional test and is thus the core test exercising decorator functionality from the end user perspective -- the only perspective that matters in the end. Unsurprisingly, this test is mildly more involved than most. *Whatevah.* This test additionally attempts to avoid similar issues to a `prior issue `__ of this decorator induced by repeated :func:`beartype.beartype` decorations of different callables annotated by one or more of the same PEP-compliant type hints. .. _issue #5: https://github.com/beartype/beartype/issues/5 Parameters ---------- iter_hints_piths_meta : Callable[[], Iterable[beartype_test.a00_unit.data.hint.util.data_hintmetautil.HintPithMetadata]] Factory function creating and returning a generator iteratively yielding ``HintPithMetadata`` instances, each describing a sample type hint exercising an edge case in the :mod:`beartype` codebase paired with a related object either satisfying or violating that hint. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeCallHintViolation from beartype._util.text.utiltextansi import strip_str_ansi from beartype_test.a00_unit.data.hint.util.data_hintmetacls import ( # HintPepMetadata, HintPithUnsatisfiedMetadata, ) from beartype_test._util.pytroar import raises_uncached # from pytest import warns # from pytest import deprecated_call from re import search # from warnings import simplefilter # ....................{ MAIN }.................... # Force pytest to temporarily allow deprecation warnings to be caught by the # warns() context manager for the duration of this test. By default, pytest # simply "passes through" all deprecation warnings for subsequent reporting # if tests otherwise successfully pass. Deprecation warnings include: # * "DeprecationWarning". # * "FutureWarning". # * "PendingDeprecationWarning". # simplefilter('always') # For each predefined type hint and associated metadata... for hint_pith_meta in iter_hints_piths_meta(): # ....................{ LOCALS }.................... # Metadata describing this hint. hint_meta = hint_pith_meta.hint_meta # Type hint to be type-checked. hint = hint_meta.hint # Object to type-check against this hint. pith = hint_pith_meta.pith # Metadata describing this pith. pith_meta = hint_pith_meta.pith_meta # print(f'Type-checking PEP type hint {repr(hint_meta.hint)}...') # Beartype decorator configured specifically for this hint. beartype_confed = beartype(conf=hint_meta.conf) def func_untyped(hint_param: hint) -> hint: ''' Undecorated function both accepting a single parameter and returning a value annotated by this hint whose implementation trivially reduces to the identity function. ''' return hint_param #FIXME: For unknown and probably uninteresting reasons, the #pytest.warns() context manager appears to be broken on our #local machine. We have no recourse but to unconditionally #ignore this warning at the module level. So much rage! #FIXME: It's likely this has something to do with the fact that #Python filters deprecation warnings by default. This is almost #certainly a pytest issue. Since this has become fairly #unctuous, we should probably submit a pytest issue report. #FIXME: Actually, pytest now appears to have explicit support for #testing that a code block emits a deprecation warning: # with pytest.deprecated_call(): # myfunction(17) #See also: https://docs.pytest.org/en/6.2.x/warnings.html#ensuring-code-triggers-a-deprecation-warning #FIXME: Fascinatingly, warns() still refuses to capture warnings. #Although we certainly could call deprecated_call(), doing so is #stymied by the fact that #"BeartypeDecorHintPep585DeprecationWarning" does *NOT* subclass the #standard "DeprecationWarning" class. *sigh* #FIXME: *NONSENSE*! It's 2024 and we *STILL* can't get this to work. #It's been bloody years. It just doesn't work and there's *NO* #reasonable means for us to debug why. We've tried literally everything: #pytest.deprecated_call(), pytest.warns(), warnings.simplefilter(). #Nuthin'. Pytest simply refuses to catch deprecation warnings. Kinda fed #up with the whole thing, honestly. #FIXME: *FASCINATING*. The comparable test_decor_pep613() unit test #successfully tests deprecation warnings emitted by the @beartype #decorator with the trivial pytest.warns() + warnings.simplefilter() #combo. So... why does doing so here fail? Some combination of iteration #and closures would seem to be the likely culprit. Notably, "pylint" #complains this about the above definition of func_untyped(): # cell-var-from-loop: Cell variable "hint" defined in loop # #It seems likely a similar issue is obstructing testing here. #FIXME: ...huh. This is Bizarro World stuff. We tried extricating this #logic into the comparable test_door_is_bearable() test, which also #iterates but *WITHOUT* defining closures. That test still fails to #capture deprecation warnings. So... it's iteration? Really? *OH*. The #iter_hints_piths_meta() iterator is itself defined as a closure of a #session-scoped fixture. Welp, that's almost certainly it. Something #about that fundamentally obstructs capturing of deprecation warnings. #Our only recourse now is to try: #* Defining a new test_hint_warnings() unit test. #* Manually iterating over type hints in that test *WITHOUT* calling # iter_hints_piths_meta(). Instead, extricate the import aspects of # iter_hints_piths_meta() into a simple "for" loop. Avoid anything # extraneous (e.g., repetition, context managers). #FIXME: *THAT WAS IT*. Our new test_hint_warnings() unit test #successfully captures deprecation warnings. So. It's our session-scoped #iter_hints_piths_meta() fixture closure, huh? Well, at least that sorta #makes sense. This is *DEFINITELY* a pytest issue -- but good luck #reducing that one to a minimal-length reproducible working example. # # @beartype-generated wrapper function type-checking that function. # func_typed = None # # # If this beartype decorator emits a warning for this hint... # if hint_meta.warning_type is not None: # with deprecated_call(): # # Decorate that function under a context manager asserting this # # decoration to emit the expected warning. # # with warns(hint_meta.warning_type): # func_typed = beartype_confed(func_untyped) # # Else, this beartype decorator emits *NO* warning for this hint. In # # this case, decorate this callable as is. # else: # func_typed = beartype_confed(func_untyped) func_typed = beartype_confed(func_untyped) # ....................{ VIOLATE }.................... # If this pith violates this hint... if isinstance(pith_meta, HintPithUnsatisfiedMetadata): # ....................{ EXCEPTION ~ type }.................... # Assert this wrapper function raises the expected exception when # type-checking this pith against this hint. with raises_uncached(BeartypeCallHintViolation) as exception_info: func_typed(pith) # Exception captured by the prior call to this wrapper function. exception = exception_info.value # Exception type localized for debuggability. Sadly, the # pytest.ExceptionInfo.__repr__() dunder method fails to usefully # describe its exception metadata. exception_type = exception_info.type # Assert this exception metadata describes the expected exception # as a sanity check against upstream pytest issues and/or issues # with our raises_uncached() context manager. assert issubclass(exception_type, BeartypeCallHintViolation) # Assert this exception to be public rather than private. The # @beartype decorator should *NEVER* raise a private exception. assert exception_type.__name__[0] != '_' # ....................{ EXCEPTION ~ culprits }.................... # Tuple of the culprits responsible for this exception, localized to # avoid inefficiently recomputing these culprits on each access. exception_culprits = exception.culprits # Assert that this tuple both exists *AND* is non-empty. assert isinstance(exception_culprits, tuple) assert exception_culprits # First culprit, which *SHOULD* be either: # * If this pith can be weakly referenced, this pith. # * Else, this pith *CANNOT* be weakly referenced. In this case, # fallback to the truncated representation of this pith. culprit_pith = exception_culprits[0] # If the first culprit is a string... if isinstance(culprit_pith, str): # Assert that this string is non-empty. assert culprit_pith # Else, the first culprit is *NOT* a string. In this case... else: # Assert that the first culprit is this pith. assert culprit_pith is pith # If two or more culprits were responsible... if len(exception_culprits) >= 2: # For each culprit following the first... for culprit_nonpith in exception_culprits[1:]: # Assert that this subsequent culprit is *NOT* the pith. assert culprit_nonpith is not pith # ....................{ EXCEPTION ~ message }.................... # Exception message raised by this wrapper function. exception_str = str(exception) exception_str = strip_str_ansi(exception_str) # print('exception message: {}'.format(exception_str)) # Assert this exception message is prefixed by a substring # identifying the decorated callable with a human-readable label. assert exception_str.startswith('Function ') # Assert that iterables of uncompiled regular expression expected # to match and *NOT* match this message are *NOT* strings, as # commonly occurs when accidentally omitting a trailing comma in # tuples containing only one string: e.g., # * "('This is a tuple, yo.',)" is a 1-tuple containing one string. # * "('This is a string, bro.')" is a string *NOT* contained in a # 1-tuple. assert not isinstance( pith_meta.exception_str_match_regexes, str) assert not isinstance( pith_meta.exception_str_not_match_regexes, str) # For each uncompiled regular expression expected to match this # message, assert this expression actually does so. # # Note that the re.search() rather than re.match() function is # called. The latter is rooted at the start of the string and thus # *ONLY* matches prefixes, while the former is *NOT* rooted at any # string position and thus matches arbitrary substrings as desired. for exception_str_match_regex in ( pith_meta.exception_str_match_regexes): assert search( exception_str_match_regex, exception_str, ) is not None # For each uncompiled regular expression expected to *NOT* match # this message, assert this expression actually does so. for exception_str_not_match_regex in ( pith_meta.exception_str_not_match_regexes): assert search( exception_str_not_match_regex, exception_str, ) is None # ....................{ SATISFY }.................... # Else, this pith satisfies this hint. In this case... else: # Assert this wrapper function successfully type-checks this pith # against this hint *WITHOUT* modifying this pith. assert func_typed(pith) is pith beartype-0.18.5/beartype_test/a00_unit/a70_decor/a80_conf/000077500000000000000000000000001461113517100230515ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a70_decor/a80_conf/test_decorconf.py000066400000000000000000000237051461113517100264330ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator configuration unit tests.** This submodule unit tests high-level functionality of the :func:`beartype.beartype` decorator configured by the optional ``conf`` keyword-only parameter. This submodule does *not* exercise fine-grained configuration (e.g., of individual type-checking strategies), which is delegated to more relevant tests elsewhere; rather, this submodule exercises this decorator to be configurable as expected from a high-level API perspective. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_decor_conf() -> None: ''' Test the :func:`beartype.beartype` decorator against the optional ``conf`` parameter agnostic of parameters instantiating that configuration. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import ( BeartypeConf, BeartypeStrategy, beartype, ) from beartype.roar import BeartypeConfException from pytest import raises # ....................{ PASS }.................... # Assert that @beartype in configuration mode returns the default private # decorator when repeatedly invoked with the default configuration. assert ( # Assert that the first @beartype call passed *NO* arguments internally # creates and returns the same default private decorator as... beartype() is # Another @beartype call passed *NO* arguments as well as... beartype() is # Another @beartype call passed the default configuration. beartype(conf=BeartypeConf()) ) # Assert that @beartype in configuration mode returns the same non-default # private decorator when repeatedly invoked with the same non-default # configuration. assert ( beartype(conf=BeartypeConf( is_debug=True, strategy=BeartypeStrategy.On, )) is beartype(conf=BeartypeConf( is_debug=True, strategy=BeartypeStrategy.On, )) ) # ....................{ FAIL }.................... # Assert that @beartype raises the expected exception when passed a "conf" # parameter that is *NOT* a configuration. with raises(BeartypeConfException): beartype(conf='Within the daedal earth; lightning, and rain,') def test_decor_conf_is_debug(capsys) -> None: ''' Test the :func:`beartype.beartype` decorator passed the optional ``conf`` parameter passed the optional ``is_debug`` parameter. Parameters ---------- capsys :mod:`pytest` fixture enabling standard output and error to be reliably captured and tested against from within unit tests and fixtures. Parameters ---------- https://docs.pytest.org/en/latest/how-to/capture-stdout-stderr.html#accessing-captured-output-from-a-test-function Official ``capsys`` reference documentation. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype import BeartypeConf, beartype from linecache import cache as linecache_cache # ..................{ LOCALS }.................. # @beartype decorator enabling debugging. bugbeartype = beartype(conf=BeartypeConf(is_debug=True)) # _earthquake() function beartyped with debugging enabled. earthquake_beartyped = bugbeartype(_earthquake) # Pytest object freezing the current state of standard output and error as # uniquely written to by this unit test up to this statement. std_captured = capsys.readouterr() # String of all captured standard output. stdout = std_captured.out # List of zero or more lines of this captured standard output. stdout_lines = stdout.splitlines(keepends=True) # ..................{ STDOUT }.................. # Assert the prior decoration printed the expected definition. assert 'line' in stdout assert 'def _earthquake(' in stdout assert 'return' in stdout assert '# is None: ''' Test the :func:`beartype.beartype` decorator passed the optional ``conf`` parameter passed the optional ``strategy`` parameter whose value is the **no-time strategy** (i.e., :attr:`beartype.BeartypeStrategy.O0). ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype import ( BeartypeConf, BeartypeStrategy, beartype, ) from beartype.roar import BeartypeCallHintViolation from pytest import raises # ..................{ LOCALS }.................. # @beartype decorator disabling type-checking. nobeartype = beartype(conf=BeartypeConf(strategy=BeartypeStrategy.O0)) # ..................{ PASS ~ function }.................. def in_the_lone_glare_of_day() -> str: ''' Arbitrary **unignorable invalid callable** (i.e., callable defining at least one PEP-compliant type hint and thus *not* ignorable by the :func:`beartype.beartype` decorator, which violates that type hint). ''' return b'In the lone glare of day, the snows descend' # Assert that this decorator is the identity decorator unconditionally # preserving all passed objects as is. in_the_lone_glare_of_day_unchecked = nobeartype(in_the_lone_glare_of_day) assert in_the_lone_glare_of_day_unchecked is in_the_lone_glare_of_day # Assert that calling this decorated callable raises *NO* type-checking # violations despite this underlying callable violating type-checking. assert isinstance(in_the_lone_glare_of_day_unchecked(), bytes) # ..................{ PASS ~ class }.................. @beartype class UponThatMountain(object): ''' Arbitrary class decorated by the :func:`beartype.beartype` decorator. ''' def none_beholds_them_there(self) -> None: ''' Arbitrary method raising a type-checking violation by definition. ''' return 'Upon that Mountain; none beholds them there,' @nobeartype def nor_when_the_flakes_burn(self) -> None: ''' Arbitrary method raising *no* type-checking violation despite violating type-checking. ''' return 'Nor when the flakes burn in the sinking sun,' # Instance of this class. upon_that_mountain = UponThatMountain() # Assert that calling an invalid method explicitly *NOT* type-checked by # @beartype raises *NO* exception. assert isinstance(upon_that_mountain.nor_when_the_flakes_burn(), str) # Assert that calling an invalid method implicitly type-checked by @beartype # raises the expected exception. with raises(BeartypeCallHintViolation): upon_that_mountain.none_beholds_them_there() # ....................{ PRIVATE ~ callables }.................... def _earthquake(and_fiery_flood: int, and_hurricane: int) -> bool: ''' Arbitrary callable to be decorated by the :func:`beartype.beartype` decorator. This callable is intentionally declared at module scope rather than within the unit test(s) referencing this callable below, as the fully-qualified name of this callable when declared at module scope is considerably more reliable than that of a nested closure. ''' return len(and_fiery_flood) % and_hurricane == 0 beartype-0.18.5/beartype_test/a00_unit/a70_decor/a90_roar/000077500000000000000000000000001461113517100230705ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a70_decor/a90_roar/__init__.py000066400000000000000000000000001461113517100251670ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a70_decor/a90_roar/test_beartype_roar.py000066400000000000000000000223301461113517100273370ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator exception unit tests.** This submodule unit tests the :func:`beartype.beartype` decorator with respect to exceptions raised by the :mod:`beartype._check.error.errorget` submodule. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_decor_violation_culprits() -> None: ''' Test the :func:`beartype.beartype` decorator with respect to **type-checking violations** (i.e., :exc:`beartype.roar.BeartypeCallHintViolation` exceptions raised by callables decorated by that decorator). ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeCallHintViolation from beartype.typing import List from pytest import raises # ....................{ ASSERTS ~ strong ref }.................... class SpiritBearIGiveYouSalmonToGoAway(object): ''' Arbitrary user-defined class. ''' pass class SpiritBearIGiftYouHoneyNotToStay(object): ''' Arbitrary user-defined class. ''' pass # Arbitrary object guaranteed to *NOT* be an instance of this class. # # Since CPython permits weak references to almost all instances of # user-defined classes (including both the above class and the "object" # superclass), this object exhibits the expected behaviour from the # "culprits" API exercised below that depends upon weak references. SPIRIT_BEAR_REFUSE_TO_GO_AWAY = SpiritBearIGiftYouHoneyNotToStay() @beartype def when_spirit_bear_hibernates_in_your_bed( best_bear_den: SpiritBearIGiveYouSalmonToGoAway) -> None: ''' Arbitrary function decorated by :func:`beartype.beartype` annotated by a type hint inducing edge-case behaviour in type-checking violations raised by this callable. ''' pass # Assert that calling this function with a parameter violating its type hint # raises the expected exception. with raises(BeartypeCallHintViolation) as exception_info: when_spirit_bear_hibernates_in_your_bed(SPIRIT_BEAR_REFUSE_TO_GO_AWAY) # Exception captured by the prior call to this wrapper function. exception = exception_info.value # Assert that exactly one culprit was responsible for this exception. assert len(exception.culprits) == 1 # Culprit of this exception. culprit = exception.culprits[0] # Assert that this culprit is the same object passed to this function. assert culprit is SPIRIT_BEAR_REFUSE_TO_GO_AWAY # ....................{ ASSERTS ~ weak ref }.................... # List of lists of byte strings violating type hints annotating the # we_are_all_spirit_bear() function defined below. # # Since CPython prohibits weak references to both lists *AND* byte strings, # this data structure exhibits known flaws in the "culprits" API exercised # below that depends upon weak references. YOU_WENT_THERE_SPIRIT_BEAR = ( b'Why do you sleep in my pinball room, Spirit Bear?') DO_NOT_GO_THERE_SPIRIT_BEAR = [[YOU_WENT_THERE_SPIRIT_BEAR]] @beartype def we_are_all_spirit_bear( best_bear_dens: List[List[str]]) -> None: ''' Arbitrary function decorated by :func:`beartype.beartype` annotated by a type hint inducing edge-case behaviour in type-checking violations raised by this callable. ''' pass # Assert that calling this function with a parameter violating its type hint # raises the expected exception. with raises(BeartypeCallHintViolation) as exception_info: we_are_all_spirit_bear(DO_NOT_GO_THERE_SPIRIT_BEAR) # Exception captured by the prior call to this wrapper function. exception = exception_info.value # Assert that exactly two culprits were responsible for this exception. assert len(exception.culprits) == 2 # Root and leaf culprits of this exception. root_culprit = exception.culprits[0] leaf_culprit = exception.culprits[1] # Assert that both of these culprits are the machine-readable # representations of their underlying objects rather than those objects. # Why? Because CPython prohibits weak references to those objects. *sigh* assert isinstance(root_culprit, str) assert isinstance(leaf_culprit, str) assert root_culprit == repr(DO_NOT_GO_THERE_SPIRIT_BEAR) assert leaf_culprit == repr(YOU_WENT_THERE_SPIRIT_BEAR) def test_decor_violation_types() -> None: ''' Test the :func:`beartype.beartype` decorator with respect to the :attr:`beartype.BeartypeConf.violation_param_type` and :attr:`beartype.BeartypeConf.violation_return_type` configuration parameters. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype import ( BeartypeConf, beartype, ) from beartype.typing import ( List, Tuple, Union, ) from pytest import ( raises, warns, ) # ..................{ CLASSES }.................. class TheVacantWoods(Exception): ''' Arbitrary exception subclass. ''' pass class SpreadRoundHim(Exception): ''' Arbitrary exception subclass. ''' pass class LowInTheWest(UserWarning): ''' Arbitrary warning subclass. ''' pass class WhereHeStood(UserWarning): ''' Arbitrary warning subclass. ''' pass # ..................{ CALLABLES }.................. def the_clear_and( garish_hills: List[str], the_distinct_valley) -> ( Union[int, Tuple[str, ...]]): ''' Arbitrary callable to be decorated by the :func:`beartype.beartype` decorator, configured in various ways below. ''' return the_distinct_valley # Beartype decorator configured to raise different types of user-defined # exceptions on invalid parameters and returns. beartype_raise = beartype(conf=BeartypeConf( violation_param_type=TheVacantWoods, violation_return_type=SpreadRoundHim, )) # Beartype decorator configured to emit different types of user-defined # warnings on invalid parameters and returns. beartype_warn = beartype(conf=BeartypeConf( violation_param_type=LowInTheWest, violation_return_type=WhereHeStood, # is_debug=True, )) # Above callable decorated with type-checking raising custom exceptions. the_clear_and_raise = beartype_raise(the_clear_and) # Above callable decorated with type-checking emitting custom warnings. the_clear_and_warn = beartype_warn(the_clear_and) # ..................{ CONSTANTS }.................. # Arbitrary list of strings to be passed to these callables. WHITHER_HAVE_FLED = [ 'Low in the west, the clear and garish hills,', 'The distinct valley and the vacant woods,', ] # Arbitrary tuple of strings to be passed to these callables. THE_HUES_OF_HEAVEN = ( 'Spread round him where he stood. Whither have fled', 'The hues of heaven that canopied his bower', ) # ..................{ PASS }.................. # Assert that these decorated callables neither raise exceptions nor emit # warnings when passed valid parameters. assert the_clear_and_raise(WHITHER_HAVE_FLED, THE_HUES_OF_HEAVEN) is ( THE_HUES_OF_HEAVEN) assert the_clear_and_warn(WHITHER_HAVE_FLED, THE_HUES_OF_HEAVEN) is ( THE_HUES_OF_HEAVEN) # Assert that this decorated callable raises the expected exception that # this callable was configured to raise on receiving an invalid parameter. with raises(TheVacantWoods): the_clear_and_raise(THE_HUES_OF_HEAVEN, WHITHER_HAVE_FLED) # Assert that this decorated callable raises the expected exception that # this callable was configured to raise on returning an invalid return. with raises(SpreadRoundHim): the_clear_and_raise(WHITHER_HAVE_FLED, WHITHER_HAVE_FLED) # Assert that this decorated callable emits the expected warning that # this callable was configured to emit on receiving an invalid parameter. with warns(LowInTheWest): the_clear_and_warn(THE_HUES_OF_HEAVEN, THE_HUES_OF_HEAVEN) # Assert that this decorated callable emits the expected warning that # this callable was configured to emit on returning an invalid return. with warns(WhereHeStood): the_clear_and_warn(WHITHER_HAVE_FLED, WHITHER_HAVE_FLED) beartype-0.18.5/beartype_test/a00_unit/a70_decor/test_decorgodmode.py000066400000000000000000000113531461113517100255230ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator God-mode** unit tests. This submodule unit tests fragile aspects of the :func:`beartype.beartype` decorator that warrant **God-mode privileges** (i.e., a complete lack of caution, common sense, and goodwill to your dev box). ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype_test._util.mark.pytskip import skip_unless_godmode # ....................{ TESTS }.................... # Skip this test unless the current user has God-mode test privileges. This # test's implementation requires a fixed-duration timeout, rendering this test # hardware- and computational load-sensitive and thus overly fragile for both # remote and local execution environments *NOT* under our direct control -- # especially remote continuous integration (CI) hosts, where test execution time # is mostly a function of free data center resources. @skip_unless_godmode() def test_wrapper_fail_obj_large() -> None: ''' Test unsuccessful usage of a wrapper function dynamically generated by the :func:`beartype.decorator` decorator to type-check a function accepting a deeply nested data structure, passed a worst-case structure exposing performance regressions in this wrapper function. This test guards against performance regressions in an `issue kindly submitted by Cuban type-checking maestro @mvaled (Manuel Vázquez Acosta) `__. .. _issue: https://github.com/beartype/beartype/issues/91 ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeCallHintParamViolation from beartype.typing import List from pytest import raises from time import time # ....................{ LOCALS }.................... #FIXME: Ideally, this test would gracefully timeout (i.e., report failure #after failing to pass for a certain reasonable duration of time). #Pragmatically, pytest provides no official means of doing so and, while #unofficial pytest plugins purporting to do so do exist (e.g., #"pytest-timeouts"), all of those plugins come with uncomfortable caveats #that effectively render any timeouts-based workflow fragile, non-portable, #and failure-prone. Instead, we simply test for timeout in the hackiest #way possible -- which is why we exclude this test from remote CI hosts. # Number of seconds after which the call to the wrapper function defined # below passed the worst-called structure defined below is automatically # failed. This effectively acts as a poor man's timeout. We are that man. TIME_MAX = 15.0 # Arbitrary callable accepting a deeply nested data structure. @beartype def behold(the_great_destroyer_of_apps: List[List[List[int]]]) -> int: return len(the_great_destroyer_of_apps) # Worst-case data structure exposing performance regressions, arbitrarily # sized so as to increase the likelihood of this test: # * Rapidly passing when the underlying code path is optimized as expected. # * Rapidly failing when that path has an unexpected speed regression. BAD_APPLE = [[['a']*512]*256]*128 # ....................{ ASSERTS }.................... # Current time in fractional seconds since the UNIX epoch. Begin timing. TIME_START = time() # Assert this wrapper function raises the expected exception when passed # this structure. Since pytest lacks sane facilities for failing tests # whose runtime exceeds a reasonable duration (see above), developers are # encouraged to manually "fail" this test by inspection at the CLI. *sigh* with raises(BeartypeCallHintParamViolation): behold(BAD_APPLE) # Current time in fractional seconds since the UNIX epoch. End timing. TIME_END = time() # Total time in fractional seconds consumed by this wrapper function. TIME_TOTAL = TIME_END - TIME_START # Assert this wrapper function raised this exception within the allotted # window of time. assert TIME_TOTAL <= TIME_MAX, ( 'Wrapper function "beartype(behold())" failed to type-check ' f'worst-case input within allotted time duration {TIME_MAX}s.' ) beartype-0.18.5/beartype_test/a00_unit/a70_decor/test_decornontype.py000066400000000000000000000144461461113517100256070ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator non-class decoration** unit tests. This submodule unit tests high-level functionality of the :func:`beartype.beartype` decorator with respect to decorating **non-classes** (e.g., unbound functions) irrespective of lower-level type hinting concerns (e.g., PEP-compliance and -noncompliance). ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype_test._util.mark.pytskip import ( skip_if_python_version_greater_than_or_equal_to, skip_if_python_version_less_than, ) # ....................{ TESTS ~ wrapper }.................... def test_decor_nontype_wrapper_isomorphic() -> None: ''' Test the :func:`beartype.beartype` decorator on **isomorphic wrappers** (i.e., callables decorated by the standard :func:`functools.wraps` decorator for wrapping pure-Python callables with additional functionality defined by higher-level decorators such that those wrappers isomorphically preserve both the number and types of all passed parameters and returns by accepting only a variadic positional argument and a variadic keyword argument). ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeCallHintParamViolation from collections.abc import Callable from functools import wraps from pytest import raises # ....................{ WRAPPERS }.................... def hang_their_mute_thoughts(on_the_mute_walls_around: str) -> int: ''' Arbitrary **undecorated wrappee** (i.e., lower-level callable wrapped by the higher-level :func:`hang_their_mute_thoughts` wrapper intentionally *not* decorated by the :func:`.beartype` decorator). ''' return len(on_the_mute_walls_around) @beartype @wraps(hang_their_mute_thoughts) def he_lingered(*args, **kwargs): ''' Arbitrary **decorated isomorphic non-closure wrapper** (i.e., isomorphic wrapper defined as a function rather than closure, decorated by the :func:`.beartype` decorator). ''' return hang_their_mute_thoughts(*args, **kwargs) @beartype def of_the_worlds_youth(func: Callable) -> Callable: ''' Arbitrary **decorated isomorphic non-closure wrapper decorator** (i.e., decorator function creating and returning an isomorphic wrapper defined as a closure, all decorated by the :func:`.beartype` decorator). ''' @beartype @wraps(func) def through_the_long_burning_day(*args, **kwargs): ''' Arbitrary **decorated isomorphic closure wrapper** (i.e., isomorphic wrapper defined as a closure, decorated by the :func:`.beartype` decorator). ''' return func(*args, **kwargs) # Return this wrapper. return through_the_long_burning_day # Isomorphic closure wrapper created and returned by the above decorator. when_the_moon = of_the_worlds_youth(hang_their_mute_thoughts) # ....................{ PASS }.................... # Assert that these wrappers passed valid parameters return the expected # values. assert he_lingered('He lingered, poring on memorials') == 32 assert when_the_moon( 'Gazed on those speechless shapes, nor, when the moon') == 52 # ....................{ FAIL }.................... # Assert that these wrappers passed invalid parameters raise the expected # exceptions. with raises(BeartypeCallHintParamViolation): he_lingered(b"Of the world's youth, through the long burning day") with raises(BeartypeCallHintParamViolation): when_the_moon(b"Filled the mysterious halls with floating shades") def test_decor_nontype_wrapper_type() -> None: ''' Test the :func:`beartype.beartype` decorator on **type wrappers** (i.e., types decorated by the standard :func:`functools.wraps` decorator for wrapping arbitrary types with additional functionality defined by higher-level decorators, despite the fact that wrapping types does *not* necessarily make as much coherent sense as one would think it does). ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from beartype.typing import Any from functools import wraps # ....................{ WRAPPERS }.................... @beartype @wraps(list) def that_echoes_not_my_thoughts(*args: Any, **kwargs: Any): ''' Arbitrary **decorated type non-closure wrapper** (i.e., wrapper defined as a function wrapped by an arbitrary type decorated by the :func:`.beartype` decorator). ''' return list(*args, **kwargs) # ....................{ ASSERTS }.................... # Assert that this wrapper passed valid parameters returns the expected # value. assert that_echoes_not_my_thoughts(('A', 'gloomy', 'smile',)) == [ 'A', 'gloomy', 'smile'] # ....................{ TESTS ~ fail : wrappee }.................... def test_decor_nontype_type_fail() -> None: ''' Test unsuccessful usage of the :func:`beartype.beartype` decorator for an **invalid wrappee** (i.e., object *not* decoratable by this decorator). ''' # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeDecorWrappeeException from pytest import raises # Assert that decorating an uncallable object raises the expected # exception. with raises(BeartypeDecorWrappeeException): beartype(('Book of the Astronomican', 'Slaves to Darkness',)) beartype-0.18.5/beartype_test/a00_unit/a70_decor/test_decortype.py000066400000000000000000000403361461113517100250710ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype decorator class decoration** unit tests. This submodule unit tests high-level functionality of the :func:`beartype.beartype` decorator with respect to decorating **classes** irrespective of lower-level type hinting concerns (e.g., PEP-compliance and -noncompliance). ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype_test._util.mark.pytskip import ( skip_if_python_version_greater_than_or_equal_to, skip_if_python_version_less_than, ) # ....................{ TESTS }.................... def test_decor_type_callable_pseudo() -> None: ''' Test the :func:`beartype.beartype` decorator on **pseudo-callables** (i.e., objects defining the pure-Python ``__call__()`` dunder method). ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeCallHintParamViolation from pytest import raises # ....................{ CLASSES }.................... class WildWestWind(object): ''' Arbitrary **pseudo-callable** (i.e., object defining the pure-Python ``__call__()`` dunder method). ''' def __call__(self, leaves_dead: str) -> str: '''Arbitrary docstring.''' return f'{leaves_dead}: O thou,' # ....................{ LOCALS }.................... # Arbitrary pseudo-callables instance of this class. autumns_being = WildWestWind() unseen_presence = WildWestWind() # ....................{ PASS }.................... # Pseudo-callable wrapped with runtime type-checking. autumns_being_typed = beartype(autumns_being) # Assert that both the original and new pseudo-callables accept and return # strings. assert autumns_being( "O wild West Wind, thou breath of Autumn's being,") == ( "O wild West Wind, thou breath of Autumn's being,: O thou,") assert autumns_being_typed( 'Thou, from whose unseen presence the leaves dead') == ( 'Thou, from whose unseen presence the leaves dead: O thou,') assert unseen_presence( 'Pestilence-stricken multitudes: O thou,') == ( 'Pestilence-stricken multitudes: O thou,: O thou,') # ....................{ FAIL }.................... # Assert that both the original and new pseudo-callables raise the expected # exception when passed invalid parameters. # # Note that the original pseudo-callable has been augmented with runtime # type-checking despite *NOT* being passed to @beartype. Is this expected? # Yes. Is this desirable? Maybe not. Either way, there's nothing @beartype # can particularly do about it. Why? Because Python ignores the __call__() # dunder method defined on objects; Python only respects the __call__() # dunder method defined on the types of objects. Because of this, @beartype # has *NO* recourse but to globally monkey-patch the type of the passed # pseudo-callable (rather than that pseudo-callable itself). with raises(BeartypeCallHintParamViolation): autumns_being( b'Are driven, like ghosts from an enchanter fleeing,') with raises(BeartypeCallHintParamViolation): autumns_being_typed( b'Yellow, and black, and pale, and hectic red,') with raises(BeartypeCallHintParamViolation): unseen_presence( b'Who chariotest to their dark wintry bed') # ....................{ TESTS ~ descriptor }.................... def test_decor_type_descriptor_builtin() -> None: ''' Test the :func:`beartype.beartype` decorator on **C-based unbound builtin method descriptors** (i.e., methods decorated by builtin method decorators). ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeCallHintParamViolation from beartype.typing import NoReturn from pytest import raises # ....................{ CLASSES }.................... class OrDidASeaOfFire(object): ''' Arbitrary class declaring a class method, static method, property getter, property setter, and property deleter -- all decorated by the :func:`beartype.beartype` decorator. ''' # ....................{ CLASS VARS }.................... so_much_of_life_and_joy_is_lost = 'all seems eternal now.' ''' Arbitrary class variable. ''' # ....................{ DUNDERS }.................... def __init__(self) -> None: ''' Arbitrary constructor, defined merely to set an instance variable subsequently manipulated by property methods defined below. ''' self._taught_her_young_ruin = ( 'Ruin? Were these their toys? or did a sea') # ....................{ CLASS METHODS }.................... @beartype @classmethod def envelop_once_this_silent_snow(cls) -> str: ''' Arbitrary class method decorated first by the builtin :class:`classmethod` descriptor and then by the :func:`beartype.beartype` decorator, exercising an edge case in the :func:`beartype.beartype` decorator. Note that reversing this order of decoration does *not* exercising an edge case in the :func:`beartype.beartype` decorator and is thus omitted from testing. ''' return f'None can reply—{cls.so_much_of_life_and_joy_is_lost}' # ....................{ STATIC METHODS }.................... @beartype @staticmethod def the_wilderness_has_a_mysterious_tongue( of_man_flies_far_in_dread: str = '') -> str: ''' Arbitrary static method decorated first by the builtin :class:`staticmethod` descriptor and then by the :func:`beartype.beartype` decorator, exercising an edge case in the :func:`beartype.beartype` decorator. Note that reversing this order of decoration does *not* exercising an edge case in the :func:`beartype.beartype` decorator and is thus omitted from testing. ''' return f'Which teaches awful doubt,{of_man_flies_far_in_dread}' # ....................{ PROPERTIES }.................... @beartype @property def where_the_old_earthquake_daemon(self) -> str: ''' Arbitrary property getter method decorated first by the builtin :class:`staticmethod` descriptor and then by the :func:`beartype.beartype` decorator, exercising an edge case in the :func:`beartype.beartype` decorator. Note that reversing this order of decoration does *not* exercising an edge case in the :func:`beartype.beartype` decorator and is thus omitted from testing. ''' return self._taught_her_young_ruin @beartype @where_the_old_earthquake_daemon.setter def where_the_old_earthquake_daemon(self, vanish: str) -> None: ''' Arbitrary property setter method decorated first by the builtin :class:`staticmethod` descriptor and then by the :func:`beartype.beartype` decorator, exercising an edge case in the :func:`beartype.beartype` decorator. Note that reversing this order of decoration does *not* exercising an edge case in the :func:`beartype.beartype` decorator and is thus omitted from testing. ''' self._taught_her_young_ruin = vanish @beartype @where_the_old_earthquake_daemon.deleter def where_the_old_earthquake_daemon(self) -> NoReturn: ''' Arbitrary property deleter method decorated first by the builtin :class:`staticmethod` descriptor and then by the :func:`beartype.beartype` decorator, exercising an edge case in the :func:`beartype.beartype` decorator. Note that reversing this order of decoration does *not* exercising an edge case in the :func:`beartype.beartype` decorator and is thus omitted from testing. ''' raise ValueError('And their place is not known.') # ....................{ LOCALS }.................... # Instance of this class. the_race = OrDidASeaOfFire() # ....................{ PASS }.................... # Assert this class method accessed on both this instance and this class # returns the expected value. assert ( OrDidASeaOfFire.envelop_once_this_silent_snow() == the_race.envelop_once_this_silent_snow() == 'None can reply—all seems eternal now.' ) # Assert this class method docstring has been preserved as is. assert OrDidASeaOfFire.envelop_once_this_silent_snow.__doc__ is not None assert OrDidASeaOfFire.envelop_once_this_silent_snow.__doc__.startswith(''' Arbitrary class method''') # Assert this static method accessed on both this instance and this class # when passed a valid parameter returns the expected value. assert ( OrDidASeaOfFire.the_wilderness_has_a_mysterious_tongue(' or faith so mild,') == the_race.the_wilderness_has_a_mysterious_tongue(' or faith so mild,') == 'Which teaches awful doubt, or faith so mild,' ) # Assert this static method docstring has been preserved as is. assert ( OrDidASeaOfFire.the_wilderness_has_a_mysterious_tongue.__doc__ is not None ) assert ( OrDidASeaOfFire.the_wilderness_has_a_mysterious_tongue.__doc__.startswith(''' Arbitrary static method''') ) # Assert this property getter method accessed on this instance returns the # expected value. assert the_race.where_the_old_earthquake_daemon == ( 'Ruin? Were these their toys? or did a sea') # Assert this property setter method accessed on this instance when passed a # valid parameter silently succeeds. the_race.where_the_old_earthquake_daemon = ( "Vanish, like smoke before the tempest's stream,") # Assert this property getter method accessed on this instance returns this # valid parameter. assert the_race.where_the_old_earthquake_daemon == ( "Vanish, like smoke before the tempest's stream,") # Assert this property method docstring has been preserved as is. assert OrDidASeaOfFire.where_the_old_earthquake_daemon.__doc__ is not None assert OrDidASeaOfFire.where_the_old_earthquake_daemon.__doc__.startswith(''' Arbitrary property getter method''') # ....................{ FAIL }.................... # Assert this static method accessed on both this instance and this class # when passed an invalid parameter raises the expected exception. with raises(BeartypeCallHintParamViolation): OrDidASeaOfFire.the_wilderness_has_a_mysterious_tongue( b'his work and dwelling') with raises(BeartypeCallHintParamViolation): the_race.the_wilderness_has_a_mysterious_tongue( b'The voiceless lightning in these solitudes') # Assert this property setter method accessed on this instance when passed # an invalid parameter raises the expected exception. with raises(BeartypeCallHintParamViolation): the_race.where_the_old_earthquake_daemon = ( b'And their place is not known. Below, vast caves') # Assert this property deleter method accessed on this instance raises the # expected exception. with raises(ValueError) as exception_info: del the_race.where_the_old_earthquake_daemon # Exception message raised by the body of that block. exception_message = str(exception_info.value) # Assert this exception message is the expected string. assert exception_message == 'And their place is not known.' # If the active Python interpreter targets Python < 3.9 and thus fails to # declare the PEP 585-compliant "types.GenericAlias" superclass, skip this test. @skip_if_python_version_less_than('3.9.0') def test_decor_type_descriptor_builtin_called() -> None: ''' Test the :func:`beartype.beartype` decorator on **explicitly called C-based unbound builtin method descriptors** (i.e., builtin method decorators that are explicitly called as functions rather than implicitly called as decorators). ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from types import GenericAlias # ....................{ CLASSES }.................... @beartype class AndMeetLoneDeath(object): ''' Arbitrary :func:`beartype.beartype`-decorated class declaring methods defined by explicitly calling C-based builtin method descriptors. ''' # Augment this user-defined class into a type hint factory via the # standard one-liner leveraged throughout both the standard library and # third-party packages. # # Note that the @classmethod decorator explicitly supports C-based # callable types. Ergo, this one-liner exercises that @beartype supports # both this common idiom *AND* this decorator behaviour without raising # unexpected exceptions at decoration time. __class_getitem__ = classmethod(GenericAlias) # If the active Python interpreter targets either Python 3.9.x *OR* 3.10.x, then # chaining the @classmethod decorator into the @property decorator is permitted; # else, doing so is prohibited. To avoid non-deterministic behaviour under both # older and newer Python versions, avoid those versions. @skip_if_python_version_less_than('3.10.0') @skip_if_python_version_greater_than_or_equal_to('3.11.0') def test_decor_type_descriptor_builtin_chain() -> None: ''' Test the :func:`beartype.beartype` decorator on chaining multiple **C-based unbound builtin method descriptors** (i.e., methods decorated by builtin method decorators) together -- notably, the builtin :class:`.classmethod` decorator into the builtin :class:`.property` decorator. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype # ....................{ CLASSES }.................... class ThanGemsOrGold(object): ''' Arbitrary class defining an arbitrary class property. ''' @beartype @classmethod @property def the_varying_roof_of_heaven(cls) -> str: ''' Arbitrary **class property** (i.e., method decorated by chaining the builtin :class:`.classmethod` decorator into the builtin :class:`.property` decorator). ''' return 'And the green earth lost in his heart its claims' # ....................{ LOCALS }.................... # Instance of this class. to_love_and_wonder = ThanGemsOrGold() # ....................{ PASS }.................... # Assert this class property accessed on both this instance and this class # returns the expected value. assert ( ThanGemsOrGold.the_varying_roof_of_heaven == to_love_and_wonder.the_varying_roof_of_heaven == 'And the green earth lost in his heart its claims' ) beartype-0.18.5/beartype_test/a00_unit/a90_claw/000077500000000000000000000000001461113517100213105ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a90_claw/__init__.py000066400000000000000000000000001461113517100234070ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a90_claw/_clawfixture.py000066400000000000000000000065251461113517100243660ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **import hook fixtures** (i.e., :mod:`pytest`-specific context managers passed as parameters to unit tests exercising the :mod:`beartype.claw` subpackage). ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from pytest import fixture # ....................{ FIXTURES ~ equality }.................... @fixture(autouse=True, scope='function') def clean_claws() -> None: # <-- heh. get it... clean *CLAWS*? it is punny. ''' Permanently, silently, and recursively remove all **bytecode files** (i.e., pure-Python bytecode compiled to platform-dependent temporary files residing in temporary ``__pycache__/`` subdirectories) of the :mod:`beartype.claw` subpackage and all subsubpackages of that subpackage regardless of depth. Note that this unit test-scoped fixture is implicitly performed *before* each unit test transitively defined in sibling and child submodules of the subpackage directly containing this submodule. Why? Because failing to do so would invite subtle but easily reproducible desynchronization woes between those files and more recent changes to the implementation of the :mod:`beartype.claw` subpackage in the main codebase. See Also -------- :func:`beartype._util.path.utilpathremove.remove_package_bytecode_files` Further details. ''' # ..................{ IMPORTS }.................. # Defer fixture-specific imports. from beartype.claw._pkg.clawpkgcontext import packages_trie_cleared from beartype._util.module.utilmodget import get_module_dir from beartype._util.path.utilpathremove import ( remove_package_bytecode_files) from beartype_test.a00_unit.data import claw # ....................{ PATHS }.................... # Path encapsulating the absolute dirname of the "beartype.claw" subpackage. # # Note that we intentionally avoid importing any subsubpackages (e.g., # "beartype_test.a00_unit.data.claw.intraprocess.hookable_package.beartype_this_package") # above. Why? Because doing so would implicitly install the exact beartype # import hook which calling unit tests are attempting to subsequently # exercise and which *MUST* be confined to a context manager for test # idempotency. claw_dir = get_module_dir(claw) # Recursively remove *ALL* previously compiled bytecode files from both this # subdirectory *and* *ALL* subsubdirectories of this subdirectory. remove_package_bytecode_files(claw_dir) # ....................{ HOOKS }.................... # With a context manager guaranteeably reverting *ALL* beartype import hooks # transitively installed in the body of this context manager, defer to the # parent unit test implicitly invoking this fixture. with packages_trie_cleared(): yield beartype-0.18.5/beartype_test/a00_unit/a90_claw/a00_core/000077500000000000000000000000001461113517100227005ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a90_claw/a00_core/__init__.py000066400000000000000000000000001461113517100247770ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a90_claw/a00_core/test_claw_api.py000066400000000000000000000031651461113517100260750ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **import hooks subpackage API unit tests** (i.e., unit tests exercising that the :mod:`beartype.claw` subpackage exports the expected public attributes with the expected types). ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_claw_api() -> None: ''' Test that the :mod:`beartype.claw` subpackage exports the expected public attributes with the expected types. ''' # Defer test-specific imports. from beartype.claw import ( beartype_all, beartype_package, beartype_packages, beartype_this_package, beartyping, ) from beartype._util.func.utilfunctest import is_func_python # Tuple of all import hooks exported by the "beartype.claw" API. BEARTYPE_CLAW_FUNCS = ( beartype_all, beartype_package, beartype_packages, beartype_this_package, beartyping, ) # Assert that these imports are all pure-Python functions. for beartype_claw_func in BEARTYPE_CLAW_FUNCS: assert is_func_python(beartype_claw_func) is True beartype-0.18.5/beartype_test/a00_unit/a90_claw/a90_hook/000077500000000000000000000000001461113517100227215ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a90_claw/a90_hook/__init__.py000066400000000000000000000000001461113517100250200ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/a90_claw/a90_hook/test_claw_extraprocess.py000066400000000000000000000134501461113517100300650ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **extraprocess import hook unit tests** (i.e., unit tests exercising :mod:`beartype.claw` import hooks within a Python subprocess forked from the active Python process). ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_claw_extraprocess_executable_submodule( monkeypatch: 'MonkeyPatch') -> None: ''' Test an arbitrary :mod:`beartype.claw` import hook against a data submodule in this test suite run as a script within a Python subprocess forked from the active Python process via the standard command-line option ``-m``. Parameters ---------- monkeypatch : MonkeyPatch :mod:`pytest` fixture allowing various state associated with the active Python process to be temporarily changed for the duration of this test. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype._util.py.utilpyinterpreter import ( get_interpreter_command_words) from beartype_test._util.command.pytcmdrun import ( run_command_forward_output, run_command_forward_stderr_return_stdout, ) from beartype_test._util.path.pytpathtest import ( get_test_unit_data_claw_extraprocess_dir) # ....................{ ASSERTS }.................... # Temporarily change the current working directory (CWD) to the # test-specific root directory containing the data package tested below. monkeypatch.chdir(get_test_unit_data_claw_extraprocess_dir()) # Tuple of all shell words with which to run a data submodule in this test # suite as a script within a Python subprocess forked from the active Python # process via the standard command-line option "-m". PYTHON_ARGS = get_interpreter_command_words() + ( # Fully-qualified name of this data submodule with respect to the root # directory containing the package containing this submodule. '-m', 'executable_submodule.main_submodule', ) # Attempt to... try: # Standard output emitted by running this this module as a script, # raising an exception on subprocess failure while forwarding all # standard error emitted by this subprocess to the standard error file # handle of this parent Python process. PYTHON_STDOUT = run_command_forward_stderr_return_stdout( command_words=PYTHON_ARGS) # Assert this output to be the expected output printed by this module. assert PYTHON_STDOUT == str(float(len( 'His infancy was nurtured. Every sight'))) # If doing so raised *ANY* exception whatsoever... except: # Re-run the same module as a script except forward both all standard # output *AND* error emitted by this subprocess to the standard output # and error file handles of this parent Python process. Doing so # significantly improves debuggability in the event of a fatal error. run_command_forward_output(command_words=PYTHON_ARGS) # Re-raise this exception. raise def test_claw_extraprocess_executable_package( monkeypatch: 'MonkeyPatch') -> None: ''' Test an arbitrary :mod:`beartype.claw` import hook against a data package in this test suite run as a script within a Python subprocess forked from the active Python process via the standard command-line option ``-m``. Parameters ---------- monkeypatch : MonkeyPatch :mod:`pytest` fixture enabling various state associated with the active Python process to be temporarily changed for the duration of this test. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype._util.py.utilpyinterpreter import ( get_interpreter_command_words) from beartype_test._util.command.pytcmdrun import ( run_command_forward_stderr_return_stdout) from beartype_test._util.path.pytpathtest import ( get_test_unit_data_claw_extraprocess_dir) # ....................{ ASSERTS }.................... # Temporarily change the current working directory (CWD) to the # test-specific root directory containing the data package tested below. monkeypatch.chdir(get_test_unit_data_claw_extraprocess_dir()) # Tuple of all shell words with which to run a data submodule in this test # suite as a script within a Python subprocess forked from the active Python # process via the standard command-line option "-m". PYTHON_ARGS = get_interpreter_command_words() + ( # Fully-qualified name of this data package with respect to the root # directory containing the package. '-m', 'executable_package', ) # Standard output emitted by running this this module as a script, raising # an exception on subprocess failure while forwarding all standard error # emitted by this subprocess to the standard error file handle of this # parent Python process. PYTHON_STDOUT = run_command_forward_stderr_return_stdout( command_words=PYTHON_ARGS) # Assert this output to be the expected output printed by this module. assert PYTHON_STDOUT == str( len('And sound from the vast earth and ambient air,')) beartype-0.18.5/beartype_test/a00_unit/a90_claw/a90_hook/test_claw_intraprocess.py000066400000000000000000000324041461113517100300570ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Beartype **intraprocess import hook unit tests** (i.e., unit tests exercising :mod:`beartype.claw` import hooks within the active Python process). ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: Isolate each unit test defined below to its own subprocess. Why? # Module imports. Since each unit test defined below tends to reimport the same # (or, at least, similar) modules as previously run unit tests defined below, # module imports and thus unit tests *MUST* be isolated to their own # subprocesses to ensure these tests may be run in any arbitrary order. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! import pytest # ....................{ TESTS }.................... @pytest.mark.run_in_subprocess def test_claw_intraprocess_beartype_this_package() -> None: ''' Test the :mod:`beartype.claw.beartype_this_package` import hook against a data subpackage in this test suite exercising *all* edge cases associated with this import hook. ''' # ....................{ IMPORTS }.................... # Implicitly subject this single package to a beartype import hook # configured by a non-default beartype configuration, installed by importing # *ANYTHING* from this package. from beartype_test.a00_unit.data.claw.intraprocess.hookable_package.beartype_this_package import ( this_submodule, empty_doc_nofuture, empty_doc_future, empty_nodoc_future, empty_nodoc_nofuture, ) # Import an arbitrary submodule *NOT* subject to that import hook. from beartype_test.a00_unit.data.claw.intraprocess import unhookable_module @pytest.mark.run_in_subprocess def test_claw_intraprocess_beartype_package() -> None: ''' Test the :mod:`beartype.claw.beartype_package` import hook against a single data subpackage in this test suite exercising *all* edge cases associated with this import hook. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import BeartypeConf from beartype.claw import beartype_package from beartype.roar import BeartypeClawHookException from pytest import raises # ....................{ LOCALS }.................... # Name of the single package to be subject to beartype import hooks below. PACKAGE_NAME = ( 'beartype_test.a00_unit.data.claw.intraprocess.hookable_package') # ....................{ PASS }.................... # Explicitly subject this single package to a beartype import hook # configured by the default beartype configuration. beartype_package(PACKAGE_NAME) # Import all submodules of the package hooked above, exercising that these # submodules are subject to that import hook. from beartype_test.a00_unit.data.claw.intraprocess.hookable_package import ( kind, pep) # Import an arbitrary submodule *NOT* subject to those import hooks. from beartype_test.a00_unit.data.claw.intraprocess import unhookable_module # Assert that repeating the same import hook as above silently succeeds. beartype_package(PACKAGE_NAME) # ....................{ FAIL }.................... # Assert that passing an invalid package name to this import hook raises the # expected exception, where invalid package name includes: # * A non-string. # * An empty string. # * A non-empty string that is *NOT* a valid Python identifier. with raises(BeartypeClawHookException): beartype_package(b'Keeps record of the trophies won from thee,') with raises(BeartypeClawHookException): beartype_package('') with raises(BeartypeClawHookException): beartype_package('0_hoping_to.still.these_obstinate_questionings') # Assert that passing an invalid beartype configuration to this import hook # raises the expected exception. with raises(BeartypeClawHookException): beartype_package( package_name=PACKAGE_NAME, conf='Of thee and thine, by forcing some lone ghost', ) # Assert that repeating a similar import hook as above under a different # (and thus conflicting) beartype configuration raises the expected # exception. with raises(BeartypeClawHookException): beartype_package( package_name=PACKAGE_NAME, conf=BeartypeConf(is_debug=True), ) @pytest.mark.run_in_subprocess def test_claw_intraprocess_beartype_packages() -> None: ''' Test the :mod:`beartype.claw.beartype_packages` import hook against multiple data subpackages in this test suite exercising *all* edge cases associated with this import hook. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import BeartypeConf from beartype.claw import beartype_packages from beartype.roar import ( BeartypeClawHookException, BeartypeDoorHintViolation, ) from pytest import raises # ....................{ LOCALS }.................... # Tuple of the names of two or more packages to be subject to beartype # import hooks below. PACKAGE_NAMES = ( 'beartype_test.a00_unit.data.claw.intraprocess.hookable_package', 'beartype_test.a00_unit.data.claw.intraprocess.hookable_package.kind', 'beartype_test.a00_unit.data.claw.intraprocess.unhookable_module', ) # ....................{ PASS }.................... # Explicitly subject these multiple packages to a beartype import hook # configured by the default beartype configuration. beartype_packages(PACKAGE_NAMES) # Import all submodules of the package hooked above, exercising that these # submodules are subject to that import hook. from beartype_test.a00_unit.data.claw.intraprocess.hookable_package import ( kind, pep) # Assert that repeating the same import hook as above silently succeeds. beartype_packages(PACKAGE_NAMES) # ....................{ FAIL }.................... # Assert that attempting to unsafely import a submodule directly hooked # above that is *NOT* hookable by @beartype raises the expected exception. with raises(BeartypeDoorHintViolation): from beartype_test.a00_unit.data.claw.intraprocess import ( unhookable_module) # Assert that passing an invalid iterable of package names to this import # hook raises the expected exception, where invalid iterable includes: # * A non-iterable. # * An empty iterable. # * A non-empty iterable containing one or more items that are either: # * A non-string. # * An empty string. # * A non-empty string that is *NOT* a valid Python identifier. with raises(BeartypeClawHookException): beartype_packages('Thy messenger, to render up the tale') with raises(BeartypeClawHookException): beartype_packages(()) with raises(BeartypeClawHookException): beartype_packages((b'Of what we are. In lone and silent hours,',)) with raises(BeartypeClawHookException): beartype_packages(('',)) with raises(BeartypeClawHookException): beartype_packages( ('when.night_makes_a.weird_sound.of.its.0_own_stillness',)) # Assert that passing an invalid beartype configuration to this import hook # raises the expected exception. with raises(BeartypeClawHookException): beartype_packages( package_names=PACKAGE_NAMES, conf='Like an inspired and desperate alchymist', ) # Assert that repeating a similar import hook as above under a different # (and thus conflicting) beartype configuration raises the expected # exception. with raises(BeartypeClawHookException): beartype_packages( package_names=PACKAGE_NAMES, conf=BeartypeConf(is_debug=True), ) @pytest.mark.run_in_subprocess def test_claw_intraprocess_beartype_all() -> None: ''' Test the :mod:`beartype.claw.beartype_all` import hook against *all* data subpackages in this test suite exercising *all* edge cases associated with this import hook. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import BeartypeConf from beartype.claw import beartype_all from beartype.roar import ( BeartypeClawHookException, BeartypeDoorHintViolation, ) from pytest import raises # ....................{ PASS }.................... # Permanently subject *ALL* modules (including both third-party and # first-party modules in Python's standard library) to a beartype import # hook configured by the default beartype configuration. beartype_all() # Import *ALL* "beartype.claw"-specific data submodules, exercising that # these submodules are subject to that import hook. from beartype_test.a00_unit.data.claw.intraprocess.hookable_package import ( kind, pep) # Assert that repeating the same import hook as above silently succeeds. beartype_all() # ....................{ FAIL }.................... # Assert that attempting to unsafely import a submodule directly hooked # above that is *NOT* hookable by @beartype raises the expected exception. with raises(BeartypeDoorHintViolation): from beartype_test.a00_unit.data.claw.intraprocess import ( unhookable_module) # Assert that passing an invalid beartype configuration to this import hook # raises the expected exception. with raises(BeartypeClawHookException): beartype_all(conf='Staking his very life on some dark hope,') # Assert that repeating a similar import hook as above under a different # (and thus conflicting) beartype configuration raises the expected # exception. with raises(BeartypeClawHookException): beartype_all(conf=BeartypeConf(is_debug=True)) @pytest.mark.run_in_subprocess def test_claw_intraprocess_beartyping() -> None: ''' Test the :mod:`beartype.claw.beartyping` import hook against *all* data subpackages in this test suite exercising *all* edge cases associated with this import hook. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import BeartypeConf from beartype.claw import beartyping from beartype.roar import ( BeartypeClawHookException, BeartypeDoorHintViolation, ) from pytest import raises # With a context manager temporarily subjecting *ALL* modules (including # both third-party and first-party modules in Python's standard library) # imported in the body of this manager to a beartype import hook configured # by the default beartype configuration... with beartyping(): # ....................{ PASS }.................... # Import *ALL* "beartype.claw"-specific data submodules, exercising that # these submodules are subject to that import hook. # # Note that Python provides *NO* robust means of unimporting previously # imported modules. Likewise, this unit test has *NO* robust means of # testing whether or not this context manager raises exceptions under a # different (and thus conflicting) beartype configuration. from beartype_test.a00_unit.data.claw.intraprocess.hookable_package import ( pep) # Assert that nesting a similar context manager under a non-default # configuration nonetheless semantically equivalent to the default # configuration silently succeeds. with beartyping(conf=BeartypeConf(is_debug=True)): from beartype_test.a00_unit.data.claw.intraprocess.hookable_package import ( kind) # ....................{ FAIL }.................... # Assert that attempting to unsafely import a submodule directly hooked # above that is *NOT* hookable by @beartype raises the expected # exception. with raises(BeartypeDoorHintViolation): from beartype_test.a00_unit.data.claw.intraprocess import ( unhookable_module) # Assert that passing an invalid beartype configuration to this context # manager raises the expected exception. with raises(BeartypeClawHookException): with beartyping(conf='Have I mixed awful talk and asking looks'): pass # Import an arbitrary submodule *NOT* subject to that context manager. from beartype_test.a00_unit.data.claw.intraprocess import unhookable_module beartype-0.18.5/beartype_test/a00_unit/a90_claw/conftest.py000066400000000000000000000013001461113517100235010ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Test configuration** (i.e., :mod:`pytest`-specific early-time configuration guaranteed to be implicitly imported by :mod:`pytest` into *all* sibling and child submodules of the test subpackage containing this :mod:`pytest` plugin). ''' # ....................{ IMPORTS }.................... # Import all subpackage-specific fixtures implicitly required by tests defined # by submodules of this subpackage. from beartype_test.a00_unit.a90_claw._clawfixture import ( clean_claws, ) beartype-0.18.5/beartype_test/a00_unit/conftest.py000066400000000000000000000021171461113517100221110ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Test configuration** (i.e., :mod:`pytest`-specific early-time configuration guaranteed to be implicitly imported by :mod:`pytest` into *all* sibling and child submodules of the test subpackage containing this :mod:`pytest` plugin). ''' # ....................{ IMPORTS }.................... # Import all subpackage-specific fixtures implicitly required by tests defined # by submodules of this subpackage. from beartype_test.a00_unit.data.hint.data_hint import ( hints_meta, hints_ignorable, not_hints_nonpep, ) from beartype_test.a00_unit.data.hint.nonpep.data_nonpep import ( hints_nonpep_meta, ) from beartype_test.a00_unit.data.hint.pep.data_pep import ( hints_pep_hashable, hints_pep_ignorable_deep, hints_pep_ignorable_shallow, hints_pep_meta, ) from beartype_test.a00_unit.data.hint.util.data_hintmetautil import ( iter_hints_piths_meta, ) beartype-0.18.5/beartype_test/a00_unit/data/000077500000000000000000000000001461113517100206225ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/__init__.py000066400000000000000000000000001461113517100227210ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/check/000077500000000000000000000000001461113517100216775ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/check/__init__.py000066400000000000000000000000001461113517100237760ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/check/forward/000077500000000000000000000000001461113517100233435ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/check/forward/__init__.py000066400000000000000000000000001461113517100254420ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/check/forward/data_fwdref.py000066400000000000000000000060301461113517100261620ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **forward reference proxy data** submodule. This submodule predefines **forward reference proxies** (i.e., low-level objects created by the :func:`beartype._check.forward.reference.fwdrefmake` submodule) exercising known edge cases on behalf of higher-level unit test submodules. ''' # ....................{ IMPORTS }.................... from beartype._check.forward.reference.fwdrefmake import ( make_forwardref_indexable_subtype) # ....................{ CONSTANTS }.................... PACKAGE_NAME = 'beartype_test.a00_unit.data' ''' Fully-qualified name of a subpackage defining an arbitrary submodule. ''' MODULE_BASENAME = 'data_type' ''' Unqualified basename of a submodule in that subpackage defining an arbitrary class. ''' MODULE_NAME = f'{PACKAGE_NAME}.{MODULE_BASENAME}' ''' Fully-qualified name of the same submodule. ''' CLASS_BASENAME = 'Class' ''' Unqualified basename of that class in that module. ''' CLASS_NAME = f'{MODULE_NAME}.{CLASS_BASENAME}' ''' Fully-qualified name of that class. ''' SCOPE_NAME = '__name__' ''' Fully-qualified name of the current test module. ''' # ....................{ FORWARDREFS ~ invalid }.................... FORWARDREF_RELATIVE_CIRCULAR = make_forwardref_indexable_subtype( # Fully-qualified name of the current test module. SCOPE_NAME, # Unqualified basename of this global currently being declared. 'FORWARDREF_RELATIVE_CIRCULAR') ''' **Circular forward reference proxy** (i.e., invalid proxy circularly and thus recursively referring to the same forward reference proxy). Since the only means of declaring a circular forward reference proxy is as a global attribute, the declaration of this proxy is necessarily isolated to its own data submodule. ''' # ....................{ FORWARDREFS ~ valid }.................... FORWARDREF_ABSOLUTE = make_forwardref_indexable_subtype( # Intentionally ignored fully-qualified name of this test submodule. SCOPE_NAME, CLASS_NAME, ) ''' Forward reference proxy to an unsubscripted class referenced with an absolute (i.e., fully-qualified) name. ''' FORWARDREF_RELATIVE = make_forwardref_indexable_subtype( MODULE_NAME, CLASS_BASENAME) ''' Forward reference proxy to an unsubscripted class referenced with a relative (i.e., unqualified) name. ''' FORWARDREF_MODULE_ABSOLUTE = make_forwardref_indexable_subtype( # Intentionally ignored fully-qualified name of this test submodule. SCOPE_NAME, MODULE_NAME, ) ''' Forward reference proxy to a submodule of a subpackage referenced with an absolute (i.e., fully-qualified) name. ''' FORWARDREF_MODULE_CLASS = FORWARDREF_MODULE_ABSOLUTE.Class ''' Forward reference proxy to an unsubscripted class of that submodule, accessed by ``"."``-delimited attribute syntax from an existing forward reference proxy. ''' beartype-0.18.5/beartype_test/a00_unit/data/claw/000077500000000000000000000000001461113517100215505ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/__init__.py000066400000000000000000000000001461113517100236470ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/extraprocess/000077500000000000000000000000001461113517100242725ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/extraprocess/README.rst000066400000000000000000000010211461113517100257530ustar00rootroot00000000000000.. # ------------------( LICENSE )------------------ .. # Copyright (c) 2014-2024 Beartype authors. .. # See "LICENSE" for further details. .. # .. # ------------------( SYNOPSIS )------------------ ======== Synopsis ======== This subpackage contains test modules exercising import hooks installed by the :mod:`beartype.claw` API into a Python subprocess forked from the active Python process (rather than directly into the active Python process) and thus "extraprocess." beartype-0.18.5/beartype_test/a00_unit/data/claw/extraprocess/__init__.py000066400000000000000000000000321461113517100263760ustar00rootroot00000000000000# raise ValueError('ugh') beartype-0.18.5/beartype_test/a00_unit/data/claw/extraprocess/executable_package/000077500000000000000000000000001461113517100300665ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/extraprocess/executable_package/__init__.py000066400000000000000000000020101461113517100321700ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **executable main beartype import hook package initialization submodule** (i.e., data module mimicking real-world usage of the :func:`beartype.claw.beartype_this_package` import hook from a top-level third-party package submodule ``{some_package}.__init__`). ''' # ....................{ IMPORTS }.................... from beartype import BeartypeConf from beartype.claw import beartype_this_package # <-- boilerplate for victory # print(f'__init__.__name__: {__name__}') # print(f'__init__.__package__: {__package__}') # ....................{ HOOKS }.................... # Install a beartype import hook for the current data subpackage directly # containing this submodule, configured by the default beartype configuration. beartype_this_package() beartype-0.18.5/beartype_test/a00_unit/data/claw/extraprocess/executable_package/__main__.py000066400000000000000000000022201461113517100321540ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **executable main beartype import hook main package submodule** (i.e., data module implicitly imported by Python when the fully-qualified name of the package directly containing this module is passed as the argument to Python's ``-m`` command-line option). ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeCallHintParamViolation from executable_package.nonexecutable_submodule import by_solemn_vision from pytest import raises # ....................{ MAIN }.................... # Call the above function with a valid parameter, which then prints that # parameter to standard output. by_solemn_vision(len('And sound from the vast earth and ambient air,')) # Assert that calling the above function with an invalid parameter raises # the expected violation. with raises(BeartypeCallHintParamViolation): by_solemn_vision('His infancy was nurtured. Every sight') nonexecutable_submodule.py000066400000000000000000000015321461113517100352750ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/extraprocess/executable_package#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **executable main beartype import hook non-executable submodule** (i.e., data module *only* intended to be imported from other submodules of this data package rather than directly executed as a script). ''' # ....................{ CALLABLES }.................... def by_solemn_vision(and_bright_silver_dream: int) -> None: ''' Arbitrary callable annotated by trivial PEP-compliant type hints. To enable the parent Python process to validate that this callable actually *was* successfully called, this callable prints the passed parameter as is to standard output. ''' print(and_bright_silver_dream) beartype-0.18.5/beartype_test/a00_unit/data/claw/extraprocess/executable_submodule/000077500000000000000000000000001461113517100304725ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/extraprocess/executable_submodule/__init__.py000066400000000000000000000020151461113517100326010ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **executable submodule beartype import hook package initialization submodule** (i.e., data module mimicking real-world usage of the :func:`beartype.claw.beartype_this_package` import hook from a top-level third-party package submodule ``{some_package}.__init__`). ''' # ....................{ IMPORTS }.................... from beartype import BeartypeConf from beartype.claw import beartype_this_package # <-- boilerplate for victory # print(f'__init__.__name__: {__name__}') # print(f'__init__.__package__: {__package__}') # ....................{ HOOKS }.................... # Install a beartype import hook for the current data subpackage directly # containing this submodule, configured by the default beartype configuration. beartype_this_package() beartype-0.18.5/beartype_test/a00_unit/data/claw/extraprocess/executable_submodule/main_submodule.py000066400000000000000000000034711461113517100340540ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **executable submodule beartype import hook package initialization submodule** (i.e., data module mimicking real-world usage of the :func:`beartype.claw.beartype_this_package` import hook from a top-level third-party package submodule ``{some_package}.__init__`). ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeCallHintParamViolation from pytest import raises # ....................{ CALLABLES }.................... def and_silence(too_enamoured_of_that_voice: float) -> None: ''' Arbitrary callable annotated by trivial PEP-compliant type hints. To enable the parent Python process to validate that this callable actually *was* successfully called, this callable prints the passed parameter as is to standard output. ''' print(too_enamoured_of_that_voice) # ....................{ MAIN }.................... # If the unqualified basename of this submodule is that of the pseudo-module # "__main__", then this submodule was actually run as a script via Python's # standard command-line option "-m". In this case... if __name__ == '__main__': # Call the above function with a valid parameter, which then prints that # parameter to standard output. and_silence(float(len('His infancy was nurtured. Every sight'))) # Assert that calling the above function with an invalid parameter raises # the expected violation. with raises(BeartypeCallHintParamViolation): and_silence('Locks its mute music in her rugged cell.') beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/000077500000000000000000000000001461113517100242645ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/README.rst000066400000000000000000000006731461113517100257610ustar00rootroot00000000000000.. # ------------------( LICENSE )------------------ .. # Copyright (c) 2014-2024 Beartype authors. .. # See "LICENSE" for further details. .. # .. # ------------------( SYNOPSIS )------------------ ======== Synopsis ======== This subpackage contains test modules exercising import hooks installed by the :mod:`beartype.claw` API into the active Python process and thus "intraprocess." beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/__init__.py000066400000000000000000000000001461113517100263630ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/hookable_package/000077500000000000000000000000001461113517100275235ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/hookable_package/__init__.py000066400000000000000000000000001461113517100316220ustar00rootroot00000000000000beartype_this_package/000077500000000000000000000000001461113517100337615ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/hookable_package__init__.py000066400000000000000000000023241461113517100360730ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/hookable_package/beartype_this_package#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **current package beartype import hook package initialization submodule** (i.e., data module mimicking real-world usage of the :func:`beartype.claw.beartype_this_package` import hook from a top-level third-party package submodule ``{some_package}.__init__`). ''' # ....................{ IMPORTS }.................... from beartype import BeartypeConf from beartype.claw import beartype_this_package # print(f'__init__.__name__: {__name__}') # print(f'__init__.__package__: {__package__}') # ....................{ HOOKS }.................... # Install a beartype import hook for the current data subpackage directly # containing this submodule, configured by an arbitrary non-default beartype # configuration to exercise that this hook correctly recursively applies this # configuration to *ALL* submodules of this subpackage. beartype_this_package(conf=BeartypeConf(is_pep484_tower=True)) # print(f'"beartype_this_package" conf id: {id(BeartypeConf(is_pep484_tower=True))}') empty_doc_future.py000066400000000000000000000011141461113517100377050ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/hookable_package/beartype_this_package#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Semantically empty submodule containing only only zero or more whitespace characters zero or more inline comments, this prefacing module docstring, and one or more ``from __future__`` import statements -- exercising a pernicious edge case in AST transformation that actually happened and destroyed everything. ''' # Arbitrary "from __future__" import statement. from __future__ import annotations empty_doc_nofuture.py000066400000000000000000000007051461113517100402470ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/hookable_package/beartype_this_package#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Semantically empty submodule containing only only zero or more whitespace characters zero or more inline comments, and this prefacing module docstring -- exercising a pernicious edge case in AST transformation that actually happened and destroyed everything. ''' empty_nodoc_future.py000066400000000000000000000010511461113517100402420ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/hookable_package/beartype_this_package#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. # Semantically empty submodule containing only only zero or more whitespace # characters zero or more inline comments, and one or more "from __future__" # import statements -- exercising a pernicious edge case in AST transformation # that actually happened and destroyed everything. # Arbitrary "from __future__" import statement. from __future__ import annotations empty_nodoc_nofuture.py000066400000000000000000000006321461113517100406030ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/hookable_package/beartype_this_package#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. # Literally empty submodule containing only zero or more whitespace characters # and zero or more inline comments -- exercising a pernicious edge case in AST # transformation that actually happened and destroyed everything. this_submodule.py000066400000000000000000000324721461113517100373710ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/hookable_package/beartype_this_package#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **current package beartype import hooked submodule** (i.e., data module mimicking real-world usage of the :func:`beartype.claw.beartype_this_package` import hook from an arbitrarily deeply nested submodule ``{some_package}...{some_submodule}` of an arbitrary third-party package ``{some_package}``). ''' # ....................{ IMPORTS }.................... from beartype import beartype from beartype.roar import ( BeartypeCallHintParamViolation, BeartypeCallHintReturnViolation, BeartypeDoorHintViolation, ) from beartype.typing import ( # Iterable, List, Optional, Union, ) from pytest import raises #FIXME: Fascinatingly, this doesn't actually test anything. Why? We have *NO* #idea whatsoever, but strongly suspect that pytest's own import hooks are #silently conflicting with @beartype's in a manner preventing @beartype from #guarding against regressions in this functionality. For now, the *ONLY* means #of testing this is to do so manually in an external module subject to a #beartype import hook that performs this import. Gag me with a spork. # Intentionally import a pure-Python module from the standard library that # internally imports a C extension -- in this case, "_struct". Doing so ensures # that beartype import hooks properly support loading of C extensions, which our # initial implementation did *NOT*. Avoid regressions by testing this, please. import struct # from beartype.claw._importlib.clawimpcache import module_name_to_beartype_conf # print(f'this_submodule conf: {repr(module_name_to_beartype_conf)}') # ....................{ PEP 526 }.................... # Validate that the beartype_this_package() import hook installed by the parent # "beartype_this_package.__init__" submodule implicitly appends all PEP # 526-compliant annotated assignment statements in other submodules of this # "beartype_this_package" subpackage with calls to beartype's statement-level # beartype.door.die_if_unbearable() exception-raiser. # Assert that a PEP 526-compliant assignment statement assigning an object # satisfying the type hint annotating that statement raises *NO* exception. # # Note that this type hint is intentionally annotated as "float" rather than # either "int" *OR* "Union[int, float]", exercising that that import hook # successfully associated this and *ALL* other submodules of the # "beartype_this_package" subpackage with a non-default beartype configuration # enabling the PEP 484-compliant implicit numeric tower (i.e., # "is_pep484_tower=True"). loves_philosophy: float = len('The fountains mingle with the river') # Assert that a PEP 526-compliant assignment statement assigning an object # violating the type hint annotating that statement raises the expected # exception. with raises(BeartypeDoorHintViolation): and_the_rivers_with_the_ocean: List[str] = ( 'The winds of heaven mix for ever') # ....................{ FUNCTIONS ~ accept }.................... # Functions whose type-checking implicitly accepts the non-default beartype # configuration established by the sibling "__init__" submodule in this # subpackage. def nothing_in_the_world(is_single: Union[float, bytes]) -> Optional[complex]: ''' Arbitrary function either returning the passed integer first doubled and then coerced into a complex number with imaginary component ``1`` if this integer is non-zero *or* raising a :exc:`.BeartypeCallHintParamViolation` exception otherwise (i.e., if this integer is zero), exercising that beartype import hooks decorate global functions as expected. ''' def in_one_spirit(meet_and_mingle: Optional[float]) -> Union[complex, str]: ''' Arbitrary closure either returning the passed integer first doubled and then coerced into a complex number with imaginary component ``1`` if this integer is non-:data:`None` *or* raising a :exc:`.BeartypeCallHintReturnViolation` exception otherwise (i.e., if this integer is :data:`None`), exercising that beartype import hooks decorate closures as expected. ''' # Return either... return ( # If this integer is "None", explicitly force this # @beartype-decorated closure to raise a # "BeartypeCallHintReturnViolation" exception; None if meet_and_mingle is None else # Else, this integer is non-"None". In this case, return this # integer doubled and then coerced into a complex number with # imaginary component "1". Why? Just because. *THIS IS BEARTYPE.* is_single + meet_and_mingle + 1j ) # Return either... return ( # If this integer is zero, explicitly force this @beartype-decorated # closure to raise a "BeartypeCallHintReturnViolation" exception. in_one_spirit(None) if is_single == 0 else # Else, this integer is non-zero. In this case, return this integer # doubled and then coerced into a complex number with imaginary # component "1". Why? Just because. *THIS IS BEARTYPE.* Graaaah! in_one_spirit(is_single) ) # Assert that calling this function passed an arbitrary integer returns the # expected complex number *WITHOUT* raising an exception. assert nothing_in_the_world(len('Why not I with thine?')) == 42 + 1j # Assert that calling this function passed an invalid parameter raises the # expected exception. with raises(BeartypeCallHintParamViolation): nothing_in_the_world('See the mountains kiss high heaven') # Assert that calling this function passed zero raises the expected exception # from the closure defined and called by this function. with raises(BeartypeCallHintReturnViolation): nothing_in_the_world(len('And the waves') - len('clasp another')) # ....................{ FUNCTIONS ~ reject }.................... # Functions whose type-checking explicitly rejects the non-default beartype # configuration established by the sibling "__init__" submodule in this # subpackage (i.e., due to being decorated by the @beartype decorator implicitly # configured by the default beartype configuration). In particular, these # functions disable the PEP 484-compliant implicit numeric tower such that: # * "float" just means "float" *WITHOUT* being expanded to "float | int". # * "complex" just means "complex" *WITHOUT* being expanded to "complex | float # | int". @beartype # <-- prefer the default beartype configuration def alastor_or(the_spirit_of_solitude: Union[float, bytes]) -> ( Optional[complex]): ''' Arbitrary function either returning the passed float first doubled and then coerced into a complex number with imaginary component ``1`` if this float is non-zero *or* raising a :exc:`.BeartypeCallHintParamViolation` exception otherwise (i.e., if this float is zero), exercising that beartype import hooks decorate global functions as expected. ''' @beartype # <-- prefer the default beartype configuration def nondum_amabam(et_amare_amabam: Optional[float]) -> Union[complex, str]: ''' Arbitrary closure either returning the passed float first doubled and then coerced into a complex number with imaginary component ``1`` if this float is non-:data:`None` *or* raising a :exc:`.BeartypeCallHintReturnViolation` exception otherwise (i.e., if this float is :data:`None`), exercising that beartype import hooks decorate closures as expected. ''' # Return either... return ( # If this float is "None", explicitly force this # @beartype-decorated closure to raise a # "BeartypeCallHintReturnViolation" exception; None if et_amare_amabam is None else # Else, this float is non-"None". In this case, return this float # doubled and then coerced into a complex number with imaginary # component "1". Why? Just because. *THIS IS BEARTYPE.* Graaaah! the_spirit_of_solitude + et_amare_amabam + 1j ) # Return either... return ( # If this float is zero, explicitly force this @beartype-decorated # closure to raise a "BeartypeCallHintReturnViolation" exception. nondum_amabam(None) if the_spirit_of_solitude == 0.0 else # Else, this integer is non-zero. In this case, return this integer # doubled and then coerced into a complex number with imaginary # component "1". Why? Just because. *THIS IS BEARTYPE.* Graaaah! nondum_amabam(the_spirit_of_solitude) ) # Assert that calling this function passed an arbitrary float returns the # expected complex number *WITHOUT* raising an exception. assert alastor_or(len('Earth, ocean, air, beloved brotherhood!') + 0.0) == ( 78 + 1j) # Assert that calling this function passed an invalid parameter raises the # expected exception. with raises(BeartypeCallHintParamViolation): alastor_or(len('If our great Mother has imbued my soul')) # Assert that calling this function passed zero raises the expected exception # from the closure defined and called by this function. with raises(BeartypeCallHintReturnViolation): alastor_or(len('quid amarem') - len('amans amare') + 0.0) # ....................{ FUNCTIONS ~ signature }.................... # Functions whose signatures exhibit various uncommon edge cases preventing # regressions in the "beartype.claw" API. def of_desperate_hope(*, wrinkled_his, quivering_lips: str): ''' Arbitrary function annotated *only* by a single keyword-only parameter. Yes, this previously blew up. It's best *not* to ask why. We shudder. ''' return wrinkled_his + quivering_lips def for_sleep(he_knew, /, kept_most_relentlessly: str): ''' Arbitrary function annotated *only* by a single positional-only parameter. Yes, this previously blew up. It's best *not* to ask why. We shudder. ''' return he_knew + kept_most_relentlessly # Assert these functions return the expected values when passed the expected # parameters. assert of_desperate_hope( wrinkled_his='Of desperate hope ', quivering_lips='wrinkled his quivering lips.', ) == 'Of desperate hope wrinkled his quivering lips.' assert for_sleep('For sleep, he knew, ', 'kept most relentlessly') == ( 'For sleep, he knew, kept most relentlessly') # Assert these functions raise the expected exceptions when passed unexpected # parameters. with raises(BeartypeCallHintParamViolation): of_desperate_hope( wrinkled_his=b'Its precious charge, ', quivering_lips=b'and silent death exposed,', ) with raises(BeartypeCallHintParamViolation): for_sleep(b'Faithless perhaps as sleep, ', b'a shadowy lure,') # ....................{ CLASSES }.................... class ConfessStAugust(object): ''' Arbitrary class to be implicitly decorated by the :func:`beartype.beartype` decorator by the :func:`beartype.claw.beartype_this_package` import hook installed by the parent ``beartype_this_package.__init__`` submodule. ''' def with_aught_of_natural_piety(self, to_feel: Union[complex, str]) -> ( Union[List[bytes], complex]): ''' Arbitrary method accepting the passed object under the non-default beartype configuration established by the sibling ``__init__`` submodule in this subpackage and returning that object as is, enabling callers to trivially test whether any call to this method violates the type hints annotating this method. ''' # Look, @beartype. Just do it! return to_feel @beartype # <-- prefer the default beartype configuration def your_love( self, and_recompense_the_boon_with_mine: Union[complex, bool]) -> ( Union[complex, List[bytes]]): ''' Arbitrary method accepting the passed object under the default beartype configuration and returning that object as is, enabling callers to trivially test whether any call to this method violates the type hints annotating this method. ''' # Look here, you. Just do it yet again! return and_recompense_the_boon_with_mine # Arbitrary instance of this class. if_dewy_morn = ConfessStAugust() # Assert that calling the first method passed an arbitrary integer returns that # integer as is *WITHOUT* raising an exception. assert if_dewy_morn.with_aught_of_natural_piety(len( 'If dewy morn, and odorous noon, and even')) == 40 # Assert that calling the first method passed an invalid parameter raises the # expected exception. with raises(BeartypeCallHintParamViolation): if_dewy_morn.with_aught_of_natural_piety( b'With sunset and its gorgeous ministers,') # Assert that calling the second method passed an arbitrary complex number # returns that complex number as is *WITHOUT* raising an exception. assert if_dewy_morn.your_love(len( "And solemn midnight's tingling silentness;") + 1j) == 42 + 1j # Assert that calling the second method passed an invalid parameter raises the # expected exception. with raises(BeartypeCallHintParamViolation): if_dewy_morn.your_love(len("If autumn's hollow sighs in the sere wood,")) beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/hookable_package/kind/000077500000000000000000000000001461113517100304505ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/hookable_package/kind/__init__.py000066400000000000000000000012241461113517100325600ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **beartype import hook data type subpackage initialization submodule** (i.e., data module mimicking real-world usage of various :func:`beartype.claw` import hooks on various types of objects, including both callables and non-callable containers). ''' # ....................{ IMPORTS }.................... from beartype_test.a00_unit.data.claw.intraprocess.hookable_package.kind import ( data_claw_type, data_claw_func, ) data_claw_func.py000066400000000000000000000127011461113517100336760ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/hookable_package/kind#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **beartype import hookable function submodule** (i.e., data module containing *only* annotated functions, mimicking real-world usage of the :func:`beartype.claw.beartype_package` import hook from an external caller). ''' # ....................{ IMPORTS }.................... from beartype import ( beartype, BeartypeConf, ) from beartype.roar import ( BeartypeClawDecorWarning, BeartypeCallHintParamViolation, BeartypeCallHintReturnViolation, BeartypeDecorHintPep484Exception, ) from beartype.typing import ( NoReturn, Optional, Union, ) from pytest import ( raises, warns, ) # from beartype.claw._importlib.clawimpcache import module_name_to_beartype_conf # print(f'this_submodule conf: {repr(module_name_to_beartype_conf)}') # ....................{ FUNCTIONS }.................... def thee_ever_and_thee_only(i_have_watched: Union[float, bytes]) -> ( Optional[complex]): ''' Arbitrary method either returning the passed float first doubled and then coerced into a complex number with imaginary component ``1`` if this float is non-zero *or* raising a :exc:`.BeartypeCallHintParamViolation` exception otherwise (i.e., if this float is zero), exercising that beartype import hooks decorate global functions as expected. ''' def thy_shadow(and_the_darkness_of_thy_steps: Optional[float]) -> ( Union[complex, str]): ''' Arbitrary closure either returning the passed float first doubled and then coerced into a complex number with imaginary component ``1`` if this float is non-:data:`None` *or* raising a :exc:`.BeartypeCallHintReturnViolation` exception otherwise (i.e., if this float is :data:`None`), exercising that beartype import hooks decorate closures as expected. ''' # Return either... return ( # If this float is "None", explicitly force this # @beartype-decorated closure to raise a # "BeartypeCallHintReturnViolation" exception; None if and_the_darkness_of_thy_steps is None else # Else, this float is non-"None". In this case, return this float # doubled and then coerced into a complex number with imaginary # component "1". Why? Just because. *THIS IS BEARTYPE.* Graaaah! i_have_watched + and_the_darkness_of_thy_steps + 1j ) # Return either... return ( # If this float is zero, explicitly force this @beartype-decorated # closure to raise a "BeartypeCallHintReturnViolation" exception. thy_shadow(None) if i_have_watched == 0.0 else # Else, this integer is non-zero. In this case, return this integer # doubled and then coerced into a complex number with imaginary # component "1". Why? Just because. *THIS IS BEARTYPE.* Graaaah! thy_shadow(i_have_watched) ) # ....................{ PASS }.................... # Assert that calling this function passed an arbitrary float returns the # expected complex number *WITHOUT* raising an exception. assert thee_ever_and_thee_only( len('And my heart ever gazes on the depth') + 0.0) == ( 72 + 1j) # ....................{ FAIL }.................... # Assert that calling this function passed an invalid parameter raises the # expected exception. with raises(BeartypeCallHintParamViolation): thee_ever_and_thee_only(len('Of thy deep mysteries. I have made my bed')) # Assert that calling this function passed zero raises the expected exception # from the closure defined and called by this function. with raises(BeartypeCallHintReturnViolation): thee_ever_and_thee_only( len('In and on coffins') - len('where black death') + 0.0) # ....................{ FAIL ~ decoration }.................... # Assert that attempting to define a function whose type hints violate one or # more PEP standards at decoration time emits the expected non-fatal warning. with warns(BeartypeClawDecorWarning): def but_the_charmed_eddies(of_autumnal_winds: NoReturn) -> None: ''' Arbitrary callable accepting a parameter erroneously annotated with the :pep:`484`-compliant :obj:`typing.NoReturn` type hint *only* allowed on returns and thus raising a decoration-time exception, which the :mod:`beartype.claw` API then coerces into a non-fatal warning. ''' pass # Assert that attempting to define a function whose type hints violate one or # more PEP standards at decoration time emits the expected exception when that # function is configured by a non-default configuration instructing the # "beartype.claw" API to raise exceptions rather than emit non-fatal warnings at # decoration time. *PHEW!* with raises(BeartypeDecorHintPep484Exception): @beartype(conf=BeartypeConf(warning_cls_on_decorator_exception=None)) def built_over_his_mouldering_bones(a_pyramid: NoReturn) -> None: ''' Arbitrary callable accepting a parameter erroneously annotated with the :pep:`484`-compliant :obj:`typing.NoReturn` type hint *only* allowed on returns and thus raising a decoration-time exception. ''' pass data_claw_type.py000066400000000000000000000127521461113517100337320ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/hookable_package/kind#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **beartype import hookable class submodule** (i.e., data module containing *only* annotated classes, mimicking real-world usage of the :func:`beartype.claw.beartype_package` import hook from an external caller). ''' # ....................{ IMPORTS }.................... from beartype import ( BeartypeConf, beartype, ) from beartype.roar import ( BeartypeCallHintParamViolation, BeartypeDoorHintViolation, ) from beartype.typing import ( List, Union, ) from pytest import raises # from beartype.claw._importlib.clawimpcache import module_name_to_beartype_conf # print(f'this_submodule conf: {repr(module_name_to_beartype_conf)}') # ....................{ CLASSES }.................... # Classes whose type-checking explicitly rejects the default beartype # configuration assumed by the remainder of this subpackage (i.e., due to being # decorated by the @beartype decorator explicitly configured by a non-default # beartype configuration). In particular, these classes enable the PEP # 484-compliant implicit numeric tower such that: # * "float" is implicitly expanded to "float | int". # * "complex" is implicitly expanded to "complex | float | int". @beartype(conf=BeartypeConf(is_pep484_tower=True)) # <-- prefer non-defaults # @beartype(conf=BeartypeConf(is_pep484_tower=True, is_debug=True)) # <-- prefer non-defaults class HerFirstSweetKisses(object): ''' Arbitrary class explicitly decorated by the :func:`beartype.beartype` decorator with non-default parameters. ''' def if_no_bright_bird( self, insect_or_gentle_beast: Union[complex, str]) -> ( Union[List[bytes], complex]): ''' Arbitrary method accepting the passed object under the non-default beartype configuration decorating this class and returning that object as is, enabling callers to trivially test whether any call to this method violates the type hints annotating this method. ''' # ....................{ PASS }.................... # Implicitly assert that assigning a valid value to an annotated local # or global variable in a method raises *NO* exception. # # Note that this edge case is distinct from assigning to an annotated # instance or class variable in a method. his_wandering_step: int = len('More graceful than her own') # Implicitly assert that assigning a valid value to an annotated # instance or class variable in a method raises *NO* exception. # # Note that this edge case is distinct from assigning to an annotated # local or global variable in a method. self.the_awful_ruins: bytes = b'The awful ruins of the days of old:' # ....................{ FAIL }.................... # Explicitly assert that assigning an invalid value to an annotated # local or global variable raises the expected exception. with raises(BeartypeDoorHintViolation): his_wandering_step: int = 'Obedient to high thoughts, has visited' # Explicitly assert that assigning an invalid value to an annotated # instance or class variable raises the expected exception. with raises(BeartypeDoorHintViolation): self.the_awful_ruins: bytes = ( 'Athens, and Tyre, and Balbec, and the waste') # Look, @beartype. Just do it! return insect_or_gentle_beast @beartype # <-- prefer the default beartype configuration yet again def i_consciously_have_injured( self, but_still_loved: Union[complex, bool]) -> ( Union[complex, List[bytes]]): ''' Arbitrary method accepting the passed object under the default beartype configuration and returning that object as is, enabling callers to trivially test whether any call to this method violates the type hints annotating this method. ''' # Look here, you. Just do it yet again! return but_still_loved # ....................{ PASS }.................... # Arbitrary instance of this class. and_cherished_these_my_kindred = HerFirstSweetKisses() # Assert that calling the first method passed an arbitrary integer returns that # integer as is *WITHOUT* raising an exception. assert and_cherished_these_my_kindred.if_no_bright_bird(len( 'This boast, belovèd brethren, and withdraw')) == 42 # ....................{ FAIL }.................... # Assert that calling the first method passed an invalid parameter raises the # expected exception. with raises(BeartypeCallHintParamViolation): and_cherished_these_my_kindred.if_no_bright_bird( b'No portion of your wonted favour now!') # Assert that calling the second method passed an arbitrary complex number # returns that complex number as is *WITHOUT* raising an exception. assert and_cherished_these_my_kindred.i_consciously_have_injured(len( 'Mother of this unfathomable world!') + 1j) == 34 + 1j # Assert that calling the second method passed an invalid parameter raises the # expected exception. with raises(BeartypeCallHintParamViolation): and_cherished_these_my_kindred.i_consciously_have_injured(len( 'Favour my solemn song, for I have loved')) beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/hookable_package/pep/000077500000000000000000000000001461113517100303075ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/hookable_package/pep/__init__.py000066400000000000000000000020061461113517100324160ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **beartype import hook Python enhancement proposal (PEP) subpackage initialization submodule** (i.e., data module mimicking real-world usage of various :func:`beartype.claw` import hooks on various PEPs). ''' # ....................{ IMPORTS }.................... from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_12 from beartype_test.a00_unit.data.claw.intraprocess.hookable_package.pep import ( data_claw_pep557, pep526, ) # If the active Python interpreter targets Python >= 3.12 and thus supports PEP # 695... if IS_PYTHON_AT_LEAST_3_12: # Defer version-specific imports. from beartype_test.a00_unit.data.claw.intraprocess.hookable_package.pep import ( data_claw_pep695) # Else, this interpreter targets Python < 3.12 and thus fails to support PEP # 695. data_claw_pep557.py000066400000000000000000000051041461113517100336260ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/hookable_package/pep#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **beartype import hookable** :pep:`557` **submodule** (i.e., data module containing *only* :pep:`557`-compliant dataclass declarations, mimicking real-world usage of the :func:`beartype.claw.beartype_package` import hook on an external package declaring these dataclasses). ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeCallHintParamViolation # from beartype.typing import ( # List, # Union, # ) from dataclasses import ( dataclass, field, ) from pytest import raises # from beartype.claw._clawstate import claw_state # print(f'this_submodule conf: {repr(claw_state.module_name_to_beartype_conf)}') # ....................{ PEP 557 }.................... @dataclass class ToRenderUpThyCharge(object): ''' Arbitrary dataclass implicitly decorated by the :func:`beartype.beartype` decorator with non-default parameters. ''' # This annotated field declaration is safely type-checkable by # die_if_unbearable(), clearly. and_though_neer_yet: str = 'Thou hast unveiled thy inmost sanctuary,' # This annotated field declaration is *NOT* safely type-checkable by # die_if_unbearable(). Clearly, a dataclass "field" instance is *NOT* a # valid string and thus violates the type hint annotating this field. Since # PEP 681 standardizes declarations like this as semantically valid, # beartype has *NO* alternative but to quietly turn a blind eye to what # otherwise might be considered a type violation. and_twilight_phantasms: str = field( default='Enough from incommunicable dream,') # Assert that instantiating this dataclass both with *AND* without passing valid # initialization parameters succeeds as expected. and_deep_noonday_thought = ToRenderUpThyCharge() has_shone_within_me = ToRenderUpThyCharge( and_though_neer_yet='And moveless, as a long-forgotten lyre', and_twilight_phantasms='Suspended in the solitary dome', ) # Assert that instantiating this dataclass with invalid initialization # parameters raises the expected exception. with raises(BeartypeCallHintParamViolation): ToRenderUpThyCharge( and_though_neer_yet=b'Of some mysterious and deserted fane,') with raises(BeartypeCallHintParamViolation): ToRenderUpThyCharge(and_twilight_phantasms=( b'I wait thy breath, Great Parent, that my strain')) data_claw_pep695.py000066400000000000000000000202301461113517100336260ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/hookable_package/pep#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **beartype import hookable** :pep:`695` **submodule** (i.e., data module containing *only* :pep:`695`-compliant type aliases and callables and classes annotated by those aliases, mimicking real-world usage of the :func:`beartype.claw.beartype_package` import hook on an external package utilizing type aliases). This submodule *only* exercises **forward referencing type aliases** (i.e., :pep:`695`-compliant ``type`` statements containing one or more unquoted relative forward references). Due to deficiencies in CPython's low-level C-based implementation of :pep:`695`-compliant type aliases, type-checking forward reference type aliases requires abstract syntax tree (AST) transformations and thus beartype import hooks. Caveats ------- **This submodule requires the active Python interpreter to target at least Python 3.12.0.** If this is *not* the case, importing this submodule raises an :exc:`SyntaxError` exception. See Also -------- :mod:`beartype_test.a00_util.data.hint.pep.proposal._data_pep695` Standard workflow defining all other type aliases to be tested, whose type-checking does *not* require AST transformations. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar import ( BeartypeCallHintParamViolation, BeartypeClawDecorWarning, BeartypeDecorHintPep695Exception, ) from beartype.typing import Union from pytest import ( raises, warns, ) # ....................{ ALIASES }.................... # Global forward referenced type alias containing: # * Two or more unquoted relative forward references to a user-defined class # that has yet to be defined. # * Two or more quoted relative forward references to another user-defined class # that has yet to be defined # # Note that we intentionally: # * Embed at least two unquoted relative forward references in this alias. Why? # Because the low-level reduce_hint_pep695() reducer responsible for handling # PEP 695-compliant type aliases is expected to iteratively resolve *ALL* # unquoted relative forward references in this alias -- *NOT* simply the first # relative forward reference in this alias. # * Embed the same unquoted relative forward reference multiple times in the # same alias. Although unlikely in real-world code, this edge case is # sufficiently horrible to warrant explicit testing. type GlobalRefAlias = Union[KindledThrough, OfHerPureMind, OfHerPureMind] # ....................{ CALLABLES }.................... def a_permeating_fire( wild_numbers_then: GlobalRefAlias | None) -> GlobalRefAlias: ''' Arbitrary :func:`.beartype`-decorated callable annotated by a global forward referenced type alias containing one or more forward references. ''' return KindledThrough(wild_numbers_then.she_raised) # ....................{ CLASSES }.................... class OfHerPureMind(object): ''' Arbitrary class referred to by the above type alias. ''' she_raised: str = 'She raised, with voice stifled in tremulous sobs' class KindledThrough(object): ''' Arbitrary class referred to by the above type alias. ''' def __init__(self, her_fair_hands: str) -> None: self.her_fair_hands = her_fair_hands # Instance of the former above class. were_bare_alone = OfHerPureMind() # ....................{ PASS }.................... # Assert that a @beartype-decorated function annotated by a global forward # referenced type alias returns the expected object. subdued_by_its_own_pathos = a_permeating_fire(were_bare_alone) assert subdued_by_its_own_pathos.her_fair_hands == ( 'She raised, with voice stifled in tremulous sobs') # Assert that @beartype raises the expected violation when calling a # @beartype-decorated function erroneously violating a global forward referenced # type alias. with raises(BeartypeCallHintParamViolation): a_permeating_fire('Were bare alone, sweeping from some strange harp') # ....................{ CALLABLES }.................... def _sweeping_from_some_strange_harp() -> None: ''' Arbitrary callable exercising various edge cases in :pep:`695`-compliant ``type`` statements defined at local rather than global scope. ''' #FIXME: *BIG YIKES.* CPython's low-level C-based implementation of PEP #695-compliant type aliases currently fails to properly resolve unquoted #relative forward references defined in a local rather than global scope. I #tried literally everything to get this to work via AST transformations -- #but whatever arcane type alias machinery it is that they've implemented #simply does *NOT* behave as expected at local scope. That said, we've #verified this *SHOULD* work via this simple snippet: # >>> type bar = wut # >>> globals()['wut'] = str # >>> print(bar.__value__) # str # #That behaves as expected -- until you actually then define the expected #class at local scope: # def foo(): # type bar = wut # globals()['wut'] = str # print(bar.__value__) # class wut(object): pass # <-- this causes madness; WTF!?!?!? # foo() # #The above print() statement now raises non-human readable exceptions #resembling: # NameError: cannot access free variable 'wut' where it is not associated # with a value in enclosing scope # #Clearly, this is madness. At the point at which the print() statement is #run, the "wut" class has yet to be redefined as a class. This constitutes a #profound CPython bug. Please submit us up the F-F-F-bomb. # ....................{ ALIASES }.................... # Local forward referenced type alias containing an unquoted relative # forward reference to a user-defined class that has yet to be defined. type LocalRefAlias = StrangeSymphony # ....................{ CALLABLES }.................... def in_their_branching_veins( the_eloquent_blood: LocalRefAlias | None) -> LocalRefAlias: ''' Arbitrary :func:`.beartype`-decorated nested callable annotated by a local forward referenced type alias containing one or more forward references. ''' return StrangeSymphony(the_eloquent_blood.told_an_ineffable_tale) # ....................{ CLASSES }.................... class StrangeSymphony(object): ''' Arbitrary class referred to by the above type alias. ''' told_an_ineffable_tale: str = 'Her beamy bending eyes, her parted lips' # Instance of the above class. her_beamy_bending_eyes = StrangeSymphony() # # ....................{ PASS }.................... # # Assert that a @beartype-decorated function annotated by a global forward # # referenced type alias returns the expected object. # her_parted_lips = in_their_branching_veins(her_beamy_bending_eyes) # assert her_parted_lips.told_an_ineffable_tale == ( # 'Her beamy bending eyes, her parted lips') # # # Assert that @beartype raises the expected violation when calling a # # @beartype-decorated function erroneously violating a local forward # # referenced type alias. # with raises(BeartypeCallHintParamViolation): # in_their_branching_veins( # 'Outstretched, and pale, and quivering eagerly.') # ....................{ FAIL }.................... # Assert that @beartype raises the expected exception when attempting to define # a local forward referenced type alias containing an unquoted relative forward # reference to a user-defined class that has yet to be defined. Sadly, CPython's # current implementation of PEP 695 is fundamentally broken -- especially in # local scopes. See above. with raises(BeartypeDecorHintPep695Exception): _sweeping_from_some_strange_harp() beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/hookable_package/pep/pep526/000077500000000000000000000000001461113517100313305ustar00rootroot00000000000000__init__.py000066400000000000000000000024101461113517100333570ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/hookable_package/pep/pep526#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **beartype import hook** :pep:`526` **subpackage initialization submodule** (i.e., data module mimicking real-world usage of various :func:`beartype.claw` import hooks on :pep:`526`-compliant annotated variable assignments). ''' # ....................{ IMPORTS }.................... from beartype import BeartypeConf from beartype.claw import beartype_package from beartype.roar import BeartypeValeLambdaWarning from beartype_test.a00_unit.data.claw.intraprocess.hookable_package.pep.pep526 import ( data_claw_pep526_raise) # Subject this single module to a beartype import hook configured to emit # non-fatal warnings of an arbitrary beartype-specific warning type unlikely to # arise via accidental change rather than raise fatal exceptions. beartype_package( 'beartype_test.a00_unit.data.claw.intraprocess.hookable_package.pep.pep526.data_claw_pep526_warn', conf=BeartypeConf(violation_door_type=BeartypeValeLambdaWarning), ) from beartype_test.a00_unit.data.claw.intraprocess.hookable_package.pep.pep526 import ( data_claw_pep526_warn) data_claw_pep526_raise.py000066400000000000000000000122351461113517100360310ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/hookable_package/pep/pep526#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **beartype import hookable** :pep:`526` **exception submodule** (i.e., data module containing *only* :pep:`526`-compliant annotated variable assignments raising :class:`beartype.roar.BeartypeDoorHintViolation` violations, mimicking real-world usage of the :func:`beartype.claw.beartype_package` import hook from an external caller). ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeDoorHintViolation from beartype.typing import ( List, Union, ) from beartype._util.text.utiltextrepr import represent_object from pytest import raises # Import another submodule of this subpackage implicitly installing different # import hooks configured by another beartype configuration isolated to that # submodule. Doing so exercises that beartype import hooks correctly support # import hook composition (i.e., combinations of arbitrary import hooks). from beartype_test.a00_unit.data.claw.intraprocess.hookable_package.beartype_this_package import ( this_submodule) # from beartype.claw._clawstate import claw_state # print(f'this_submodule conf: {repr(claw_state.module_name_to_beartype_conf)}') # ....................{ PEP 526 }.................... # Validate that the import hook presumably installed by the caller implicitly # appends all PEP 526-compliant annotated assignment statements in this # submodule with calls to beartype's statement-level # beartype.door.die_if_unbearable() exception-raiser. # Assert that a PEP 526-compliant annotated assignment statement assigning an # object satisfying the type hint annotating that statement raises *NO* # exception. and_winter_robing: int = len('And winter robing with pure snow and crowns') # Assert that a PEP 526-compliant annotated statement lacking an assignment # raises *NO* exception. such_magic_as_compels_the_charmed_night: str # Arbitrary object violating type hints below. OF_STARRY_ICE = len('Of starry ice the grey grass and bare boughs;') # Assert that a PEP 526-compliant annotated assignment statement assigning an # object violating the type hint annotating that statement raises the expected # exception. with raises(BeartypeDoorHintViolation) as exception_info: of_starry_ice: Union[float, List[str]] = OF_STARRY_ICE # Exception message raised by that assignment. exception_message = str(exception_info.value) # Truncated representation of the invalid object assigned by that assignment. pith_repr = represent_object(OF_STARRY_ICE) # Assert that this message contains a truncated representation of this pith. assert pith_repr in exception_message # Assert that this raiser successfully replaced the temporary # placeholder previously prefixing this message. assert 'global ' in exception_message.lower() assert __name__ in exception_message assert ' violates type hint ' in exception_message # ....................{ CLASSES }.................... class ThePausesOfHerMusic(object): and_her_breath: int = 'The pauses of her music, and her breath' ''' Class variable whose initial value violates the :pep:`526`-compliant type hint annotating this variable. This variable effectively validates that the import hook presumably installed by the caller ignores all :pep:`526`-compliant annotated class variable assignment statements by *not* implicitly appending these statements with calls to the statement-level :func:`beartype.door.die_if_unbearable` exception-raiser. ''' pass # ....................{ CALLABLES }.................... def beneath_the_cold_glare() -> None: ''' Arbitrary callable whose body contains one or more :pep:`526`-compliant annotated variable assignments, exercising edge cases in :mod:`beartype.claw` import hooks handling these assignments. ''' # Arbitrary object violating type hints below. OF_THE_DESOLATE_NIGHT = len('Beneath the cold glare of the desolate night,') # Assert that a PEP 526-compliant annotated assignment statement assigning # an object violating the type hint annotating that statement raises the # expected exception. with raises(BeartypeDoorHintViolation) as exception_info: through_tangled_swamps: Union[float, List[str]] = OF_THE_DESOLATE_NIGHT # Exception message raised by that assignment. local_exception_message = str(exception_info.value) # Truncated representation of the invalid object assigned by that assignment. local_pith_repr = represent_object(OF_THE_DESOLATE_NIGHT) # Assert that this message contains a truncated representation of this pith. assert local_pith_repr in local_exception_message # Assert that this raiser successfully replaced the temporary # placeholder previously prefixing this message. assert 'callable ' in local_exception_message.lower() assert __name__ in local_exception_message assert 'beneath_the_cold_glare() ' in local_exception_message assert ' violates type hint ' in local_exception_message data_claw_pep526_warn.py000066400000000000000000000035641461113517100357020ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/hookable_package/pep/pep526#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **beartype import hookable** :pep:`526` **warning submodule** (i.e., data module containing *only* :pep:`526`-compliant annotated variable assignments emitting :exc:`beartype.roar.BeartypeValeLambdaWarning` violations, mimicking real-world usage of the :func:`beartype.claw.beartype_package` import hook from an external caller). ''' # ....................{ IMPORTS }.................... from beartype.roar import BeartypeValeLambdaWarning from beartype.typing import ( List, Union, ) from pytest import warns # from beartype.claw._clawstate import claw_state # print(f'this_submodule conf: {repr(claw_state.module_name_to_beartype_conf)}') # ....................{ PEP 526 }.................... # Validate that the import hook presumably installed by the caller implicitly # appends all PEP 526-compliant annotated assignment statements in this # submodule with calls to beartype's statement-level # beartype.door.die_if_unbearable() warning-emitter. # Assert that a PEP 526-compliant annotated assignment statement assigning an # object satisfying the type hint annotating that statement emits *NO* warning. as_oceans_moon: int = len("As ocean's moon looks on the moon in heaven.") # Assert that a PEP 526-compliant annotated statement lacking an assignment # emits *NO* warning. looks_on_the_moon: str # Assert that a PEP 526-compliant annotated assignment statement assigning an # object violating the type hint annotating that statement emits the expected # warning. with warns(BeartypeValeLambdaWarning): in_heaven: Union[float, List[str]] = len( 'The spirit of sweet human love has sent') # print('!!!!!!YAY!!!!!!!!!') unhookable_submodule.py000066400000000000000000000040411461113517100342230ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/hookable_package#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **current package beartype import hook unhooked submodule** (i.e., data module *not* hooked by any import hooks published by the :mod:`beartype.claw` subpackage and thus expected to be governed by standard Python type-checking semantics -- which is to say, *no* type-checking at all). ''' # ....................{ IMPORTS }.................... from beartype.typing import ( List, Union, ) # from beartype.claw._importlib.clawimpcache import module_name_to_beartype_conf # print(f'this_submodule conf: {repr(module_name_to_beartype_conf)}') # ....................{ PEP 526 }.................... # Validate that *NO* import hooks installed by the caller apply to this # submodule. In this case, assert that PEP 526-compliant annotated assignment # statements are *NOT* appended with calls to beartype's statement-level # beartype.door.die_if_unbearable() exception-raiser. # Assert that a PEP 526-compliant assignment statement assigning an object # violating the type hint annotating that statement raises *NO* exception. and_winter_robing: str = b'And winter robing with pure snow and crowns' assert isinstance(and_winter_robing, bytes) # ....................{ FUNCTIONS }.................... def of_starry_ice(the_grey_grass_and_bare_boughs: Union[str, complex]) -> ( Union[complex, List[bytes]]): ''' Arbitrary method neither implicitly *nor* explicitly type-checked by the :func:`beartype.beartype` decorator. ''' # This means nothing to us. Nothing! return the_grey_grass_and_bare_boughs # Assert that a function call passed a parameter violating the type hint # annotating that parameter raises *NO* exception. voluptuous_pantings = of_starry_ice( b"If spring's voluptuous pantings when she breathes") assert isinstance(voluptuous_pantings, bytes) beartype-0.18.5/beartype_test/a00_unit/data/claw/intraprocess/unhookable_module.py000066400000000000000000000040411461113517100303310ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **current package beartype import hook unhooked submodule** (i.e., data module *not* hooked by any import hooks published by the :mod:`beartype.claw` subpackage and thus expected to be governed by standard Python type-checking semantics -- which is to say, *no* type-checking at all). ''' # ....................{ IMPORTS }.................... from beartype.typing import ( List, Union, ) # from beartype.claw._importlib.clawimpcache import module_name_to_beartype_conf # print(f'this_submodule conf: {repr(module_name_to_beartype_conf)}') # ....................{ PEP 526 }.................... # Validate that *NO* import hooks installed by the caller apply to this # submodule. In this case, assert that PEP 526-compliant annotated assignment # statements are *NOT* appended with calls to beartype's statement-level # beartype.door.die_if_unbearable() exception-raiser. # Assert that a PEP 526-compliant assignment statement assigning an object # violating the type hint annotating that statement raises *NO* exception. and_winter_robing: str = b'And winter robing with pure snow and crowns' assert isinstance(and_winter_robing, bytes) # ....................{ FUNCTIONS }.................... def of_starry_ice(the_grey_grass_and_bare_boughs: Union[str, complex]) -> ( Union[complex, List[bytes]]): ''' Arbitrary method neither implicitly *nor* explicitly type-checked by the :func:`beartype.beartype` decorator. ''' # This means nothing to us. Nothing! return the_grey_grass_and_bare_boughs # Assert that a function call passed a parameter violating the type hint # annotating that parameter raises *NO* exception. voluptuous_pantings = of_starry_ice( b"If spring's voluptuous pantings when she breathes") assert isinstance(voluptuous_pantings, bytes) beartype-0.18.5/beartype_test/a00_unit/data/data_type.py000066400000000000000000000555151461113517100231610ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **generic types data** submodule. This submodule predefines low-level class constants exercising known edge cases on behalf of higher-level unit test submodules. ''' # ....................{ IMPORTS }.................... from beartype.typing import ( AsyncGenerator, Callable, Coroutine, Generator, Iterator, ) from beartype._util.func.utilfuncmake import make_func from collections import defaultdict from contextlib import contextmanager from enum import Enum from functools import ( lru_cache, partial, wraps, ) from sys import exc_info # ....................{ CLASSES }.................... class CallableClass(object): ''' Arbitrary pure-Python **callable class** (i.e., class defining the :meth:`__call__` dunder method implicitly called by Python when instances of this class are called using the standard calling convention). ''' def __call__(self, *args, **kwargs) -> int: ''' Dunder method implicitly called when this object is called. ''' return len(args) + len(kwargs) # ....................{ CLASSES ~ enum }.................... class MasterlessDecreeVenomlessWhich(Enum): ''' Arbitrary enumeration whose members are accessed elsewhere as integers, typically when subscripting the :pep:`586`-compliant :attr:`typing.Literal` type hint factory. ''' NOMENCLATURE_WEATHER_VANES_OF = 0 NOMINALLY_UNSWAIN_AUTODIDACTIC_IDIOCRACY_LESS_A = 1 # ....................{ CLASSES ~ hierarchy : 1 }.................... # Arbitrary class hierarchy. class Class(object): ''' Arbitrary pure-Python class defining arbitrary methods. ''' # ....................{ NESTED }.................... class NestedClass(object): ''' Arbitrary pure-Python **nested class** whose definition is nested inside the definition of another class. ''' pass # ....................{ METHODS }.................... def instance_method(self): ''' Arbitrary pure-Python instance method. ''' pass @property def instance_property(self): ''' Arbitrary pure-Python instance property. ''' pass @classmethod def class_method(cls): ''' Arbitrary pure-Python class method. ''' pass @staticmethod def static_method(): ''' Arbitrary pure-Python static method. ''' pass class Subclass(Class): ''' Arbitrary pure-Python subclass of an arbitrary pure-Python superclass. ''' pass class SubclassSubclass(Subclass): ''' Arbitrary pure-Python subclass of an arbitrary pure-Python subclass of an arbitrary pure-Python superclass. ''' pass # ....................{ CLASSES ~ hierarchy : 2 }.................... # Yet another arbitrary class hierarchy. class OtherClass(object): ''' Arbitrary pure-Python class defining an arbitrary method. ''' def instance_method(self): ''' Arbitrary pure-Python instance method. ''' pass class OtherSubclass(OtherClass): ''' Arbitrary pure-Python subclass of an arbitrary pure-Python superclass. ''' pass class OtherSubclassSubclass(OtherSubclass): ''' Arbitrary pure-Python subclass of an arbitrary pure-Python subclass of an arbitrary pure-Python superclass. ''' pass # ....................{ CLASSES ~ isinstance }.................... class NonIsinstanceableMetaclass(type): ''' Metaclass overriding the ``__instancecheck__()`` dunder method to unconditionally raise an exception, preventing classes with this metaclass from being passed as the second parameter to the :func:`isinstance` builtin. ''' def __instancecheck__(self, obj: object) -> bool: raise TypeError( f'{self} not passable as second parameter to isinstance().') class NonIsinstanceableClass(object, metaclass=NonIsinstanceableMetaclass): ''' Class whose metaclass overrides the ``__instancecheck__()`` dunder method to unconditionally raise an exception, preventing this class from being passed as the second parameter to the :func:`isinstance` builtin. ''' pass # ....................{ CLASSES ~ issubclass }.................... class NonIssubclassableMetaclass(type): ''' Metaclass overriding the ``__subclasscheck__()`` dunder method to unconditionally raise an exception, preventing classes with this metaclass from being passed as the second parameter to the :func:`issubclass` builtin. ''' def __subclasscheck__(self, obj: object) -> bool: raise TypeError( f'{self} not passable as second parameter to issubclass().') class NonIssubclassableClass(object, metaclass=NonIssubclassableMetaclass): ''' Class whose metaclass overrides the ``__subclasscheck__()`` dunder method to unconditionally raise an exception, preventing this class from being passed as the second parameter to the :func:`issubclass` builtin. ''' pass # ....................{ CLASSES ~ with : module name }.................... class ClassModuleNameFake(object): ''' Arbitrary pure-Python class with a **non-existent module name** (i.e., whose ``__module__`` dunder attribute refers to a file that is guaranteed to *not* exist on the local filesystem). ''' pass class ClassModuleNameNone(object): ''' Arbitrary pure-Python class with a **missing module name** (i.e., whose ``__module__`` dunder attribute is :data:`None`). ''' pass # Monkey-patch the above classes with "bad" module names. ClassModuleNameFake.__module__ = 'If_I.were.a_dead_leaf.thou_mightest.bear' ClassModuleNameNone.__module__ = None # ....................{ CALLABLES ~ async : factory }.................... # Note that we intentionally avoid declaring a factory function for deprecated # generator-based coroutines decorated by either the types.coroutine() or # asyncio.coroutine() decorators. CPython 3.10 removes support for these # decorators and thus generator-based coroutines. See also: # https://docs.python.org/3/library/asyncio-task.html#asyncio.coroutine async def async_coroutine_factory(text: str) -> Coroutine[None, None, str]: ''' Arbitrary pure-Python asynchronous non-generator coroutine factory function. ''' # Defer function-specific imports. from asyncio import sleep # Asynchronously switch to another scheduled asynchronous callable (if any). await sleep(0) # Return an arbitrary string. return f'{text}Yet not a city, but a flood of ruin' async def async_generator_factory(text: str) -> AsyncGenerator[str, None]: ''' Arbitrary pure-Python asynchronous generator factory function. ''' # Defer function-specific imports. from asyncio import sleep # Asynchronously switch to another scheduled asynchronous callable (if any). await sleep(0) # Yield an arbitrary string. yield f'{text}Rolls its perpetual stream; vast pines are strewing' # ....................{ CALLABLES ~ async : instance }.................... async_generator = async_generator_factory( "Its destin'd path, or in the mangled soil") ''' Arbitrary pure-Python asynchronous generator. ''' async_coroutine = async_coroutine_factory( "Branchless and shatter'd stand; the rocks, drawn down") ''' Arbitrary pure-Python asynchronous non-generator coroutine. ''' # Prevent Python from emitting "ResourceWarning" warnings. async_coroutine.close() # ....................{ CALLABLES ~ sync }.................... def function(): ''' Arbitrary pure-Python function physically declared by this submodule. ''' pass function_lambda = lambda: 'The day was fair and sunny: sea and sky' ''' Arbitrary pure-Python lambda function physically declared by this submodule. ''' function_in_memory = make_func( func_name='function_in_memory', func_code=''' def function_in_memory(): return 'And tremble and despoil themselves: oh hear!' ''', func_doc=''' Arbitrary pure-Python function dynamically declared in-memory. ''') @wraps(function) def wrapper_isomorphic(*args, **kwargs): ''' **Isomorphic wrapper** (i.e., higher-level function transparently preserving both the positions and types of all parameters and returns passed to the lowel-level wrappee callable). ''' return function(*args, **kwargs) # ....................{ CALLABLES ~ sync : decorator }.................... def decorator(func: Callable) -> Callable: ''' **Identity decorator** (i.e., decorator returning the passed callable unmodified). This decorator enables logic elsewhere to exercise the :mod:`beartype.beartype` decorator with respect to nested callables decorated by both the :mod:`beartype.beartype` decorator and one or more decorators *not* the :mod:`beartype.beartype` decorator. ''' return func def decorator_isomorphic(func): ''' **Isomorphic decorator** (i.e., function returning an isomorphic decorator closure transparently preserving both the positions and types of all parameters passed to the passed callable). ''' @wraps(func) def _closure_isomorphic(*args, **kwargs): ''' **Isomorphic decorator closure** (i.e., closure transparently preserving both the positions and types of all parameters and returns passed to the decorated callable). ''' return func(*args, **kwargs) # Return this closure. return _closure_isomorphic def decorator_nonisomorphic(func): ''' **Non-isomorphic decorator** (i.e., function returning a non-isomorphic decorator closure destroying the positions and/or types of one or more parameters passed to the passed callable). ''' # Defer decorator-specific imports. from functools import wraps @wraps(func) def _closure_nonisomorphic(*args): ''' **Non-isomorphic decorator closure** (i.e., closure destroying the positions and/or types of one or more parameters passed to the decorated callable). This closure fails to accept keyword parameters and thus effectively "destroys" all keyword parameters passed to the decorated callable. ''' return func(*args) # Return this closure. return _closure_nonisomorphic # ....................{ CALLABLES ~ sync : generator }.................... def sync_generator_factory() -> Generator[int, None, None]: ''' Create and return a pure-Python generator yielding a single integer, accepting nothing, and returning nothing. ''' yield 1 def sync_generator_factory_yield_int_send_float_return_str() -> ( Generator[int, float, str]): ''' Create and return a pure-Python generator yielding integers, accepting floating-point numbers sent to this generator by the caller, and returning strings. See Also ---------- https://www.python.org/dev/peps/pep-0484/#id39 ``echo_round`` function strongly inspiring this implementation, copied verbatim from this subsection of :pep:`484`. ''' # Initial value externally sent to this generator. res = yield while res: res = yield round(res) # Return a string constant. return 'Unmarred, scarred revenant remnants' # ....................{ CALLABLES ~ sync : closure }.................... def closure_factory(): ''' Arbitrary pure-Python closure factory function. ''' # Arbitrary non-local variable. outer_variable = 42 def closure(): ''' Arbitrary pure-Python closure. ''' nonlocal outer_variable # Return this closure. return closure def closure_cell_factory(): ''' Arbitrary pure-Python closure cell factory function. ''' # Arbitrary non-local variable. outer_variable = 1337 def closure(): ''' Arbitrary pure-Python closure. ''' nonlocal outer_variable # Return this closure's first and only cell variable. return closure.__closure__[0] # ....................{ CALLABLES ~ sync : instance }.................... sync_generator = sync_generator_factory() ''' Arbitrary pure-Python synchronous generator. ''' # ....................{ CALLABLES ~ sync : mod : contextlib}.................... @contextmanager def context_manager_factory(obj: object) -> Iterator[object]: ''' **Context manager factory** (i.e., function creating and returning a new **identity context manager** (i.e., context manager trivially yielding the passed object implemented as a generator factory function decorated by the standard :func:`contextlib.contextmanager` decorator)). ''' yield obj # ....................{ CALLABLES ~ sync : mod : functools }.................... @lru_cache def function_lru_cached(n: int) -> int: ''' Arbitrary :func:`functools.lru_cache`-memoized function. ''' return n + 1 def function_partialized( and_one_majestic_river: str, that_mighty_shadow_loves: str, ) -> str: ''' Arbitrary pure-Python function to be wrapped by the :class:`.partial` factory as the :data:`.function_partial` object, accepting both positional and keyword parameters to exercise all possible edge cases. ''' return and_one_majestic_river + that_mighty_shadow_loves FUNCTION_PARTIALIZED_ARG_VALUE = ( 'The breath and blood of distant lands, for ever') ''' Arbitrary value passed as the first and only positional parameter to the :func:`function_partialized` function by the :data:`.function_partial` object. ''' FUNCTION_PARTIALIZED_KWARG_NAME = 'that_mighty_shadow_loves' ''' Name of the first and only keyword parameter passed to the :func:`function_partialized` function by the :data:`.function_partial` object. ''' FUNCTION_PARTIALIZED_KWARG_VALUE = 'The slimy caverns of the populous deep.' ''' Arbitrary value passed as the first and only keyword parameter to the :func:`function_partialized` function by the :data:`.function_partial` object. ''' builtin_partial = partial(divmod, 2) ''' Arbitrary instance of the :class:`.partial` factory wrapping the C-based :func:`divmod` builtin. ''' function_partial = partial( function_partialized, FUNCTION_PARTIALIZED_ARG_VALUE, that_mighty_shadow_loves=FUNCTION_PARTIALIZED_KWARG_VALUE, ) ''' Arbitrary instance of the :class:`.partial` factory wrapping the pure-Python :func:`function_partialized` function. ''' function_partial_bad = partial( function_partialized, FUNCTION_PARTIALIZED_ARG_VALUE, that_mighty_shadow_loves=FUNCTION_PARTIALIZED_KWARG_VALUE, the_day_was_fair_and_sunny='sea and sky', ) ''' Arbitrary instance of the :class:`.partial` factory wrapping the pure-Python :func:`function_partialized` function in an invalid manner by passing more parameters than that function actually accepts. ''' # ....................{ CALLABLES ~ sync : module }.................... def function_module_name_fake() -> None: ''' Arbitrary pure-Python class with a **non-existent module name** (i.e., whose ``__module__`` dunder attribute refers to a file that is guaranteed to *not* exist on the local filesystem). ''' pass def function_module_name_none() -> None: ''' Arbitrary pure-Python function with a **missing module name** (i.e., whose ``__module__`` dunder attribute is :data:`None`). ''' pass # Monkey-patch the above callables with "bad" module names. function_module_name_fake.__module__ = 'He_had.a_mask.like_Castlereagh' function_module_name_none.__module__ = None # ....................{ CONSTANTS }.................... CALLABLE_CODE_OBJECT = function.__code__ ''' Arbitrary callable code object. ''' # Initialized below. TRACEBACK = None ''' Arbitrary traceback object. ''' # Define the "TRACEBACK" constant via EAFP. try: raise TypeError except TypeError: TRACEBACK = exc_info()[2] # ....................{ CONSTANTS ~ filenames }.................... MODULE_FILENAME = __file__ ''' Absolute filename of the current submodule, declared purely for convenience. ''' # ....................{ DICTS }.................... def _default_dict_str_factory() -> str: ''' Arbitrary factory function returning arbitrary strings. ''' return 'His steps to the sea-shore. A swan was there,' default_dict_int_to_str = defaultdict(_default_dict_str_factory) ''' Non-empty default dictionary mapping integers to strings, initialized with an arbitrary factory function returning arbitrary strings. ''' default_dict_int_to_str[0] = 'Beside a sluggish stream among the reeds.' default_dict_int_to_str[1] # == 'His steps to the sea-shore. A swan was there,' default_dict_str_to_str = defaultdict(_default_dict_str_factory) ''' Non-empty default dictionary mapping strings to strings, initialized with an arbitrary factory function returning arbitrary strings. ''' default_dict_str_to_str['His eyes pursued its flight.'] = ( 'Thou hast a home,') # ....................{ SETS ~ callable }.................... CALLABLES_PYTHON = frozenset(( function, Class, Class.instance_method, Class.class_method, Class.static_method, )) ''' Frozen set of pure-Python callables exercising edge cases. ''' CALLABLES_C = frozenset(( len, # Built-in FunctionType ().count, # Built-in Method Type object.__init__, # Wrapper Descriptor Type object().__str__, # Method Wrapper Type str.join, # Method Descriptor Type #FIXME: *UGH.* This probably should be callable under PyPy 3.6, but #it's not, which is why we've currently disabled this. That's clearly a #PyPy bug. Uncomment this *AFTER* we drop support for PyPy 3.6 (and any #newer PyPy versions also failing to implement this properly). We #should probably also consider filing an upstream issue with PyPy, #because this is non-ideal and non-orthogonal behaviour with CPython. #dict.__dict__['fromkeys'], )) ''' Frozen set of C-based callables exercising edge cases. ''' CALLABLES = CALLABLES_PYTHON | CALLABLES_C ''' Frozen set of both pure-Python *and* C-based callables exercising edge cases. ''' #FIXME: Currently unused but preserved for posterity. # NON_CALLABLES = ( # CALLABLE_CODE_OBJECT, # type.__dict__, # Mapping Proxy Type # implementation, # Simple Namespace Type # async_coroutine, # async_generator, # sync_generator, # closure_cell_factory(), # Cell type # TRACEBACK, # TRACEBACK.tb_frame, # ) # ''' # Tuple of callable-like non-callable objects exercising edge cases, intentionally # defined as a tuple rather than frozen set due to the unhashability of one or # more members (e.g., ``TRACEBACK``). # ''' # ....................{ SETS ~ type : builtin }.................... TYPES_BUILTIN = frozenset(( bool, complex, dict, float, frozenset, int, list, set, str, tuple, )) ''' Frozen set of all **builtin types** i.e., globally accessible C-based type requiring *no* explicit importation)(. ''' # ....................{ SETS ~ type : non-builtin }.................... # Fully initialized below by the _init() function. TYPES_BUILTIN_FAKE = None ''' Frozen set of all **fake builtin types** (i.e., types that are *not* builtin but which nonetheless erroneously masquerade as being builtin). See Also ---------- :data:`beartype._data.cls.datacls.TYPES_BUILTIN_FAKE` Related runtime set. Whereas that runtime-specific set is efficiently defined explicitly by listing all non-builtin builtin mimic types, this test-specific set is inefficiently defined implicitly by introspecting the :mod:`builtins` module. While less efficient, this test-specific set serves as an essential sanity check on that runtime-specific set. ''' # Fully initialized below by the _init() function. TYPES_NONBUILTIN = None ''' Frozen set of arbitrary non-builtin types. ''' # ....................{ INITIALIZERS }.................... def _init() -> None: ''' Initialize this submodule. ''' # Defer initialization-specific imports. import builtins, types from beartype._data.module.datamodpy import BUILTINS_MODULE_NAME from beartype._util.utilobject import get_object_type_unless_type # Global variables assigned to below. global TYPES_BUILTIN_FAKE, TYPES_NONBUILTIN # Set of all fake builtin types. TYPES_BUILTIN_FAKE_SET = set() # Frozen set of all standard modules well-known to define at least one fake # builtin type. # # Note that, although the "builtins" module defines *MOST* fake builtins, # the "types" module defines *SOME* fake builtins as well. These include: # * "types.TracebackType". MODULES_TYPES_BUILTIN_FAKE = frozenset((builtins, types)) # For each such module... for module_type_builtin_fake in MODULES_TYPES_BUILTIN_FAKE: # For each builtin (i.e., globally accessible object implicitly imported # by the active Python interpreter into *EVERY* lexical context)... for builtin_name, builtin in module_type_builtin_fake.__dict__.items(): # Either: # * If this builtin is already a class, this builtin as is. # * If this builtin is *NOT* already a class, this builtin's class. builtin_type = get_object_type_unless_type(builtin) # If... if ( # This builtin type insists itself to be defined by the # "builtins" module and thus be a builtin *AND*... builtin_type.__module__ == BUILTINS_MODULE_NAME and # The "builtins" module contains *NO* globally-scoped attribute # whose name is that of this type... builtin_type.__name__ not in builtins.__dict__ # Add this cheatin', lyin', no-good fake builtin type to this set. ): # if builtin_type.__name__ == 'PyCapsule': # print(f'Auto-detected fake PyCapsule {repr(builtin_name)}...') # continue # print(f'Auto-detected fake builtin type {repr(builtin_type)}...') TYPES_BUILTIN_FAKE_SET.add(builtin_type) # Frozen set of all fake builtin types. TYPES_BUILTIN_FAKE = frozenset((TYPES_BUILTIN_FAKE_SET)) # Frozen set of arbitrary non-builtin types. TYPES_NONBUILTIN = frozenset(( # Arbitrary non-builtin type. Class, )) | TYPES_BUILTIN_FAKE # print(f'Auto-detected non-builtin types: {repr(TYPES_NONBUILTIN)}') # Initialize this submodule. _init() beartype-0.18.5/beartype_test/a00_unit/data/func/000077500000000000000000000000001461113517100215555ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/func/__init__.py000066400000000000000000000000001461113517100236540ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/func/data_func.py000066400000000000000000000102631461113517100240550ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **sample callable** submodule. This submodule predefines sample pure-Python callables exercising known edge cases on behalf of higher-level unit test submodules. ''' # ....................{ IMPORTS }.................... from beartype_test.a00_unit.data.data_type import decorator_isomorphic from typing import Union # ....................{ CALLABLES }.................... def func_args_0() -> str: ''' Arbitrary callable accepting *no* parameters. ''' return 'And so there grew great tracts of wilderness,' def func_args_1_flex_mandatory(had_one_fair_daughter: str) -> str: ''' Arbitrary callable accepting one mandatory flexible parameter. ''' return 'But man was less and less, till Arthur came.' def func_args_1_varpos(*and_in_her_his_one_delight: str) -> str: ''' Arbitrary callable accepting one variadic positional parameter. ''' return 'Wherein the beast was ever more and more,' def func_args_2_flex_mandatory( thick_with_wet_woods: str, and_many_a_beast_therein: str) -> str: ''' Arbitrary callable accepting two or more mandatory flexible parameters. ''' return 'For here between the man and beast we die.' def func_args_3_flex_mandatory_optional_varkw( and_the_wolf_tracks_her_there: str, how_hideously: str = "Its shapes are heap'd around!", **rude_bare_and_high ) -> str: ''' Arbitrary callable accepting one mandatory flexible parameter, one optional flexible parameter, and one variadic keyword parameter. This test exercises a recent failure in our pre-0.10.0 release cycle: https://github.com/beartype/beartype/issues/78 ''' return "Ghastly, and scarr'd, and riven.—Is this the scene" # ....................{ CALLABLES ~ pep 3102 }.................... # Keyword-only keywords require PEP 3102 compliance, which has thankfully been # available since Python >= 3.0. def func_args_1_kwonly_mandatory( *, when_can_I_take_you_from_this_place: str) -> str: ''' Arbitrary callable accepting one mandatory keyword-only parameter. ''' return 'When is the word but a sigh?' def func_args_2_kwonly_mixed( *, white_summer: Union[dict, str] = 'So far I have gone to see you again.', hiding_your_face_in_the_palm_of_your_hands: Union[set, str], ) -> Union[tuple, str]: ''' Arbitrary callable passed one optional keyword-only parameter and one mandatory keyword-only parameter (in that non-standard and quite counter-intuitive order), each annotated with PEP-compliant type hints. ''' return white_summer + '\n' + hiding_your_face_in_the_palm_of_your_hands def func_args_5_flex_mandatory_varpos_kwonly_varkw( we_are_selfish_men, oh_raise_us_up, *and_give_us, return_to_us_again='Of inward happiness.', **manners_virtue_freedom_power, ) -> str: ''' Arbitrary callable accepting two mandatory flexible parameters, one variadic positional parameter, one optional keyword-only parameter (defined implicitly), and one variadic keyword parameter. ''' # Arbitrary local variable declared in the body of this callable. thy_soul_was_like_a_star = 'and dwelt apart:' return thy_soul_was_like_a_star # ....................{ CALLABLES ~ wrapped }.................... # Callables wrapped by a decorator wrapper reducing their signatures to the # standard parameter-passing idiom for decorators trivially implemented as # closures (i.e., *NOT* @beartype-style decorators): # def wrapper(*args, **kwargs): ... func_args_0_wrapped = decorator_isomorphic( func_args_0) func_args_1_flex_mandatory_wrapped = decorator_isomorphic( func_args_1_flex_mandatory) func_args_1_varpos_wrapped = decorator_isomorphic( func_args_1_varpos) func_args_2_flex_mandatory_wrapped = decorator_isomorphic( func_args_2_flex_mandatory) func_args_3_flex_mandatory_optional_varkw_wrapped = decorator_isomorphic( func_args_3_flex_mandatory_optional_varkw) beartype-0.18.5/beartype_test/a00_unit/data/func/data_pep570.py000066400000000000000000000076151461113517100241510ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`570` **data submodule.** This submodule exercises :pep:`570` support implemented in the :func:`beartype.beartype` decorator by declaring callables accepting one or more **positional-only parameters** (i.e., parameters that *must* be passed positionally, syntactically followed in the signatures of their callables by the :pep:`570`-compliant ``/,`` pseudo-parameter). Caveats ---------- **This submodule requires the active Python interpreter to target Python >= 3.8.** If this is *not* the case, importing this submodule raises an :class:`SyntaxError` exception. In particular, this submodule *must* not be imported from module scope. If this submodule is imported from module scope *and* the active Python interpreter targets Python < 3.8, :mod:`pytest` raises non-human-readable exceptions at test collection time resembling: /usr/lib64/python3.6/site-packages/_pytest/python.py:578: in _importtestmodule mod = import_path(self.fspath, mode=importmode) /usr/lib64/python3.6/site-packages/_pytest/pathlib.py:531: in import_path importlib.import_module(module_name) /usr/lib64/python3.6/importlib/__init__.py:126: in import_module return _bootstrap._gcd_import(name[level:], package, level) :994: in _gcd_import ??? :971: in _find_and_load ??? :955: in _find_and_load_unlocked ??? :665: in _load_unlocked ??? /usr/lib64/python3.6/site-packages/_pytest/assertion/rewrite.py:161: in exec_module source_stat, co = _rewrite_test(fn, self.config) /usr/lib64/python3.6/site-packages/_pytest/assertion/rewrite.py:354: in _rewrite_test tree = ast.parse(source, filename=fn_) /usr/lib64/python3.6/ast.py:35: in parse return compile(source, filename, mode, PyCF_ONLY_AST) E File "/home/leycec/py/beartype/beartype_test/a00_unit/a20_util/func/test_utilfuncarg.py", line 237 E /, E ^ E SyntaxError: invalid syntax ''' # ....................{ IMPORTS }.................... from typing import Union # ....................{ CALLABLES }.................... def func_args_2_posonly_mixed( before_spreading_his_black_wings: Union[bytearray, str], reaching_for_the_skies: Union[bool, str] = 'in this forest', /, ) -> Union[list, str]: ''' Arbitrary :pep:`570`-compliant callable passed a mandatory and optional positional-only parameter, all annotated with PEP-compliant type hints. ''' return ( before_spreading_his_black_wings + '\n' + reaching_for_the_skies) def func_args_10_all_except_flex_mandatory( in_solitude_i_wander, through_the_vast_enchanted_forest, the_surrounding_skies='are one', /, torn_apart_by='the phenomenon of lightning', rain_is_pouring_down='my now shivering shoulders', *in_the_rain_my_tears_are_forever_lost, the_darkened_oaks_are_my_only_shelter, red_leaves_are_blown_by='the wind', an_ebony_raven_now_catches='my eye.', **sitting_in_calmness, ) -> str: ''' Arbitrary :pep:`570`-compliant callable accepting all possible kinds of parameters, including both mandatory and optional variants of these kinds except mandatory flexible parameters. Since callables cannot by definition accept both optional positional-only parameters *and* mandatory flexible parameters, this callable necessarily omits the latter in favour of the former. ''' # Arbitrary local variable declared in the body of this callable. before_spreading_his_black_wings = 'Reaching for the skies.' return before_spreading_his_black_wings beartype-0.18.5/beartype_test/a00_unit/data/hint/000077500000000000000000000000001461113517100215645ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/hint/__init__.py000066400000000000000000000000001461113517100236630ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/hint/data_hint.py000066400000000000000000000201731461113517100240740ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype type hints data-driven testing submodule.** This submodule predefines low-level global constants exercising known edge cases on behalf of higher-level unit test submodules -- including PEP-compliant type hints, PEP-noncompliant type hint, and objects satisfying neither. ''' # ....................{ IMPORTS }.................... from beartype._cave._cavefast import ( AnyType, NoneType, ) from beartype._cave._cavemap import NoneTypeOr from pytest import fixture # ....................{ FIXTURES }.................... @fixture(scope='session') def hints_meta(hints_pep_meta, hints_nonpep_meta) -> ( 'Tuple[HintNonpepMetadata]'): ''' Session-scoped fixture yielding a tuple of **PEP-agnostic type hint metadata** (i.e., :class:`beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintNonpepMetadata` instances, each describing a sample type hint exercising an edge case in the :mod:`beartype` codebase -- including both PEP-compliant and -noncompliant type hints). Parameters ---------- hints_pep_meta : Tuple[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] Tuple of PEP-compliant type hint metadata describing PEP-compliant type hints exercising edge cases in the :mod:`beartype` codebase. hints_nonpep_meta : Tuple[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintNonpepMetadata] Tuple of PEP-noncompliant type hint metadata describing PEP-noncompliant type hints exercising edge cases in the :mod:`beartype` codebase. ''' # One world. One-liner. Let's get together and code alright. yield hints_pep_meta + hints_nonpep_meta @fixture(scope='session') def hints_ignorable( hints_pep_ignorable_shallow, hints_pep_ignorable_deep, ) -> frozenset: ''' Session-scoped fixture yielding a frozen set of **ignorable PEP-agnostic type hints,** including both PEP-compliant *and* -noncompliant and shallowly *and* deeply ignorable type hints. Parameters ---------- hints_pep_ignorable_shallow : frozenset Frozen set of all shallowly ignorable PEP-compliant type hints. hints_pep_ignorable_deep : frozenset Frozen set of all deeply ignorable PEP-compliant type hints. ''' # I code the one-liner, but I did not code the two-liner. yield ( hints_pep_ignorable_shallow | hints_pep_ignorable_deep ) # ....................{ FIXTURES ~ not : sets }.................... @fixture(scope='session') def not_hints_nonpep(hints_pep_hashable) -> frozenset: ''' Session-scoped fixture yielding a frozen set of various objects that are *not* PEP-noncompliant type hints exercising well-known edge cases. Parameters ---------- hints_pep_hashable : frozenset Tuple of PEP-compliant type hint metadata describing PEP-compliant type hints exercising edge cases in the :mod:`beartype` codebase. ''' yield frozenset(( # Set comprehension of tuples containing PEP-compliant type hints. Although # tuples containing PEP-noncompliant type hints are themselves valid # PEP-noncompliant type hints supported by @beartype, tuples containing # PEP-compliant type hints are invalid and thus unsupported. { # Tuple containing this PEP-compliant type hint... (int, hint_pep_hashable, NoneType,) # For each hashable PEP-compliant type hint... for hint_pep_hashable in hints_pep_hashable # That is neither: # * An isinstanceable class. # * A string-based forward reference. # # Both are unique edge cases supported as both PEP 484-compliant outside # tuples *AND* beartype-specific inside tuples. Including these hints # here would erroneously cause tests to treat tuples containing these # hints as *NOT* tuple type hints. if not isinstance(hint_pep_hashable, (str, type)) } | # Set comprehension of hashable PEP-compliant non-class type hints. hints_pep_hashable | # Hashable objects invalid as type hints (e.g., scalars). NOT_HINTS_HASHABLE )) # ....................{ NON-HINTS ~ sets }.................... #FIXME: Refactor *ALL* remaining globals below into comparable fixtures, please. NOT_HINTS_HASHABLE = frozenset(( # Scalar that is neither a type nor string (i.e., forward reference). 0.12345678910111213141516, # Empty tuple. (), # Tuple containing a scalar that is neither a type nor string. (list, 'list', 0xFEEDFACE, NoneType,), )) ''' Frozen set of various objects that are hashable but nonetheless unsupported by the :func:`beartype.beartype` decorator as valid type hints. ''' # ....................{ NON-HINTS ~ tuples }.................... NOT_HINTS_UNHASHABLE = ( # Dictionary. {'For all things turn to barrenness': 'In the dim glass the demons hold,',}, # List. ['The glass of outer weariness,', 'Made when God slept in times of old.',], # Set. {'There, through the broken branches, go', 'The ravens of unresting thought;',}, ) ''' Tuple of various objects that are unhashable and thus unsupported by the :func:`beartype.beartype` decorator as valid type hints. Since sets *cannot* by design contain unhashable objects, this container is defined as a tuple rather than a set. ''' NOT_HINTS = tuple(NOT_HINTS_HASHABLE) + NOT_HINTS_UNHASHABLE ''' Tuple of various objects unsupported by the :func:`beartype.beartype` decorator as valid type hints, including both hashable and unhashable unsupported objects. Since sets *cannot* by design contain unhashable objects, this container is defined as a tuple rather than a set. ''' # ....................{ NON-PEP ~ classes }.................... class NonpepCustom(str): ''' PEP-noncompliant user-defined class subclassing an arbitrary superclass. ''' pass class NonpepCustomFakeTyping(object): ''' PEP-noncompliant user-defined class subclassing an arbitrary superclass erroneously masquerading as a :mod:`typing` class. Specifically, this class: * Defines the :meth:`__repr__` dunder method to return a string prefixed by the substring ``"typing."``. * Defines the :attr:`__module__` dunder attribute to be the fully-qualified module name ``"typing"``. ''' def __repr__(self) -> str: return 'typing.FakeTypingType' NonpepCustomFakeTyping.__module__ = 'typing' # ....................{ NON-PEP ~ sets }.................... # Note that we intentionally omit the "NonpepCustomFakeTyping" class here, as # that class masquerades too well as a "typing" class -- so well, in fact, that # "beartype._util.hint.pep.utilpeptest" functions are incapable of reasonably # distinguishing instances of that class from actual "typing" type hints. HINTS_NONPEP = frozenset(( # Builtin container type. list, # Builtin scalar type. str, # User-defined type. NonpepCustom, # Non-empty tuple containing two types. NoneTypeOr[AnyType], # Non-empty tuple containing two types and a fully-qualified forward # reference. (int, 'beartype._cave.._cavefast.NoneType', set) )) ''' Frozen set of PEP-noncompliant type hints exercising well-known edge cases. ''' HINTS_NONPEP_UNIGNORABLE = ( # PEP-noncompliant type. str, # PEP-noncompliant tuple of types. NoneTypeOr[AnyType], ) ''' Frozen set of **unignorable PEP-noncompliant type hints** (i.e., PEP-noncompliant type hints that are *not* ignorable). ''' # ....................{ NOT ~ sets }.................... NOT_HINTS_PEP = ( # PEP-noncompliant type hints. HINTS_NONPEP | # Hashable objects invalid as type hints (e.g., scalars). NOT_HINTS_HASHABLE ) ''' Tuple of various objects that are *not* PEP-compliant type hints exercising well-known edge cases. ''' beartype-0.18.5/beartype_test/a00_unit/data/hint/data_hintref.py000066400000000000000000000330401461113517100245660ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **forward reference data submodule.** This submodule exercises **forward reference type hints** (i.e., strings whose values are the names of classes and tuples of classes, one or more of which typically have yet to be defined) support implemented in the :func:`beartype.beartype` decorator. This support can *only* be fully exercised from within an independent data submodule rather than the body of a unit test. Why? Because: * That decorator is only safely importable from within the body of a unit test. * Forward reference type hints can only refer to objects defined at module scope rather than from within the body of a unit test. * Forward reference type hints referring to objects previously defined at module scope fail to exercise the deferred nature of forward references. * Ergo, callables that are decorated by that decorator, annotated by one or more forward reference type hints, and both declared and called from within the body of a unit test fail to exercise this deferred nature. * Ergo, only callables that are decorated by that decorator, annotated by one or more forward reference type hints, and both declared and called at module scope before their referents exercise this deferred nature. ''' # ....................{ IMPORTS }.................... from beartype import beartype from beartype.typing import ( Generic, List, Optional, Sequence, Tuple, Type, # <-- intentionally imported due to being embedded in strings below TypeVar, Union, ) from beartype._data.hint.datahinttyping import T # ....................{ LOCALS }.................... LikeATornCloud = TypeVar('LikeATornCloud', bound='BeforeTheHurricane') ''' Type variable whose bound is expressed as a PEP-compliant relative forward reference to a self-referential type that has yet to be defined. ''' # ....................{ FUNCTIONS ~ pep : discrete }.................... # Arbitrary functions annotated by PEP-compliant forward references defined as # both unqualified (relative) and fully-qualified (absolute) Python identifiers, # possibly nested in one or more parent type hints. TheDarkestForwardRefOfTheYear = ( 'beartype_test.a00_unit.data.hint.data_hintref.TheDarkestEveningOfTheYear') @beartype def the_woods_are_lovely(dark_and_deep: TheDarkestForwardRefOfTheYear) -> ( TheDarkestForwardRefOfTheYear): ''' :func:`beartype.beartype`-decorated function annotated by a fully-qualified forward reference referring to a type that has yet to be declared. ''' return dark_and_deep @beartype def stopping_by_woods_on(a_snowy_evening: 'TheDarkestEveningOfTheYear') -> ( 'TheDarkestEveningOfTheYear'): ''' :func:`beartype.beartype`-decorated function annotated by a relative forward reference referring to a type that has yet to be declared. ''' return a_snowy_evening TheDarkestUnionOfTheYear = Union[complex, 'TheDarkestEveningOfTheYear', bytes] @beartype def but_i_have_promises(to_keep: TheDarkestUnionOfTheYear) -> ( TheDarkestUnionOfTheYear): ''' :func:`beartype.beartype`-decorated function annotated by a union of arbitrary types *and* a relative forward reference referring to a type that has yet to be declared. ''' return to_keep NearTheShore = Optional[Sequence['TheDarkestEveningOfTheYear']] @beartype def a_little_shallop(floating: NearTheShore = ()) -> NearTheShore: ''' :func:`beartype.beartype`-decorated function accepting an optional parameter annotated by a union of arbitrary types *and* a relative forward reference referring to a type that has yet to be declared. ''' return floating # ....................{ FUNCTIONS ~ pep : composite }.................... # Arbitrary functions annotated by PEP-compliant forward references defined as # non-trivial Python expressions (i.e., strings that are *NOT* reducible to # trivial Python identifiers) containing unqualified (relative) and # fully-qualified (absolute) Python identifiers, either nested in one or more # parent type hints *OR* themselves serving as parent type hints nesting one or # more child type hints. # # Whereas discrete forward references are trivially supportable with simple # dynamic module attribute lookups at call time, composite forward references # can only be supported with non-trivial runtime parsing of Python expressions. @beartype def its_fields_of_snow( and_pinnacles_of_ice: 'List[TheDarkestForwardRefOfTheYear]') -> ( TheDarkestForwardRefOfTheYear): ''' :func:`beartype.beartype`-decorated function annotated by a composite type hint defined as a standard type hint subscripted by a relative forward reference referring to a type that has yet to be declared. ''' return and_pinnacles_of_ice[0] TheDarkestSubclassOfTheYear = 'Type[TheDarkestEveningOfTheYear]' @beartype def the_dry_leaf(rustles_in_the_brake: TheDarkestSubclassOfTheYear) -> ( TheDarkestSubclassOfTheYear): ''' :func:`beartype.beartype`-decorated function annotated by a composite type hint defined as a ``beartype.typing.Type[...]`` hint subscripted by a relative forward reference referring to a type that has yet to be declared, exercising an edge case with respect to proxying of :func:`issubclass` type-checks in forward reference proxies. See Also -------- https://github.com/beartype/beartype/issues/289 Issue exposing this edge case. ''' return rustles_in_the_brake # ....................{ FUNCTIONS ~ non-pep : discrete }.................... # Arbitrary functions annotated by PEP-noncompliant forward references defined # as both unqualified (relative) and fully-qualified (absolute) Python # identifiers, possibly nested in one or more parent type hints. TheDarkestTupleOfTheYear = (complex, TheDarkestForwardRefOfTheYear, bool) @beartype def of_easy_wind(and_downy_flake: TheDarkestTupleOfTheYear) -> ( TheDarkestTupleOfTheYear): ''' Decorated function annotated by a PEP-noncompliant tuple containing both standard types and a fully-qualified forward reference referring to a type that has yet to be declared. ''' return and_downy_flake # ....................{ CLASSES }.................... class TheDarkestEveningOfTheYear(str): ''' Arbitrary class previously referred to by forward references above. ''' pass class WithSluggishSurge(Generic[T]): ''' Arbitrary generic declaring a method annotated by a forward reference referring to an instance of this same generic. ''' @beartype def or_where_the_secret_caves(self) -> 'WithSluggishSurge[T]': ''' Arbitrary method annotated by a forward reference referring to an instance of this same generic. ''' return self # ....................{ CLASSES ~ self-referential }.................... # @beartype-decorated classes defining methods annotated by one or more # self-referential type hints (i.e., hints referring to the same class via a # PEP-compliant relative forward reference). @beartype class BeforeTheHurricane(object): ''' :func:`beartype.beartype`-decorated class defining a method annotated by a **self-referential relative forward reference** (i.e., referring to this class currently being defined). ''' def in_a_silver_vision_floats(self) -> Tuple[ LikeATornCloud, LikeATornCloud]: ''' Method annotated by a 2-tuple of type variables whose bounds are expressed as PEP-compliant relative forward references to this class currently being defined. This method exercises this edge case: https://github.com/beartype/beartype/issues/367 ''' # Return a 2-tuple intentionally violating this type variable. return ('As one that', 'in a silver vision floats') # Test decorating a user-defined class with the @beartype decorator where: # 1. That class defines a method annotated by a self-referential relative # forward reference (i.e., referring to that class currently being defined). # 2. That method is then called. # 3. That logic is then repeated *THREE TIMES,* thus redefining that class and # re-calling that method three times. Doing so simulates a hot reload (i.e., # external reload of the hypothetical user-defined module defining that class # and that function). # # For reasons that are *NOT* particularly interesting (and would consume seven # volumes of fine print), it has to be 3 iterations. 2 is too few. # # See also this user-reported issue underlying this test case: # https://github.com/beartype/beartype/issues/365 for _ in range(3): @beartype class OnTheBareMast(object): ''' :func:`beartype.beartype`-decorated class defining a method annotated by a **self-referential relative forward reference** (i.e., referring to this class currently being defined). ''' def __init__(self, and_took_his_lonely_seat: int) -> None: ''' Initialize this object with the passed arbitrary parameter. ''' self.and_took_his_lonely_seat = and_took_his_lonely_seat @classmethod def over_the_tranquil_sea( cls, and_took_his_lonely_seat: int) -> 'OnTheBareMast': ''' Method annotated by a self-referential relative forward reference. ''' return OnTheBareMast(and_took_his_lonely_seat + 0xBABECAFE) # Instantiate this class by invoking this factory class method. And_felt_the_boat_speed = OnTheBareMast.over_the_tranquil_sea(0xFEEDBABE) # ....................{ FUNCTIONS ~ pep : composite : moar }.................... # Arbitrary functions annotated by PEP-compliant forward references defined as # non-trivial Python expressions (i.e., strings that are *NOT* reducible to # trivial Python identifiers) containing unqualified (relative) and # fully-qualified (absolute) Python identifiers, requiring the previously # defined classes to have already been declared. @beartype def winding_among_the_springs( of_fire_and_poison: 'Inaccessible') -> 'Inaccessible': ''' Decorated function annotated by a relative forward reference referring to a **type alias** (i.e., global attribute whose value is a composite type hint defined as a standard type hint subscripted by an arbitrary type). ''' return of_fire_and_poison Inaccessible = WithSluggishSurge[int] ''' Arbitrary **type alias** (i.e., global attribute whose value is a composite type hint defined as a standard type hint subscripted by an arbitrary type). ''' # ....................{ CLOSURES }.................... #FIXME: Technically, @beartype *MAYBE* could actually resolve nested forward #references by dynamically inspecting the call stack (depending on whether #Python injects parent callables into the call stacks of closures, which it #probably does, but there's no way of knowing until we try, and that's a lot of #work to go to for potentially no gain whatsoever if Python doesn't behave like #we think it does). That gets real cray-cray and non-portable real fast, so #we're better off avoiding that unless we absolutely must -- and we mustn't, #because PEP 484 + 563 + Python 3.10 means that unqualified forward references #*MUST* now refer to globally scoped classes. So... why even bother, you know? # Note that we do *NOT* bother declaring nested callables annotated with forward # references to nested classes. Why? Unlike static analysis tooling, runtime # decorators have *NO* internal access into nested lexical scopes and thus # *CANNOT* by definition resolve forward references to nested classes. # # For example, the following callable is callable without exception but the # closures returned by that callable are *NOT*, because @beartype fails to # resolve the nested forward references annotating those closures: # from collections.abc import Callable # from typing import Tuple # # # Undecorated callable nesting... # def between_the_woods_and_frozen_lake() -> Tuple[Callable, Callable, type]: # # ..................{ CLOSURES }.................. # # Decorated closure annotated by an unnested unqualified forward # # reference referring to a type that has yet to be declared. # @beartype # def to_stop_without(a_farmhouse_near: 'WhoseWoodsTheseAreIThinkIKnow') -> ( # 'WhoseWoodsTheseAreIThinkIKnow'): # return a_farmhouse_near # # # Decorated closure annotated by a nested unqualified forward # # reference referring to a type that has yet to be declared. # TheDarkestNestedUnionOfTheYear = Union[ # int, 'WhoseWoodsTheseAreIThinkIKnow', bool] # @beartype # def to_watch_his_woods( # fill_up_with_snow: TheDarkestNestedUnionOfTheYear) -> ( # TheDarkestNestedUnionOfTheYear): # return fill_up_with_snow # # # ..................{ CLASSES }.................. # # User-defined class previously referred to by forward references above. # class WhoseWoodsTheseAreIThinkIKnow(str): pass # # # ..................{ RETURNS }.................. # # Return *ALL* of the above constructs, including closures and classes. # return (to_stop_without, to_watch_his_woods, WhoseWoodsTheseAreIThinkIKnow) beartype-0.18.5/beartype_test/a00_unit/data/hint/nonpep/000077500000000000000000000000001461113517100230635ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/hint/nonpep/__init__.py000066400000000000000000000000001461113517100251620ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/hint/nonpep/beartype/000077500000000000000000000000001461113517100246765ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/hint/nonpep/beartype/__init__.py000066400000000000000000000000001461113517100267750ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/hint/nonpep/beartype/_data_nonpepbeartype.py000066400000000000000000000203211461113517100314310ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype-specific PEP-noncompliant type hints** (i.e., unofficial type hints supported *only* by the :mod:`beartype.beartype` decorator) test data. These hints include: * **Fake builtin types** (i.e., types that are *not* builtin but which nonetheless erroneously masquerade as being builtin). * **Tuple unions** (i.e., tuples containing *only* standard classes and forward references to standard classes). ''' # ....................{ FIXTURES }.................... def hints_nonpepbeartype_meta() -> 'List[HintNonpepMetadata]': ''' List of :mod:`beartype`-specific **type hint metadata** (i.e., :class:`beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintNonpepMetadata` instances describing test-specific :mod:`beartype`-specific PEP-noncompliant sample type hints with metadata generically leveraged by various PEP-agnostic unit tests). ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype.plug import BeartypeHintable from beartype.vale import Is from beartype._util.api.utilapityping import iter_typing_attrs from beartype_test.a00_unit.data.hint.util.data_hintmetacls import ( HintNonpepMetadata, HintPithSatisfiedMetadata, HintPithUnsatisfiedMetadata, ) # ..................{ LOCALS }.................. # List of all PEP-noncompliant type hint metadata to be returned. hints_nonpep_meta = [] # ..................{ LISTS }.................. # Add beartype-specific PEP-noncompliant test type hints to this list. hints_nonpep_meta.extend(( # ................{ TUPLE UNION }................ # Beartype-specific tuple unions (i.e., tuples containing one or more # isinstanceable classes). # Tuple union of one isinstanceable class. HintNonpepMetadata( hint=(str,), piths_meta=( # String constant. HintPithSatisfiedMetadata('Pinioned coin tokens'), # Byte-string constant. HintPithUnsatisfiedMetadata( pith=b'Murkily', # Match that the exception message raised for this pith # declares the types *NOT* satisfied by this object. exception_str_match_regexes=( r'\bstr\b', ), # Match that the exception message raised for this pith # does *NOT* contain a newline or bullet delimiter. exception_str_not_match_regexes=( r'\n', r'\*', ), ), ), ), # Tuple union of two or more isinstanceable classes. HintNonpepMetadata( hint=(int, str), piths_meta=( # Integer constant. HintPithSatisfiedMetadata(12), # String constant. HintPithSatisfiedMetadata('Smirk‐opined — openly'), # Byte-string constant. HintPithUnsatisfiedMetadata( pith=b'Betokening', # Match that the exception message raised for this object # declares the types *NOT* satisfied by this object. exception_str_match_regexes=( r'\bint\b', r'\bstr\b', ), # Match that the exception message raised for this object # does *NOT* contain a newline or bullet delimiter. exception_str_not_match_regexes=( r'\n', r'\*', ), ), ), ), )) # ..................{ VALIDATORS ~ is }.................. # Beartype-specific validators defined as lambda functions. IsNonempty = Is[lambda text: bool(text)] # ..................{ FACTORIES }.................. # For each "Annotated" type hint factory importable from a typing module... for Annotated in iter_typing_attrs('Annotated'): # ..................{ LOCALS ~ plugin }.................. # Local variables requiring an "Annotated" type hint factory # additionally exercising beartype's plugin API. class StringNonempty(str, BeartypeHintable): ''' **Non-empty string** (i.e., :class:`str` subclass satisfying the :class:`BeartypeHintable` protocol by defining the :meth:`__beartype_hint__` class method to return a type hint constraining instances of this subclass to non-empty strings). ''' @classmethod def __beartype_hint__(cls) -> object: ''' Beartype type hint transform reducing to an annotated of this subclass validating instances of this subclass to be non-empty. ''' # Munificent one-liner: I invoke thee! return Annotated[cls, IsNonempty] #FIXME: Uncomment after finalizing the "_plughintable" API, please. # # ................{ TUPLES }................ # # Add PEP 593-specific test type hints to this tuple global. # data_module.HINTS_NONPEP_META.extend(( # # ..............{ ANNOTATED ~ beartype : is : plugin }.............. # # Note that beartype's plugin API straddles the fine line between # # PEP-compliant and PEP-noncompliant type hints. Superficially, most # # isinstanceable types are PEP-noncompliant and thus exercised in # # unit tests via the "HintNonpepMetadata" dataclass. Semantically, # # isinstanceable type satisfying the "BeartypeHintable" protocol # # define __beartype_hint__() class methods returning PEP-compliant # # type hints instead exercised in unit tests via the # # "HintPepMetadata" dataclass. To avoid confusion, these types are: # # Superficially accepted as PEP-noncompliant. Treating them instead # # as PEP-compliant would be feasible but require non-trivial # # replacement of these types with the type hints returned by their # # __beartype_hint__() class methods. Doing so would also probably # # break literally everything. Did we mention that? # # # Isinstanceable type satisfying the "BeartypeHintable" protocol, # # whose __beartype_hint__() class method returns an annotated of an # # isinstanceable type annotated by one beartype-specific validator # # defined as a lambda function. # HintNonpepMetadata( # hint=StringNonempty, # piths_meta=( # # String constant satisfying this validator. # HintPithSatisfiedMetadata( # "Impell no pretty‐spoked fellahs’ prudently"), # # Byte-string constant *NOT* an instance of the expected # # type. # HintPithUnsatisfiedMetadata( # pith=b'Impudent Roark-sparkful', # # Match that the exception message raised for this # # object embeds the code for this validator's lambda # # function. # exception_str_match_regexes=( # r'Is\[.*\bbool\(text\).*\]',), # ), # # Empty string constant violating this validator. # HintPithUnsatisfiedMetadata(''), # ), # ), # )) # ..................{ RETURN }.................. # Return this list of all PEP-noncompliant type hint metadata. return hints_nonpep_meta beartype-0.18.5/beartype_test/a00_unit/data/hint/nonpep/data_nonpep.py000066400000000000000000000041711461113517100257300ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **PEP-noncompliant type hints test data.** This submodule predefines low-level global constants whose values are PEP-noncompliant type hints, exercising known edge cases on behalf of higher-level unit test submodules. ''' # ....................{ IMPORTS }.................... from pytest import fixture # ....................{ FIXTURES }.................... @fixture(scope='session') def hints_nonpep_meta() -> 'Tuple[HintNonpepMetadata]': ''' Session-scoped fixture yielding a tuple of **PEP-noncompliant type hint metadata** (i.e., :class:`beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintNonpepMetadata` instances, each describing a sample PEP-noncompliant type hint exercising an edge case in the :mod:`beartype` codebase). ''' # ..................{ IMPORTS }.................. # Defer fixture-specific imports. from beartype_test.a00_unit.data.hint.util.data_hintmetacls import ( HintNonpepMetadata) from beartype_test._util.kind.pytkindmake import make_container_from_funcs # ..................{ LIST }.................. _hints_nonpep_meta = make_container_from_funcs(( # PEP-compliant type hints. 'beartype_test.a00_unit.data.hint.nonpep.beartype._data_nonpepbeartype.hints_nonpepbeartype_meta', 'beartype_test.a00_unit.data.hint.nonpep.proposal._data_nonpep484.hints_nonpep484_meta', )) # ..................{ YIELD }.................. # Assert this list contains *ONLY* instances of the expected dataclass. assert ( isinstance(hint_nonpep_meta, HintNonpepMetadata) for hint_nonpep_meta in _hints_nonpep_meta ), (f'{repr(_hints_nonpep_meta)} not iterable of ' f'"HintNonpepMetadata" instances.') # Yield a tuple coerced from this list. yield tuple(_hints_nonpep_meta) beartype-0.18.5/beartype_test/a00_unit/data/hint/nonpep/proposal/000077500000000000000000000000001461113517100247225ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/hint/nonpep/proposal/__init__.py000066400000000000000000000000001461113517100270210ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/hint/nonpep/proposal/_data_nonpep484.py000066400000000000000000000420141461113517100301640ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`484`-compliant PEP-noncompliant type hint test data. :pep:`484`-compliant type hints *mostly* indistinguishable from PEP-noncompliant type hints include: * :func:`typing.NamedTuple`, a high-level factory function deferring to the lower-level :func:`collections.namedtuple` factory function creating and returning :class:`tuple` instances annotated by PEP-compliant type hints. * :func:`typing.TypedDict`, a high-level factory function creating and returning :class:`dict` instances annotated by PEP-compliant type hints. ''' # ....................{ FIXTURES }.................... def hints_nonpep484_meta() -> 'List[HintNonpepMetadata]': ''' List of :pep:`484`-sorta-compliant **type hint metadata** (i.e., :class:`beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintNonpepMetadata` instances describing test-specific :pep:`484`--sorta-compliant sample type hints with metadata generically leveraged by various PEP-agnostic unit tests). ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. import sys from beartype import BeartypeConf from beartype.typing import ( NamedTuple, ) from beartype._cave._cavefast import ( EllipsisType, FunctionType, FunctionOrMethodCType, MethodBoundInstanceOrClassType, ModuleType, NoneType, NotImplementedType, ) from beartype_test.a00_unit.data.data_type import Class from beartype_test.a00_unit.data.hint.util.data_hintmetacls import ( HintNonpepMetadata, HintPithSatisfiedMetadata, HintPithUnsatisfiedMetadata, ) # ..................{ LOCALS }.................. # List of all PEP-noncompliant type hint metadata to be returned. hints_nonpep_meta = [] #FIXME: *WOOPS.* We should have read the standards a bit closer. Neither #"typing.NamedTuple" or "typing.TypedDict" are intended for direct use as #type hints. To quote official "typing" documentation: # These are not used in annotations. They are building blocks for declaring # types. # #Of course, all types *ARE* valid type hints. "typing.NamedTuple" and #"typing.TypedDict" subclasses are types and thus also valid type hints. So, #the superficial testing we perform below is certainly useful; we just don't #need to do anything further, really. Phew! # PEP-compliant user-defined "collections.namedtuple" instance typed with # PEP-compliant type hints. NamedTupleType = NamedTuple( 'NamedTupleType', [('fumarole', str), ('enrolled', int)]) # ..................{ LISTS }.................. # Add PEP 484-specific (albeit technically PEP-noncompliant from the # beartype perspective) test type hints to this list. hints_nonpep_meta.extend(( # ................{ NAMEDTUPLE }................ # "NamedTuple" instances transparently reduce to standard tuples and # *MUST* thus be handled as non-"typing" type hints. HintNonpepMetadata( hint=NamedTupleType, piths_meta=( # Named tuple containing correctly typed items. HintPithSatisfiedMetadata( NamedTupleType(fumarole='Leviathan', enrolled=37)), # String constant. HintPithUnsatisfiedMetadata('Of ͼarthen concordance that'), #FIXME: Uncomment after implementing "NamedTuple" support. # # Named tuple containing incorrectly typed items. # HintPithUnsatisfiedMetadata( # pith=NamedTupleType(fumarole='Leviathan', enrolled=37), # # Match that the exception message raised for this object... # exception_str_match_regexes=( # # Declares the name of this tuple's problematic item. # r'\s[Ll]ist item 0\s', # ), # ), ), ), # ................{ TYPEDDICT }................ # "TypedDict" instances transparently reduce to dicts. #FIXME: Implement us up, but note when doing so that: #* We currently unconditionally reduce "TypeDict" to "Mapping". #* "TypedDict" was first introduced with Python 3.8. # ................{ TYPE ~ builtin }................ # Integer. HintNonpepMetadata( hint=int, piths_meta=( # Integer constant. HintPithSatisfiedMetadata(42), # <-- we went there, folks # String constant. HintPithUnsatisfiedMetadata( pith='Introspectively ‘allein,’ dealigning consangui-', # Match that the exception message raised for this pith # contains... exception_str_match_regexes=( # The type *NOT* satisfied by this object. r'\bint\b', ), # Match that the exception message raised for this pith # does *NOT* contain... exception_str_not_match_regexes=( # A newline. r'\n', # A bullet delimiter. r'\*', # The double-quoted name of this builtin type. r'"int"', ), ), ), ), # Unicode string. HintNonpepMetadata( hint=str, piths_meta=( # String constant. HintPithSatisfiedMetadata('Glassily lassitudinal bȴood-'), # Byte-string constant. HintPithUnsatisfiedMetadata( pith=b'Stains, disdain-fully ("...up-stairs!"),', # Match that the exception message raised for this pith # contains... exception_str_match_regexes=( # The type *NOT* satisfied by this object. r'\bstr\b', # The representation of this object preserved as is. r'\sb\'Stains, disdain-fully \("...up-stairs!"\),\'\s', ), # Match that the exception message raised for this pith # does *NOT* contain... exception_str_not_match_regexes=( # A newline. r'\n', # A bullet delimiter. r'\*', # The double-quoted name of this builtin type. r'"str"', ), ), # Integer constant. HintPithUnsatisfiedMetadata( pith=666, # <-- number of the beast, yo # Match that the exception message raised for this pith # contains... exception_str_match_regexes=( # The type *NOT* satisfied by this object. r'\bstr\b', # The representation of this object preserved as is. r'\s666\s', ), # Match that the exception message raised for this pith # does *NOT* contain... exception_str_not_match_regexes=( # A newline. r'\n', # A bullet delimiter. r'\*', # The double-quoted name of this builtin type. r'"str"', ), ), ), ), # ................{ TYPE ~ builtin : tower }................ # Types pertaining to the implicit numeric tower (i.e., optional PEP # 484-compliant (sub)standard in which type hints defined as broad # numeric types implicitly match all narrower numeric types as well by # enabling the "beartype.BeartypeConf.is_pep484_tower" parameter). When # enabled, @beartype implicitly expands: # * "float" to "float | int". # * "complex" to "complex | float | int". # # See also the "data_pep484" submodule, which defines additional # PEP 484-compliant type hints nesting type hints pertaining to the # implicit numeric tower (e.g., "Union[float, str]"). # Floating-point number with the implicit numeric tower disabled. HintNonpepMetadata( hint=float, conf=BeartypeConf(is_pep484_tower=False), piths_meta=( # Floating-point number constant. HintPithSatisfiedMetadata(0.110001), # Integer constant. HintPithUnsatisfiedMetadata( pith=110001, # Match that the exception message raised for this pith # contains... exception_str_match_regexes=( # The type *NOT* satisfied by this object. r'\bfloat\b', ), # Match that the exception message raised for this pith # does *NOT* contain... exception_str_not_match_regexes=( # A newline. r'\n', # A bullet delimiter. r'\*', # The double-quoted name of this builtin type. r'"float"', ), ), ), ), # Floating-point number with the implicit numeric tower enabled. HintNonpepMetadata( hint=float, conf=BeartypeConf(is_pep484_tower=True), piths_meta=( # Floating-point number constant. HintPithSatisfiedMetadata(0.577215664901532860606512090082), # Integer constant. HintPithSatisfiedMetadata(5772), # Complex number constant. HintPithUnsatisfiedMetadata( pith=(1566 + 4901j), # Match that the exception message raised for this pith # contains... exception_str_match_regexes=( # The type *NOT* satisfied by this object. r'\bfloat\b', ), # Match that the exception message raised for this pith # does *NOT* contain... exception_str_not_match_regexes=( # A newline. r'\n', # A bullet delimiter. r'\*', # The double-quoted name of this builtin type. r'"float"', ), ), ), ), # Complex number with the implicit numeric tower disabled. HintNonpepMetadata( hint=complex, conf=BeartypeConf(is_pep484_tower=False), piths_meta=( # Complex number constant. HintPithSatisfiedMetadata(1.787 + 2316.5j), # Floating-point number constant. HintPithUnsatisfiedMetadata( pith=0.300330000000000330033, # Match that the exception message raised for this pith # contains... exception_str_match_regexes=( # The type *NOT* satisfied by this object. r'\bcomplex\b', ), # Match that the exception message raised for this pith # does *NOT* contain... exception_str_not_match_regexes=( # A newline. r'\n', # A bullet delimiter. r'\*', # The double-quoted name of this builtin type. r'"complex"', ), ), ), ), # Complex number with the implicit numeric tower enabled. HintNonpepMetadata( hint=complex, conf=BeartypeConf(is_pep484_tower=True), piths_meta=( # Complex number constant. HintPithSatisfiedMetadata(2.622 + 575.5j), # Floating-point number constant. HintPithSatisfiedMetadata(0.8346268), # Integer constant. HintPithSatisfiedMetadata(1311), # String constant. HintPithUnsatisfiedMetadata( pith='Park-ed trails', # Match that the exception message raised for this pith # contains... exception_str_match_regexes=( # The type *NOT* satisfied by this object. r'\bcomplex\b', ), # Match that the exception message raised for this pith # does *NOT* contain... exception_str_not_match_regexes=( # A newline. r'\n', # A bullet delimiter. r'\*', # The double-quoted name of this builtin type. r'"complex"', ), ), ), ), # ................{ TYPE ~ builtin : fake }................ # Fake builtin types (i.e., types that are *NOT* builtin but which # nonetheless erroneously masquerade as being builtin), exercising edge # cases in @beartype code generation. # # See also: # * The "beartype._data.cls.datacls.TYPES_BUILTIN_FAKE" set. # Fake builtin ellipsis type. HintNonpepMetadata( hint=EllipsisType, piths_meta=( # Ellipsis singleton. HintPithSatisfiedMetadata(...), # String constant. HintPithUnsatisfiedMetadata( 'Masterless decree, venomless, which'), ), ), # Fake builtin pure-Python function type. HintNonpepMetadata( hint=FunctionType, piths_meta=( # Pure-Python function. HintPithSatisfiedMetadata(hints_nonpep484_meta), # String constant. HintPithUnsatisfiedMetadata('Nomenclature weather‐vanes of'), ), ), # Fake builtin C-based function type. HintNonpepMetadata( hint=FunctionOrMethodCType, piths_meta=( # C-based function. HintPithSatisfiedMetadata(len), # String constant. HintPithUnsatisfiedMetadata( 'Nominally unswain, autodidactic idiocracy, less a'), ), ), # Fake builtin bound method type. HintNonpepMetadata( hint=MethodBoundInstanceOrClassType, piths_meta=( # Bound method. HintPithSatisfiedMetadata(Class().instance_method), # String constant. HintPithUnsatisfiedMetadata( 'ç‐omically gnomical whitebellied burden’s empathy of'), ), ), # Fake builtin module type. HintNonpepMetadata( hint=ModuleType, piths_meta=( # Imported module. HintPithSatisfiedMetadata(sys), # String constant. HintPithUnsatisfiedMetadata( 'Earpiece‐piecemealed, mealy straw headpiece-'), ), ), # Fake builtin "None" singleton type. HintNonpepMetadata( hint=NoneType, piths_meta=( # "None" singleton. HintPithSatisfiedMetadata(None), # String constant. HintPithUnsatisfiedMetadata( 'Earned peace appeasement easements'), ), ), # Fake builtin "NotImplemented" type. HintNonpepMetadata( hint=NotImplementedType, piths_meta=( # "NotImplemented" singleton. HintPithSatisfiedMetadata(NotImplemented), # String constant. HintPithUnsatisfiedMetadata('Than'), ), ), )) # ..................{ RETURN }.................. # Return this list of all PEP-noncompliant type hint metadata. return hints_nonpep_meta beartype-0.18.5/beartype_test/a00_unit/data/hint/pep/000077500000000000000000000000001461113517100223505ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/hint/pep/__init__.py000066400000000000000000000000001461113517100244470ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/hint/pep/data_pep.py000066400000000000000000000311271461113517100245030ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype PEP-compliant type hints test data.** This submodule predefines low-level global constants whose values are PEP-compliant type hints, exercising known edge cases on behalf of higher-level unit test submodules. ''' # ....................{ TODO }.................... #FIXME: In hindsight, the structure of both this submodule and subsidiary #submodules imported below by the _init() method is simply *ABYSMAL.* Instead: #* Do everything below first for low-hanging fruit *NOT* widely used throughout # our test suite. This means (in order): # * "HINTS_PEP_IGNORABLE_DEEP". # * "HINTS_PEP_IGNORABLE_SHALLOW". # * "HINTS_IGNORABLE". # * "HINTS_PEP_HASHABLE". # * "NOT_HINTS_NONPEP". # * "HINTS_PEP_META". # # In particular, avoid attempting to refactor "HINTS_PEP_META" until *AFTER* # refactoring everything else. Refactoring "HINTS_PEP_META" will prove # extremely time-consuming and thus non-trivial, sadly. *sigh* #* Define one new @pytest.fixture-decorated session-scoped function for each # public global variable currently defined below: e.g., # # Instead of this... # HINTS_PEP_IGNORABLE_SHALLOW = None # # # ...do this instead. # from beartype_test.a00_util.data.hint.pep.proposal.data_pep484 import ( # hints_pep_ignorable_shallow_pep484, # ) # from beartype_test.a00_util.data.hint.pep.proposal._data_pep593 import ( # hints_pep_ignorable_shallow_pep593, # ) # from collections.abc import Set # from pytest import fixture # # @fixture(scope='session') # def hints_pep_ignorable_shallow( # hints_pep_ignorable_shallow_pep484, # hints_pep_ignorable_shallow_pep593, # ..., # ) -> Set: # return ( # hints_pep_ignorable_shallow_pep484 | # hints_pep_ignorable_shallow_pep593 | # ... # ) #* In the "beartype_test.a00_unit.conftest" submodule, import those fixtures to # implicitly expose those fixtures to all unit tests: e.g., # from beartype_test.a00_util.data.hint.pep.data_pep import ( # hints_pep_ignorable_shallow, # ) ## Refactor all unit tests previously explicitly importing # "HINTS_PEP_IGNORABLE_SHALLOW" to instead accept the # "hints_pep_ignorable_shallow" fixture as a function parameter: e.g., # def test_em_up(hints_pep_ignorable_shallow: Set) -> None: ... # #The advantages are obvious. Currently, we unconditionally build these globals #out in an extremely convoluted process that happens really extremely early at #*PYTEST COLLECTION TIME.* That's horrible. The above refactoring instead defers #that build-out to *TEST CALL TIME.* Any tests skipped or ignored for the #current test session will result in fixtures required by those tests also being #skipped and ignored. Ultimately, though, the principal benefit is #maintainability; the above approach isolates PEP-specific data containers to #their own PEP-specific fixtures, which are then composable into even larger #PEP-agnostic fixtures. It just makes sense. Let's do this sometime, everybody. # ....................{ IMPORTS }.................... from pytest import fixture # ....................{ FIXTURES }.................... @fixture(scope='session') def hints_pep_meta() -> 'Tuple[HintPepMetadata]': ''' Session-scoped fixture yielding a tuple of **PEP-compliant type hint metadata** (i.e., :class:`beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata` instances, each describing a sample PEP-compliant type hint exercising an edge case in the :mod:`beartype` codebase). Design ------ This tuple was initially designed as a dictionary mapping from PEP-compliant type hints to :class:`HintPepMetadata` instances describing those hints, until :mod:`beartype` added support for PEPs enabling unhashable PEP-compliant type hints (e.g., ``typing.Annotated[list, []]`` under :pep:`593`) impermissible for use as dictionary keys or set members. ''' # ..................{ IMPORTS }.................. # Defer fixture-specific imports. from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_12 from beartype_test.a00_unit.data.hint.util.data_hintmetacls import ( HintPepMetadata) from beartype_test._util.kind.pytkindmake import make_container_from_funcs # ..................{ LIST }.................. _hints_pep_meta = make_container_from_funcs(( # PEP-compliant type hints. 'beartype_test.a00_unit.data.hint.pep.proposal.data_pep484.hints_pep484_meta', 'beartype_test.a00_unit.data.hint.pep.proposal._data_pep544.hints_pep544_meta', 'beartype_test.a00_unit.data.hint.pep.proposal._data_pep585.hints_pep585_meta', 'beartype_test.a00_unit.data.hint.pep.proposal._data_pep586.hints_pep586_meta', 'beartype_test.a00_unit.data.hint.pep.proposal._data_pep589.hints_pep589_meta', 'beartype_test.a00_unit.data.hint.pep.proposal._data_pep593.hints_pep593_meta', 'beartype_test.a00_unit.data.hint.pep.proposal._data_pep604.hints_pep604_meta', 'beartype_test.a00_unit.data.hint.pep.proposal._data_pep675.hints_pep675_meta', # PEP-noncompliant type hints defined by both standard and third-party # packages internally treated by @beartype as PEP-compliant to # streamline code generation. # # PEP-noncompliant type hints are intentionally tested *AFTER* # PEP-compliant type hints to simplify debugging in the event that core # functionality is catastrophically broken. *gulp* 'beartype_test.a00_unit.data.hint.pep.module._data_hintmodnumpy.hints_pep_meta_numpy', 'beartype_test.a00_unit.data.hint.pep.module._data_hintmodos.hints_pep_meta_os', 'beartype_test.a00_unit.data.hint.pep.module._data_hintmodweakref.hints_pep_meta_weakref', )) # If the active Python interpreter targets Python >= 3.12, this interpreter # supports PEP 695 -- including the PEP 695-specific "type" alias statement # whose syntax is *EXTREMELY* invalid under prior Python versions. Ideally, # this syntax would simply be ignored by older Python versions like all # other PEP-specific syntax developed by newer Python versions (e.g., PEP # 604-style "|"-delimited new unions). Sadly, older Python versions raise # exceptions resembling the following on attempting to import from *ANY* # modules containing even a single PEP 695-specific "type" alias statement # -- even if those older Python versions never even execute that statement: # SyntaxError: invalid syntax # <-- uhh, wut if IS_PYTHON_AT_LEAST_3_12: from beartype_test.a00_unit.data.hint.pep.proposal._data_pep695 import ( hints_pep695_meta) _hints_pep_meta.extend(hints_pep695_meta()) # Else, the active Python interpreter targets Python < 3.12 and thus fails # to support PEP 695. # ..................{ YIELD }.................. # Assert this list contains *ONLY* instances of the expected dataclass. assert ( isinstance(hint_pep_meta, HintPepMetadata) for hint_pep_meta in _hints_pep_meta ), f'{repr(_hints_pep_meta)} not iterable of "HintPepMetadata" instances.' # Yield a tuple coerced from this list. yield tuple(_hints_pep_meta) @fixture(scope='session') def hints_pep_hashable(hints_pep_meta) -> frozenset: ''' Session-scoped fixture yielding a frozen set of **hashable PEP-compliant non-class type hints** (i.e., PEP-compliant type hints that are *not* classes but *are* accepted by the builtin :func:`hash` function *without* raising an exception and thus usable in hash-based containers like dictionaries and sets). Hashable PEP-compliant class type hints (e.g., generics, protocols) are largely indistinguishable from PEP-noncompliant class type hints and thus useless for testing purposes. Parameters ---------- hints_pep_meta : Tuple[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata] Tuple of PEP-compliant type hint metadata describing PEP-compliant type hints exercising edge cases in the :mod:`beartype` codebase. ''' # Defer fixture-specific imports. from beartype._util.utilobject import is_object_hashable # Yield this frozen set. yield frozenset( hint_meta.hint for hint_meta in hints_pep_meta if is_object_hashable(hint_meta.hint) ) # ....................{ FIXTURES ~ ignorable }.................... @fixture(scope='session') def hints_pep_ignorable_shallow() -> frozenset: ''' Session-scoped fixture yielding a frozen set of **shallowly ignorable PEP-compliant type hints** (i.e., ignorable on the trivial basis of their machine-readable representations alone and thus in the low-level :obj:`beartype._data.hint.pep.datapeprepr.HINTS_REPR_IGNORABLE_SHALLOW` set, but which are typically *not* safely instantiable from those representations and thus require explicit instantiation here). ''' # ..................{ IMPORTS }.................. # Defer fixture-specific imports. from beartype_test._util.kind.pytkindmake import make_container_from_funcs # ..................{ FIXTURE }.................. # List of all shallowly ignorable PEP-compliant type hints to be returned. _hints_pep_ignorable_shallow = make_container_from_funcs(( 'beartype_test.a00_unit.data.hint.pep.proposal.data_pep484.hints_pep484_ignorable_shallow', 'beartype_test.a00_unit.data.hint.pep.proposal._data_pep544.hints_pep544_ignorable_shallow', )) # Yield a frozen set coerced from this list. yield frozenset(_hints_pep_ignorable_shallow) @fixture(scope='session') def hints_pep_ignorable_deep() -> frozenset: ''' Session-scoped fixture yielding a frozen set of **deeply ignorable PEP-compliant type hints** (i.e., *not* ignorable on the trivial basis of their machine-readable representations alone and thus *not* in the low-level :obj:`beartype._data.hint.pep.datapeprepr.HINTS_REPR_IGNORABLE_DEEP` set, but which are nonetheless ignorable and thus require dynamic testing by the high-level :func:`beartype._util.hint.utilhinttest.is_hint_ignorable` tester function to demonstrate this fact). ''' # ..................{ IMPORTS }.................. # Defer fixture-specific imports. from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_12 from beartype_test._util.kind.pytkindmake import make_container_from_funcs # ..................{ FIXTURE }.................. # List of all deeply ignorable PEP-compliant type hints to be returned. _hints_pep_ignorable_deep = make_container_from_funcs(( 'beartype_test.a00_unit.data.hint.pep.proposal.data_pep484.hints_pep484_ignorable_deep', 'beartype_test.a00_unit.data.hint.pep.proposal._data_pep544.hints_pep544_ignorable_deep', 'beartype_test.a00_unit.data.hint.pep.proposal._data_pep593.hints_pep593_ignorable_deep', 'beartype_test.a00_unit.data.hint.pep.proposal._data_pep604.hints_pep604_ignorable_deep', )) # If the active Python interpreter targets Python >= 3.12, this interpreter # supports PEP 695 -- including the PEP 695-specific "type" alias statement # whose syntax is *EXTREMELY* invalid under prior Python versions. Ideally, # this syntax would simply be ignored by older Python versions like all # other PEP-specific syntax developed by newer Python versions (e.g., PEP # 604-style "|"-delimited new unions). Sadly, older Python versions raise # exceptions resembling the following on attempting to import from *ANY* # modules containing even a single PEP 695-specific "type" alias statement # -- even if those older Python versions never even execute that statement: # SyntaxError: invalid syntax # <-- uhh, wut if IS_PYTHON_AT_LEAST_3_12: from beartype_test.a00_unit.data.hint.pep.proposal._data_pep695 import ( hints_pep695_ignorable_deep) _hints_pep_ignorable_deep.extend(hints_pep695_ignorable_deep()) # Else, the active Python interpreter targets Python < 3.12 and thus fails # to support PEP 695. # Yield a frozen set coerced from this list. yield frozenset(_hints_pep_ignorable_deep) beartype-0.18.5/beartype_test/a00_unit/data/hint/pep/module/000077500000000000000000000000001461113517100236355ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/hint/pep/module/__init__.py000066400000000000000000000000001461113517100257340ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/hint/pep/module/_data_hintmodnumpy.py000066400000000000000000000305421461113517100300760ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **NumPy-specific PEP-noncompliant type hints** (i.e., unofficial type hints published by the third-party :mod:`numpy` package) test data. These hints include: * **Typed NumPy arrays** (i.e., subscriptions of the :obj:`numpy.typing.NDArray` type hint factory). Caveats ------- Although NumPy-specific type hints are technically PEP-noncompliant, the :mod:`beartype` codebase currently treats these hints as PEP-compliant to dramatically simplify code generation for these hints. Ergo, so we do. ''' # ....................{ FIXTURES }.................... def hints_pep_meta_numpy() -> 'List[HintPepMetadata]': ''' List of **NumPy type hint metadata** (i.e., :class:`beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata` instances describing test-specific sample NumPy type hints with metadata generically leveraged by various PEP-agnostic unit tests). ''' # ..................{ IMPORTS ~ early }.................. # Defer early-time imports. from beartype_test._util.module.pytmodtest import ( is_package_numpy_typing_ndarray_deep) # ..................{ LOCALS }.................. # List of all PEP-specific type hint metadata to be returned. hints_pep_meta = [] # ..................{ UNSUPPORTED }.................. # If beartype does *NOT* deeply support "numpy.typing.NDArray" type hints # under the active Python interpreter, return the empty list. if not is_package_numpy_typing_ndarray_deep(): return hints_pep_meta # Else, beartype deeply supports "numpy.typing.NDArray" type hints under # the active Python interpreter. # ..................{ IMPORTS }.................. # Defer fixture-specific imports. from beartype.typing import ( Any, Tuple, ) from beartype.vale import Is from beartype._data.hint.pep.sign.datapepsigns import ( HintSignNumpyArray, HintSignTuple, ) from beartype._util.api.utilapityping import import_typing_attr # from beartype._util.api.utilapityping import get_typing_attrs from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_9 from beartype_test.a00_unit.data.hint.util.data_hintmetacls import ( HintPepMetadata, HintPithSatisfiedMetadata, HintPithUnsatisfiedMetadata, ) # Defer NumPy-specific imports. from numpy import asarray, dtype, float32, float64, floating from numpy.typing import NDArray # ..................{ VALIDATORS }.................. # "typing.Annotated" type hint factory imported from either the "typing" or # "typing_extensions" modules if importable *OR* "None" otherwise. By prior # validation, this factory *MUST* be non-"None" here. Annotated = import_typing_attr('Annotated') # Validator matching one-dimensional NumPy arrays of floats of 64-bit # precision, combining both validator and NumPy type hinting syntax. This # exercises an edge case previously generating syntactically invalid code. Numpy1DFloat64Array = Annotated[ NDArray[float64], Is[lambda array: array.ndim == 1]] # ..................{ LISTS }.................. # Add PEP-specific type hint metadata to this list. hints_pep_meta.extend(( # ................{ NUMPY ~ array }................ # Untyped unsubscripted NumPy array. HintPepMetadata( hint=NDArray, pep_sign=HintSignNumpyArray, # "NDArray" is implemented as: # * Under Python >= 3.9, a PEP 585-compliant generic. # * Under Python >= 3.8, a pure-Python generic backport. is_pep585_builtin_subscripted=IS_PYTHON_AT_LEAST_3_9, is_type_typing=False, is_typing=False, # Oddly, NumPy implicitly parametrizes the "NDArray[Any]" type hint # by expanding that hint to "numpy.typing.NDArray[+ScalarType]", # where "+ScalarType" is a public type variable declared by the # "numpy" package bounded above by the "numpy.generic" abstract # base class for NumPy scalars. *sigh* is_typevars=True, piths_meta=( # NumPy array containing only 64-bit integers. HintPithSatisfiedMetadata(asarray(( 1, 0, 3, 5, 2, 6, 4, 9, 2, 3, 8, 4, 1, 3, 7, 7, 5, 0,))), # NumPy array containing only 64-bit floats. HintPithSatisfiedMetadata(asarray(( 1.3, 8.23, 70.222, 726.2431, 8294.28730, 100776.357238,))), # String constant. HintPithUnsatisfiedMetadata( pith='Ine Gerrymander‐consigned electorate sangu‐', # Match that the exception message raised for this object # embeds the representation of the expected class. exception_str_match_regexes=(r'\bnumpy\.ndarray\b',), ), ), ), # Untyped subscripted NumPy array. HintPepMetadata( hint=NDArray[Any], pep_sign=HintSignNumpyArray, is_pep585_builtin_subscripted=IS_PYTHON_AT_LEAST_3_9, is_type_typing=False, is_typing=False, piths_meta=( # NumPy array containing only 64-bit integers. HintPithSatisfiedMetadata(asarray(( 1, 7, 39, 211, 1168, 6728, 40561, 256297, 1696707,))), # NumPy array containing only 64-bit floats. HintPithSatisfiedMetadata(asarray(( 1.1, 2.4, -4.4, 32.104, 400.5392, -3680.167936,))), # String constant. HintPithUnsatisfiedMetadata( pith=( 'Inity my guinea‐konsealed Ğuinness’ pint ' 'glockenspieled spells', ), # Match that the exception message raised for this object # embeds the representation of the expected class. exception_str_match_regexes=(r'\bnumpy\.ndarray\b',), ), ), ), # ................{ NUMPY ~ array : dtype : equals }................ # Typed NumPy array subscripted by an actual data type (i.e., instance # of the "numpy.dtype" class). HintPepMetadata( hint=NDArray[dtype(float64)], pep_sign=HintSignNumpyArray, is_pep585_builtin_subscripted=IS_PYTHON_AT_LEAST_3_9, is_type_typing=False, is_typing=False, piths_meta=( # NumPy array containing only 64-bit floats. HintPithSatisfiedMetadata( asarray((1.0, 1.5, 1.8333, 2.08333, 2.28333, 2.45,)), ), # String constant. HintPithUnsatisfiedMetadata( pith='Aggrandizing strifes with‐in', # Match that the exception message raised for this object # embeds the representation of the expected class. exception_str_match_regexes=(r'\bnumpy\.ndarray\b',), ), # NumPy array containing only 64-bit integers. HintPithUnsatisfiedMetadata( pith=asarray((4, 36, 624, 3744, 5108, 10200, 54912,)), # Match that the exception message raised for this object # embeds the representation of the expected data type. exception_str_match_regexes=(r'\bfloat64\b',), ), ), ), # Typed NumPy array subscripted by a scalar data type. Since scalar # data types are *NOT* actual data types, this exercises an edge case. HintPepMetadata( hint=NDArray[float64], pep_sign=HintSignNumpyArray, is_pep585_builtin_subscripted=IS_PYTHON_AT_LEAST_3_9, is_type_typing=False, is_typing=False, piths_meta=( # NumPy array containing only 64-bit floats. HintPithSatisfiedMetadata(asarray( (2.0, 2.5, 2.6, 2.7083, 2.716, 2.71805,))), # String constant. HintPithUnsatisfiedMetadata( pith='Silver, ore, and almost dazedly aggro‐', # Match that the exception message raised for this object # embeds the representation of the expected class. exception_str_match_regexes=(r'\bnumpy\.ndarray\b',), ), # NumPy array containing only 64-bit integers. HintPithUnsatisfiedMetadata( pith=asarray((1, 1, 1, 1, 2, 3, 6, 11, 23, 47, 106, 235,)), # Match that the exception message raised for this object # embeds the representation of the expected data type. exception_str_match_regexes=(r'\bfloat64\b',), ), ), ), # ................{ NUMPY ~ array : dtype : subclass }................ # Typed NumPy array subscripted by a data type superclass. HintPepMetadata( hint=NDArray[floating], pep_sign=HintSignNumpyArray, is_pep585_builtin_subscripted=IS_PYTHON_AT_LEAST_3_9, is_type_typing=False, is_typing=False, piths_meta=( # NumPy array containing only 32-bit floats. HintPithSatisfiedMetadata(asarray( (1.2, 2.4, 3.0, 3.6, 4.0, 4.5, 4.8, 5.6, 6.0, 6.3, 7.0,), dtype=float32, )), # NumPy array containing only 64-bit floats. HintPithSatisfiedMetadata(asarray( (3.2, 5, 1, 2, 1, 8, 2, 5, 1, 3, 1, 2.8, 1, 1.5, 1, 1, 4,), dtype=float64, )), # String constant. HintPithUnsatisfiedMetadata('Then, and'), # NumPy array containing only 64-bit integers. HintPithUnsatisfiedMetadata(asarray( (3, 6, 5, 12, 7, 18, 9, 12, 11, 30, 13, 16, 15, 18, 17,))), ), ), # ................{ NUMPY ~ array : nested }................ # 2-tuple of one-dimensional typed NumPy arrays of 64-bit floats. HintPepMetadata( hint=Tuple[Numpy1DFloat64Array, Numpy1DFloat64Array], pep_sign=HintSignTuple, isinstanceable_type=tuple, is_pep585_builtin_subscripted=Tuple is tuple, piths_meta=( # 2-tuple of NumPy arrays containing only 64-bit floats. HintPithSatisfiedMetadata(( asarray((0.5, 0.75, 0.875, 0.9375, 0.96875, 0.984375)), asarray((1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5)), )), # String constant. HintPithUnsatisfiedMetadata( pith=( "A Spherically clerical," "cylindroid‐cindered cleft, and", ), # Match that the exception message raised for this object # embeds the representation of the expected class. exception_str_match_regexes=(r'\bnumpy\.ndarray\b',), ), # 2-tuple of NumPy arrays containing only integers. HintPithUnsatisfiedMetadata( pith=( asarray((1, 1, 4, 6, 14, 23, 45, 72, 126, 195, 315,)), asarray((1, 0, 1, 1, 2, 2, 5, 4, 9, 10, 16, 19, 31,)), ), # Match that the exception message raised for this object # embeds the representation of the expected data type. exception_str_match_regexes=(r'\bfloat64\b',), ), ), ), )) # ..................{ RETURN }.................. # Return this list of all PEP-specific type hint metadata. return hints_pep_meta beartype-0.18.5/beartype_test/a00_unit/data/hint/pep/module/_data_hintmodos.py000066400000000000000000000160461461113517100273520ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' :mod:`os`-specific **PEP-noncompliant type hints** (i.e., unofficial type hints published by the standard :mod:`os` package) test data. These hints include subscriptions of: * The :class:`os.PathLike` type hint factory. Caveats ------- Although :mod:`os`-specific type hints are technically PEP-noncompliant, the :mod:`beartype` codebase currently treats these hints as PEP-compliant to dramatically simplify code generation for these hints. Ergo, so we do. ''' # ....................{ FIXTURES }.................... def hints_pep_meta_os() -> 'List[HintPepMetadata]': ''' List of :mod:`os`-specific **type hint metadata** (i.e., :class:`beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata` instances describing test-specific sample :mod:`os`-specific type hints with metadata generically leveraged by various PEP-agnostic unit tests). ''' # ..................{ IMPORTS ~ early }.................. # Defer early-time imports. from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_9 # ..................{ LOCALS }.................. # List of all module-specific type hint metadata to be returned. hints_pep_meta = [] # If the active Python interpreter targets less than Python < 3.9, this # interpreter fails to support PEP 585. In this case, return the empty list. if not IS_PYTHON_AT_LEAST_3_9: return hints_pep_meta # Else, the active Python interpreter targets at least Python >= 3.9 and # thus supports PEP 585. # ..................{ IMPORTS }.................. # Defer version-specific imports. from beartype.typing import ( Any, AnyStr, ) from beartype._data.hint.pep.sign.datapepsigns import ( HintSignPep585BuiltinSubscriptedUnknown) from beartype_test.a00_unit.data.hint.util.data_hintmetacls import ( HintPepMetadata, HintPithSatisfiedMetadata, HintPithUnsatisfiedMetadata, ) from os import PathLike from pathlib import Path # ..................{ CLASSES }.................. class PathBytes(object): # <-- get it? "Path" bytes? BITES? *har har* ''' **Bytestring path** (i.e., object satisfying the class:`os.PathLike[bytes]` protocol by defining the :meth:`__fspath__` dunder method to return a bytestring rather than string). ''' def __init__(self, path: bytes) -> None: ''' Initialize this bytestring path against the passed low-level bytestring. ''' assert isinstance(path, bytes) self._path = path def __fspath__(self) -> bytes: ''' Low-level bytestring coerced from this bytestring path. ''' return self._path # ..................{ LOCALS }.................. # Arbitrary string path unlikely to exist anywhere, thankfully. this_path_strings = Path('/home/alexis/runs/before/she/crawls/') # Arbitrary bytestring path unlikely to exist anywhere, also thankfully. this_path_bytes = PathBytes(b'/home/leycec/cackles/while/laying/low/') # ..................{ LISTS }.................. # Add module-specific type hint metadata to this list. hints_pep_meta.extend(( # ................{ PATHLIKE }................ # Object whose __fspath__() dunder method returns a string. HintPepMetadata( hint=PathLike[str], pep_sign=HintSignPep585BuiltinSubscriptedUnknown, isinstanceable_type=PathLike, is_pep585_builtin_subscripted=True, piths_meta=( # Platform-agnostic path encapsulating a string pathname. HintPithSatisfiedMetadata(this_path_strings), #FIXME: Uncomment *AFTER* deeply type-checking "PathLike[...]". # # Platform-agnostic path encapsulating a bytestring pathname. # HintPithUnsatisfiedMetadata(this_path_bytes), # String constant. HintPithUnsatisfiedMetadata( 'There came, a dream of hopes that never yet'), ), ), # Object whose __fspath__() dunder method returns a bytestring. HintPepMetadata( hint=PathLike[bytes], pep_sign=HintSignPep585BuiltinSubscriptedUnknown, isinstanceable_type=PathLike, is_pep585_builtin_subscripted=True, piths_meta=( # Platform-agnostic path encapsulating a bytestring pathname. HintPithSatisfiedMetadata(this_path_bytes), #FIXME: Uncomment *AFTER* deeply type-checking "PathLike[...]". # # Platform-agnostic path encapsulating a string pathname. # HintPithUnsatisfiedMetadata(this_path_strings), # Bytestring constant. HintPithUnsatisfiedMetadata( b'Had flushed his cheek. He dreamed a veiled maid'), ), ), # ................{ PATHLIKE ~ any }................ # Objects whose __fspath__() dunder methods return either a string *OR* # bytestring. HintPepMetadata( hint=PathLike[Any], pep_sign=HintSignPep585BuiltinSubscriptedUnknown, isinstanceable_type=PathLike, is_pep585_builtin_subscripted=True, piths_meta=( # Platform-agnostic path encapsulating a bytestring pathname. HintPithSatisfiedMetadata(this_path_bytes), # Platform-agnostic path encapsulating a string pathname. HintPithSatisfiedMetadata(this_path_bytes), # String constant. HintPithUnsatisfiedMetadata( 'Sate near him, talking in low solemn tones.'), ), ), HintPepMetadata( hint=PathLike[AnyStr], pep_sign=HintSignPep585BuiltinSubscriptedUnknown, isinstanceable_type=PathLike, is_pep585_builtin_subscripted=True, is_typevars=True, piths_meta=( # Platform-agnostic path encapsulating a bytestring pathname. HintPithSatisfiedMetadata(this_path_bytes), # Platform-agnostic path encapsulating a string pathname. HintPithSatisfiedMetadata(this_path_bytes), # Bytestring constant. HintPithUnsatisfiedMetadata( b'Her voice was like the voice of his own soul'), ), ), )) # ..................{ RETURN }.................. # Return this list of all PEP-specific type hint metadata. return hints_pep_meta beartype-0.18.5/beartype_test/a00_unit/data/hint/pep/module/_data_hintmodweakref.py000066400000000000000000000123241461113517100303500ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' :mod:`weakref`-specific **PEP-noncompliant type hints** (i.e., unofficial type hints published by the standard :mod:`weakref` package) test data. These hints include subscriptions of: * The :class:`weakref.ref` type hint factory. Caveats ------- Although :mod:`weakref`-specific type hints are technically PEP-noncompliant, the :mod:`beartype` codebase currently treats these hints as PEP-compliant to dramatically simplify code generation for these hints. Ergo, so we do. ''' # ....................{ FIXTURES }.................... def hints_pep_meta_weakref() -> 'List[HintPepMetadata]': ''' List of :mod:`weakref`-specific **type hint metadata** (i.e., :class:`beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata` instances describing test-specific sample :mod:`weakref`-specific type hints with metadata generically leveraged by various PEP-agnostic unit tests). ''' # ..................{ IMPORTS ~ early }.................. # Defer early-time imports. from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_9 # ..................{ LOCALS }.................. # List of all module-specific type hint metadata to be returned. hints_pep_meta = [] # If the active Python interpreter targets less than Python < 3.9, this # interpreter fails to support PEP 585. In this case, return the empty list. if not IS_PYTHON_AT_LEAST_3_9: return hints_pep_meta # Else, the active Python interpreter targets at least Python >= 3.9 and # thus supports PEP 585. # ..................{ IMPORTS }.................. # Defer version-specific imports. from beartype.typing import ( Any, ) from beartype._data.hint.pep.sign.datapepsigns import ( HintSignPep585BuiltinSubscriptedUnknown) from beartype_test.a00_unit.data.hint.util.data_hintmetacls import ( HintPepMetadata, HintPithSatisfiedMetadata, HintPithUnsatisfiedMetadata, ) from gc import collect from weakref import ref # ..................{ CLASSES }.................. class TheCalmOfThought(object): ''' Arbitrary class to be weakly referenced below. ''' pass class LikeWovenSounds(object): ''' Arbitrary class to *not* be weakly referenced below. ''' pass # ..................{ LOCALS }.................. # Arbitrary instances of the above classes to be weakly referenced below. heard_in = TheCalmOfThought() its_music_long = TheCalmOfThought() streams_and_breezes = LikeWovenSounds() # Weak references to these instances. heard_in_ref = ref(heard_in) its_music_long_ref = ref(its_music_long) streams_and_breezes_ref = ref(streams_and_breezes) # Delete one but *NOT* the other of these instances. del its_music_long # Trigger garbage collection and thus collection of this deleted instance. collect() # ..................{ LISTS }.................. # Add module-specific type hint metadata to this list. hints_pep_meta.extend(( # ................{ REF }................ # Weak reference to *ANY* arbitrary object. HintPepMetadata( hint=ref[Any], pep_sign=HintSignPep585BuiltinSubscriptedUnknown, isinstanceable_type=ref, is_pep585_builtin_subscripted=True, piths_meta=( # Weak reference to a living object. HintPithSatisfiedMetadata(heard_in_ref), # Weak reference to a dead object. HintPithSatisfiedMetadata(its_music_long_ref), # String constant. HintPithUnsatisfiedMetadata( 'Heard in the calm of thought; its music long,'), ), ), # Weak reference to instances of a specific type. HintPepMetadata( hint=ref[TheCalmOfThought], pep_sign=HintSignPep585BuiltinSubscriptedUnknown, isinstanceable_type=ref, is_pep585_builtin_subscripted=True, piths_meta=( # Weak reference to a living object. HintPithSatisfiedMetadata(heard_in_ref), # Weak reference to a dead object. HintPithSatisfiedMetadata(its_music_long_ref), #FIXME: Uncomment *AFTER* deeply type-checking "ref[...]". # # Weak reference to an instance of a different type. # HintPithUnsatisfiedMetadata(streams_and_breezes_ref), # String constant. HintPithUnsatisfiedMetadata( 'Like woven sounds of streams and breezes, held'), ), ), )) # ..................{ RETURN }.................. # Return this list of all PEP-specific type hint metadata. return hints_pep_meta beartype-0.18.5/beartype_test/a00_unit/data/hint/pep/proposal/000077500000000000000000000000001461113517100242075ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/hint/pep/proposal/__init__.py000066400000000000000000000000001461113517100263060ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/hint/pep/proposal/_data_pep544.py000066400000000000000000000666771461113517100267600ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`544`-compliant **type hint test data.** ''' # ....................{ TODO }.................... #FIXME: Test user-defined multiple-inherited protocols (i.e., user-defined #classes directly subclassing the "typing.Protocol" ABC and one or more other #superclasses) once @beartype supports these protocols as well. # ....................{ FIXTURES }.................... def hints_pep544_meta() -> 'List[HintPepMetadata]': ''' List of :pep:`544`-compliant **type hint metadata** (i.e., :class:`beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata` instances describing test-specific :pep:`544`-compliant sample type hints with metadata generically leveraged by various PEP-agnostic unit tests). ''' # ..................{ IMPORTS }.................. # Defer fixture-specific imports. from abc import ( ABC, abstractmethod, ) from beartype.typing import ( Any, AnyStr, runtime_checkable, ) from beartype._data.hint.datahinttyping import T from beartype._data.hint.pep.sign.datapepsigns import ( HintSignBinaryIO, HintSignGeneric, HintSignIO, HintSignTextIO, ) from beartype._util.api.utilapityping import get_typing_attrs from beartype_test.a00_unit.data.hint.util.data_hintmetacls import ( HintPepMetadata, HintPithSatisfiedMetadata, HintPithUnsatisfiedMetadata, ) from beartype_test._util.module.pytmodtest import ( is_package_beartype_vale_usable) from pathlib import Path # ..................{ LOCALS }.................. # List of all PEP-specific type hint metadata to be returned. hints_pep_meta = [] # Absolute filename of this data submodule, to be subsequently opened for # cross-platform IO testing purposes. SUBMODULE_FILENAME = __file__ # ..................{ CALLABLES }.................. def open_file_text(): ''' Function returning an open read-only file handle in text mode. ''' return open(SUBMODULE_FILENAME, 'r', encoding='utf8') def open_file_binary(): ''' Function returning an open read-only file handle in binary mode. ''' return open(SUBMODULE_FILENAME, 'rb') # List of one or more "HintPithUnsatisfiedMetadata" instances validating # objects *NOT* satisfied by either "typing.BinaryIO" *OR* # "typing.IO[bytes]". This list conditionally depends on the active Python # interpreter and thus *CANNOT* be defined as a standard tuple. binaryio_piths_meta = [ # Open read-only binary file handle to this submodule. HintPithSatisfiedMetadata( pith=open_file_binary, is_pith_factory=True), # Bytestring constant. HintPithUnsatisfiedMetadata( b"Of a thieved imagination's reveries"), ] # If beartype validators are usable under the active Python interpreter... if is_package_beartype_vale_usable(): # Add a "HintPithUnsatisfiedMetadata" instance validating open # read-only text file handles to violate both "typing.BinaryIO" *AND* # "typing.IO[bytes]" type hints. This validation requires beartype # validators, as the only means of differentiating objects satisfying # the "typing.BinaryIO" protocol from those satisfying the "typing.IO" # protocol is with an inverted instance check. Cue beartype validators. binaryio_piths_meta.append( HintPithUnsatisfiedMetadata( pith=open_file_text, is_pith_factory=True)) # ..................{ PROTOCOLS ~ structural }.................. class ProtocolCustomStructural(object): ''' User-defined class structurally (i.e., implicitly) satisfying *without* explicitly subclassing this user-defined protocol. ''' def alpha(self) -> str: return "Sufferance's humus excursion, humility’s endurance, an" def omega(self) -> str: return 'Surfeit need' # Instance of this class. protocol_custom_structural = ProtocolCustomStructural() class ProtocolSupportsInt(object): ''' User-defined protocol structurally (i.e., implicitly) satisfying *without* explicitly subclassing the predefined :class:`typing.SupportsInt` abstract base class (ABC). Note that the implementations of this and *all* other predefined :mod:`typing` protocols (e.g., :class:`typing.SupportsFloat`) bundled with older Python versions < 3.8 are *not* safely type-checkable at runtime. For safety, tests against *all* protocols including these previously predefined protocols *must* be isolated to this submodule. ''' def __int__(self) -> int: return 42 # ..................{ FACTORIES ~ Protocol }.................. # For each PEP-specific type hint factory importable from each currently # importable "typing" module... for Protocol in get_typing_attrs('Protocol'): # ................{ PROTOCOLS ~ user }................ @runtime_checkable class ProtocolCustomUntypevared(Protocol): ''' User-defined protocol parametrized by *NO* type variables declaring arbitrary concrete and abstract methods. ''' def alpha(self) -> str: return 'Of a Spicily sated' @abstractmethod def omega(self) -> str: pass @runtime_checkable class ProtocolCustomTypevared(Protocol[T]): ''' User-defined protocol parametrized by a type variable declaring arbitrary concrete and abstract methods. ''' def alpha(self) -> str: return 'Gainfully ungiving criticisms, schismatizing Ŧheo‐' @abstractmethod def omega(self) -> str: pass # ................{ PROTOCOLS ~ user : abc }................ @runtime_checkable class ProtocolCustomSuperclass(Protocol): ''' User-defined protocol superclass. ''' instance_variable: int class ProtocolCustomABC(ProtocolCustomSuperclass, ABC): ''' User-defined abstract protocol subclassing both this superclass *and* the standard abstract base class (ABC) superclass, exercising a prior issue with respect to non-trivial protocol hierarchies. See also: https://github.com/beartype/beartype/issues/117 ''' instance_variable = 42 def concrete_method(self) -> str: return ( 'Lit, Faux-Phonetician Grecian predilection derelictions ' 'predi‐sposed to' ) @abstractmethod def abstract_method(self) -> str: pass class ProtocolCustomSubclass(ProtocolCustomABC): ''' User-defined protocol subclass concretely subclassing this ABC. ''' def abstract_method(self) -> str: return 'Concrete‐shambling,' # ................{ LISTS }................ # Add PEP-specific type hint metadata to this list. hints_pep_meta.extend(( # ..............{ PROTOCOLS ~ user }.............. # Despite appearances, protocols implicitly subclass # "typing.Generic" and thus do *NOT* transparently reduce to # standard types. # # Note that the "data_pep484" submodule already exercises # predefined "typing" protocols (e.g., "typing.SupportsInt"), which # were technically introduced with PEP 484 and thus available since # Python >= 3.4 or so. # User-defined protocol parametrized by *NO* type variables. HintPepMetadata( hint=ProtocolCustomUntypevared, pep_sign=HintSignGeneric, generic_type=ProtocolCustomUntypevared, is_type_typing=False, piths_meta=( # Unrelated object satisfying this protocol. HintPithSatisfiedMetadata(protocol_custom_structural), # String constant. HintPithUnsatisfiedMetadata('For durance needs.'), ), ), # User-defined protocol parametrized by a type variable. HintPepMetadata( hint=ProtocolCustomTypevared, pep_sign=HintSignGeneric, generic_type=ProtocolCustomTypevared, is_typevars=True, is_type_typing=False, piths_meta=( # Unrelated object satisfying this protocol. HintPithSatisfiedMetadata(protocol_custom_structural), # String constant. HintPithUnsatisfiedMetadata('Machist-'), ), ), # User-defined protocol parametrized by a type variable, itself # parametrized by the same type variables in the same order. HintPepMetadata( hint=ProtocolCustomTypevared[T], pep_sign=HintSignGeneric, generic_type=ProtocolCustomTypevared, is_typevars=True, is_typing=False, piths_meta=( # Unrelated object satisfying this protocol. HintPithSatisfiedMetadata(protocol_custom_structural), # String constant. HintPithUnsatisfiedMetadata( 'Black and white‐bit, bilinear linaements'), ), ), # User-defined protocol parametrized by a type variable, itself # parametrized by a concrete type satisfying this type variable. HintPepMetadata( hint=ProtocolCustomTypevared[str], pep_sign=HintSignGeneric, generic_type=ProtocolCustomTypevared, is_typing=False, piths_meta=( # Unrelated object satisfying this protocol. HintPithSatisfiedMetadata(protocol_custom_structural), # String constant. HintPithUnsatisfiedMetadata( 'We are as clouds that veil the midnight moon;'), ), ), # User-defined abstract protocol subclassing the ABC superclass. HintPepMetadata( hint=ProtocolCustomABC, pep_sign=HintSignGeneric, generic_type=ProtocolCustomABC, is_type_typing=False, piths_meta=( # Unrelated object satisfying this protocol. HintPithSatisfiedMetadata(ProtocolCustomSubclass()), # String constant. HintPithUnsatisfiedMetadata( 'Conspiratorially oratory‐fawning faces'), ), ), )) # ..................{ FACTORIES ~ BinaryIO }.................. # For each PEP-specific type hint factory importable from each currently # importable "typing" module, add PEP-specific type hint metadata. for BinaryIO in get_typing_attrs('BinaryIO'): hints_pep_meta.append( # ..............{ GENERICS ~ io : unsubscripted }.............. # Unsubscripted "BinaryIO" abstract base class (ABC). HintPepMetadata( hint=BinaryIO, pep_sign=HintSignBinaryIO, generic_type=BinaryIO, piths_meta=binaryio_piths_meta, ) ) # ..................{ FACTORIES ~ TextIO }.................. # For each PEP-specific type hint factory importable from each currently # importable "typing" module, add PEP-specific type hint metadata. for TextIO in get_typing_attrs('TextIO'): hints_pep_meta.append( # ..............{ GENERICS ~ io : unsubscripted }.............. # Unsubscripted "TextIO" abstract base class (ABC). HintPepMetadata( hint=TextIO, pep_sign=HintSignTextIO, generic_type=TextIO, piths_meta=( # Open read-only text file handle to this submodule. HintPithSatisfiedMetadata( pith=open_file_text, is_pith_factory=True), # String constant. HintPithUnsatisfiedMetadata( 'Statistician’s anthemed meme athame'), # Open read-only binary file handle to this submodule. HintPithUnsatisfiedMetadata( pith=open_file_binary, is_pith_factory=True), ), ) ) # ..................{ FACTORIES ~ IO }.................. # For each PEP-specific type hint factory importable from each currently # importable "typing" module, add PEP-specific type hint metadata. for IO in get_typing_attrs('IO'): hints_pep_meta.extend(( # ..............{ GENERICS ~ io : unsubscripted }.............. # Unsubscripted "IO" abstract base class (ABC). HintPepMetadata( hint=IO, pep_sign=HintSignIO, generic_type=IO, is_typevars=True, piths_meta=( # Open read-only text file handle to this submodule. HintPithSatisfiedMetadata( pith=open_file_text, is_pith_factory=True), # Open read-only binary file handle to this submodule. HintPithSatisfiedMetadata( pith=open_file_binary, is_pith_factory=True), # String constant. HintPithUnsatisfiedMetadata( 'To piously magistrate, dis‐empower, and'), ), ), # ..............{ GENERICS ~ io : subscripted }.............. # All possible subscriptions of the "IO" abstract base class (ABC). HintPepMetadata( hint=IO[Any], pep_sign=HintSignIO, generic_type=IO, piths_meta=( # Open read-only binary file handle to this submodule. HintPithSatisfiedMetadata( pith=open_file_binary, is_pith_factory=True), # Open read-only text file handle to this submodule. HintPithSatisfiedMetadata( pith=open_file_text, is_pith_factory=True), # String constant. HintPithUnsatisfiedMetadata( 'Stoicly Anti‐heroic, synthetic'), ), ), HintPepMetadata( hint=IO[bytes], pep_sign=HintSignIO, generic_type=IO, piths_meta=binaryio_piths_meta, ), HintPepMetadata( hint=IO[str], pep_sign=HintSignIO, generic_type=IO, piths_meta=( # Open read-only text file handle to this submodule. HintPithSatisfiedMetadata( pith=open_file_text, is_pith_factory=True), # String constant. HintPithUnsatisfiedMetadata( 'Thism‐predestined City’s pestilentially ' 'celestial dark of' ), # Open read-only binary file handle to this submodule. HintPithUnsatisfiedMetadata( pith=open_file_binary, is_pith_factory=True), ), ), # Parametrization of the "IO" abstract base class (ABC). HintPepMetadata( hint=IO[AnyStr], pep_sign=HintSignIO, generic_type=IO, is_typevars=True, piths_meta=( # Open read-only binary file handle to this submodule. HintPithSatisfiedMetadata( pith=open_file_binary, is_pith_factory=True), # Open read-only text file handle to this submodule. HintPithSatisfiedMetadata( pith=open_file_text, is_pith_factory=True), # String constant. HintPithUnsatisfiedMetadata('Starkness'), ), ), )) # ..................{ FACTORIES ~ SupportsAbs }.................. # For each PEP-specific type hint factory importable from each currently # importable "typing" module, add PEP-specific type hint metadata. for SupportsAbs in get_typing_attrs('SupportsAbs'): hints_pep_meta.append( # ..............{ PROTOCOLS ~ supports }.............. # Unsubscripted "SupportsAbs" abstract base class (ABC). HintPepMetadata( hint=SupportsAbs, pep_sign=HintSignGeneric, generic_type=SupportsAbs, # Oddly, some but *NOT* all "typing.Supports*" ABCs are # parametrized by type variables. *shrug* is_typevars=True, piths_meta=( # Integer constant. HintPithSatisfiedMetadata(777), # <-- what can this mean!?!?!? # String constant. HintPithUnsatisfiedMetadata('Scour Our flowering'), ), ) ) # ..................{ FACTORIES ~ SupportsBytes }.................. # For each PEP-specific type hint factory importable from each currently # importable "typing" module, add PEP-specific type hint metadata. for SupportsBytes in get_typing_attrs('SupportsBytes'): hints_pep_meta.append( # ..............{ PROTOCOLS ~ supports }.............. # Unsubscripted "SupportsBytes" abstract base class (ABC). HintPepMetadata( hint=SupportsBytes, pep_sign=HintSignGeneric, generic_type=SupportsBytes, piths_meta=( # Platform-agnostic filesystem path object constant. # # Note that exceedingly few stdlib types actually define the # __bytes__() dunder method. Among the few are classes # defined by the "pathlib" module, which is why we # instantiate such an atypical class here. See also: # https://stackoverflow.com/questions/45522536/where-can-the-bytes-method-be-found HintPithSatisfiedMetadata( pith=lambda: Path('/'), is_context_manager=True, is_pith_factory=True, ), # String constant. HintPithUnsatisfiedMetadata( 'Fond suburb’s gibbet‐ribbed castrati'), ), ) #FIXME: Uncomment after we determine whether or not any stdlib #classes actually define the __complex__() dunder method. There #don't appear to be any, suggesting that the only means of testing #this would be to define a new custom "ProtocolSupportsComplex" #class as we do above for the "ProtocolSupportsInt" class. *shrug* # # Unsubscripted "SupportsComplex" abstract base class (ABC). # SupportsComplex: HintPepMetadata( # pep_sign=Generic, # piths_meta=( # # Integer constant. # 108, # # String constant. # HintPithUnsatisfiedMetadata('Fondled ΘuroƂorus-'), # ), # ), ) # ..................{ FACTORIES ~ SupportsFloat }.................. # For each PEP-specific type hint factory importable from each currently # importable "typing" module, add PEP-specific type hint metadata. for SupportsFloat in get_typing_attrs('SupportsFloat'): hints_pep_meta.append( # ..............{ PROTOCOLS ~ supports }.............. # Unsubscripted "SupportsFloat" abstract base class (ABC). HintPepMetadata( hint=SupportsFloat, pep_sign=HintSignGeneric, generic_type=SupportsFloat, piths_meta=( # Integer constant. HintPithSatisfiedMetadata(92), # String constant. HintPithUnsatisfiedMetadata('Be’yond a'), ), ) ) # ..................{ FACTORIES ~ SupportsIndex }.................. # For each PEP-specific type hint factory importable from each currently # importable "typing" module, add PEP-specific type hint metadata. for SupportsIndex in get_typing_attrs('SupportsIndex'): hints_pep_meta.append( # ..............{ PROTOCOLS ~ supports }.............. # Unsubscripted "SupportsIndex" abstract base class (ABC) first # introduced by Python 3.8.0. HintPepMetadata( hint=SupportsIndex, pep_sign=HintSignGeneric, generic_type=SupportsIndex, piths_meta=( # Integer constant. HintPithSatisfiedMetadata(29), # String constant. HintPithUnsatisfiedMetadata('Self-ishly'), ), ) ) # ..................{ FACTORIES ~ SupportsInt }.................. # For each PEP-specific type hint factory importable from each currently # importable "typing" module, add PEP-specific type hint metadata. for SupportsInt in get_typing_attrs('SupportsInt'): hints_pep_meta.append( # ..............{ PROTOCOLS ~ supports }.............. # Unsubscripted "SupportsInt" abstract base class (ABC). HintPepMetadata( hint=SupportsInt, pep_sign=HintSignGeneric, generic_type=SupportsInt, piths_meta=( # Floating-point number constant. HintPithSatisfiedMetadata(25.78), # Structurally subtyped instance. HintPithSatisfiedMetadata(ProtocolSupportsInt()), # String constant. HintPithUnsatisfiedMetadata( 'Ungentlemanly self‐righteously, and'), ), ) ) # ..................{ FACTORIES ~ SupportsRound }.................. # For each PEP-specific type hint factory importable from each currently # importable "typing" module, add PEP-specific type hint metadata. for SupportsRound in get_typing_attrs('SupportsRound'): hints_pep_meta.append( # ..............{ PROTOCOLS ~ supports }.............. # Unsubscripted "SupportsRound" abstract base class (ABC). HintPepMetadata( hint=SupportsRound, pep_sign=HintSignGeneric, generic_type=SupportsRound, # Oddly, some but *NOT* all "typing.Supports*" ABCs are # parametrized by type variables. *shrug* is_typevars=True, piths_meta=( # Floating-point number constant. HintPithSatisfiedMetadata(87.52), # String constant. HintPithUnsatisfiedMetadata( 'Our Fathers vowed, indulgently,'), ), ) ) # ..................{ RETURN }.................. # Return this list of all PEP-specific type hint metadata. return hints_pep_meta def hints_pep544_ignorable_shallow() -> list: ''' List of :pep:`544`-compliant **shallowly ignorable type hints** (i.e., ignorable on the trivial basis of their machine-readable representations). ''' # ..................{ IMPORTS }.................. from beartype._util.api.utilapityping import get_typing_attrs # ..................{ LOCALS }.................. # List of all PEP-specific shallowly ignorable type hints to be returned. hints_pep_ignorable_shallow = [] # ..................{ LISTS }.................. # For the PEP 544-specific "Protocol" superclass importable from any typing # module... for Protocol in get_typing_attrs('Protocol'): # ................{ SETS }................ # Add PEP-specific shallowly ignorable type hints to this list. hints_pep_ignorable_shallow.append( # Ignoring the "typing.Protocol" superclass is vital. For unknown # and probably uninteresting reasons, *ALL* possible objects satisfy # this superclass. Ergo, this superclass is synonymous with the # root "object" superclass: e.g., # >>> import typing as t # >>> isinstance(object(), t.Protocol) # True # <-- wat # >>> isinstance('wtfbro', t.Protocol) # True # <-- WAT # >>> isinstance(0x696969, t.Protocol) # True # <-- i'm outta here. Protocol, ) # ..................{ RETURN }.................. # Return this list. return hints_pep_ignorable_shallow def hints_pep544_ignorable_deep() -> list: ''' List of :pep:`544`-compliant **deeply ignorable type hints** (i.e., ignorable only on the non-trivial basis of their nested child type hints). ''' # ..................{ IMPORTS }.................. from beartype._util.api.utilapityping import get_typing_attrs from beartype._data.hint.datahinttyping import S, T # ..................{ LOCALS }.................. # List of all PEP-specific deeply ignorable type hints to be returned. hints_pep_ignorable_deep = [] # ..................{ LISTS }.................. # For the PEP 544-specific "Protocol" superclass importable from any typing # module... for Protocol in get_typing_attrs('Protocol'): # ................{ SETS }................ # Add PEP-specific deeply ignorable type hints to this list. hints_pep_ignorable_deep.append( # Parametrizations of the "typing.Protocol" abstract base class. Protocol[S, T], ) # ..................{ RETURN }.................. # Return this list. return hints_pep_ignorable_deep beartype-0.18.5/beartype_test/a00_unit/data/hint/pep/proposal/_data_pep585.py000066400000000000000000001772261461113517100267560ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`585`-compliant **type hint test data.** ''' # ....................{ FIXTURES }.................... def hints_pep585_meta() -> 'List[HintPepMetadata]': ''' Session-scoped fixture returning a list of :pep:`585`-compliant **type hint metadata** (i.e., :class:`beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata` instances describing test-specific :pep:`585`-compliant sample type hints with metadata generically leveraged by various PEP-agnostic unit tests). ''' # ..................{ IMPORTS ~ early }.................. # Defer early-time imports. from beartype._util.py.utilpyversion import ( IS_PYTHON_AT_MOST_3_11, IS_PYTHON_AT_LEAST_3_9, ) # ..................{ LOCALS }.................. # List of all PEP-specific type hint metadata to be returned. hints_pep_meta = [] # If the active Python interpreter targets Python < 3.9, this interpreter # fails to support PEP 585. In this case, return the empty list. if not IS_PYTHON_AT_LEAST_3_9: return hints_pep_meta # Else, the active Python interpreter targets Python >= 3.9 and thus # supports PEP 585. # ..................{ IMPORTS ~ version }.................. # Defer version-specific imports. import re from beartype.typing import ( Any, Union, ) from beartype._cave._cavefast import IntType from beartype._data.hint.datahinttyping import S, T from beartype._data.hint.pep.sign.datapepsigns import ( HintSignByteString, HintSignCallable, HintSignContextManager, HintSignDefaultDict, HintSignDict, HintSignGeneric, HintSignList, HintSignMapping, HintSignMatch, HintSignMutableMapping, HintSignMutableSequence, HintSignOrderedDict, HintSignPattern, HintSignSequence, HintSignTuple, HintSignType, ) from beartype_test.a00_unit.data.data_type import ( Class, Subclass, SubclassSubclass, OtherClass, OtherSubclass, context_manager_factory, default_dict_int_to_str, default_dict_str_to_str, ) from beartype_test.a00_unit.data.hint.util.data_hintmetacls import ( HintPepMetadata, HintPithSatisfiedMetadata, HintPithUnsatisfiedMetadata, ) from collections import ( OrderedDict, defaultdict, ) from collections.abc import ( Callable, Container, Iterable, Mapping, MutableMapping, MutableSequence, Sequence, Sized, ) from contextlib import AbstractContextManager from re import ( Match, Pattern, ) # ..................{ GENERICS ~ single }.................. # Note we intentionally do *NOT* declare unsubscripted PEP 585-compliant # generics (e.g., "class _Pep585GenericUnsubscriptedSingle(list):"). Why? # Because PEP 585-compliant generics are necessarily subscripted; when # unsubscripted, the corresponding subclasses are simply standard types. class _Pep585GenericTypevaredSingle(list[T]): ''' :pep:`585`-compliant user-defined generic subclassing a single parametrized builtin type. ''' # Redefine this generic's representation for debugging purposes. def __repr__(self) -> str: return f'{self.__class__.__name__}({super().__repr__()})' class _Pep585GenericUntypevaredShallowSingle(list[str]): ''' :pep:`585`-compliant user-defined generic subclassing a single subscripted (but unparametrized) builtin type. ''' # Redefine this generic's representation for debugging purposes. def __repr__(self) -> str: return f'{self.__class__.__name__}({super().__repr__()})' class _Pep585GenericUntypevaredDeepSingle(list[list[str]]): ''' :pep:`585`-compliant user-defined generic subclassing a single unparametrized :mod:`typing` type, itself subclassing a single unparametrized :mod:`typing` type. ''' pass # ..................{ GENERICS ~ multiple }.................. class _Pep585GenericUntypevaredMultiple( Callable, AbstractContextManager[str], Sequence[str]): ''' :pep:`585`-compliant user-defined generic subclassing multiple subscripted (but unparametrized) :mod:`collection.abc` abstract base classes (ABCs) *and* an unsubscripted :mod:`collection.abc` ABC. ''' # ................{ INITIALIZERS }................ def __init__(self, sequence: tuple) -> None: ''' Initialize this generic from the passed tuple. ''' assert isinstance(sequence, tuple), f'{repr(sequence)} not tuple.' self._sequence = sequence # ................{ ABCs }................ # Define all protocols mandated by ABCs subclassed by this generic. def __call__(self) -> int: return len(self) def __contains__(self, obj: object) -> bool: return obj in self._sequence def __enter__(self) -> object: return self def __exit__(self, *args, **kwargs) -> bool: return False def __getitem__(self, index: int) -> object: return self._sequence[index] def __iter__(self) -> bool: return iter(self._sequence) def __len__(self) -> bool: return len(self._sequence) def __reversed__(self) -> object: return self._sequence.reverse() class _Pep585GenericTypevaredShallowMultiple(Iterable[T], Container[T]): ''' :pep:`585`-compliant user-defined generic subclassing multiple directly parametrized :mod:`collections.abc` abstract base classes (ABCs). ''' # ................{ INITIALIZERS }................ def __init__(self, iterable: tuple) -> None: ''' Initialize this generic from the passed tuple. ''' assert isinstance(iterable, tuple), f'{repr(iterable)} not tuple.' self._iterable = iterable # ................{ ABCs }................ # Define all protocols mandated by ABCs subclassed by this generic. def __contains__(self, obj: object) -> bool: return obj in self._iterable def __iter__(self) -> bool: return iter(self._iterable) class _Pep585GenericTypevaredDeepMultiple( Sized, Iterable[tuple[S, T]], Container[tuple[S, T]]): ''' :pep:`585`-compliant user-defined generic subclassing multiple indirectly parametrized (but unsubscripted) :mod:`collections.abc` abstract base classes (ABCs) *and* an unsubscripted and unparametrized :mod:`collections.abc` ABC. ''' # ................{ INITIALIZERS }................ def __init__(self, iterable: tuple) -> None: ''' Initialize this generic from the passed tuple. ''' assert isinstance(iterable, tuple), f'{repr(iterable)} not tuple.' self._iterable = iterable # ................{ ABCs }................ # Define all protocols mandated by ABCs subclassed by this generic. def __contains__(self, obj: object) -> bool: return obj in self._iterable def __iter__(self) -> bool: return iter(self._iterable) def __len__(self) -> bool: return len(self._iterable) # ..................{ PRIVATE ~ forwardref }.................. # Fully-qualified classname of an arbitrary class guaranteed to be # importable. _TEST_PEP585_FORWARDREF_CLASSNAME = ( 'beartype_test.a00_unit.data.data_type.Subclass') # Arbitrary class referred to by :data:`_PEP484_FORWARDREF_CLASSNAME`. _TEST_PEP585_FORWARDREF_TYPE = Subclass # ..................{ LISTS }.................. # Add PEP-specific type hint metadata to this list. hints_pep_meta.extend(( # ................{ CALLABLE }................ # Callable accepting no parameters and returning a string. HintPepMetadata( hint=Callable[[], str], pep_sign=HintSignCallable, isinstanceable_type=Callable, is_pep585_builtin_subscripted=True, piths_meta=( # Lambda function returning a string constant. HintPithSatisfiedMetadata(lambda: 'Eudaemonia.'), # String constant. HintPithUnsatisfiedMetadata('...grant we heal'), ), ), # ................{ CONTEXTMANAGER }................ # Context manager yielding strings. HintPepMetadata( hint=AbstractContextManager[str], pep_sign=HintSignContextManager, isinstanceable_type=AbstractContextManager, is_pep585_builtin_subscripted=True, piths_meta=( # Context manager. HintPithSatisfiedMetadata( pith=lambda: context_manager_factory( 'We were mysteries, unwon'), is_context_manager=True, is_pith_factory=True, ), # String constant. HintPithUnsatisfiedMetadata('We donned apportionments'), ), ), # ................{ DICT }................ # Flat dictionary. HintPepMetadata( hint=dict[int, str], pep_sign=HintSignDict, isinstanceable_type=dict, is_pep585_builtin_subscripted=True, piths_meta=( # Dictionary mapping integer keys to string values. HintPithSatisfiedMetadata({ 1: 'For taxing', 2: "To a lax and golden‐rendered crucifixion, affix'd", }), # String constant. HintPithUnsatisfiedMetadata( 'To that beep‐prattling, LED‐ and lead-rattling crux'), ), ), # Generic dictionary. HintPepMetadata( hint=dict[S, T], pep_sign=HintSignDict, isinstanceable_type=dict, is_typevars=True, is_pep585_builtin_subscripted=True, piths_meta=( # Dictionary mapping string keys to integer values. HintPithSatisfiedMetadata({ 'Less-ons"-chastened': 2, 'Chanson': 1, }), # String constant. HintPithUnsatisfiedMetadata('Swansong.'), ), ), # ................{ GENERATOR }................ # Note that testing generators requires creating generators, which # require a different syntax to that of standard callables; ergo, # generator type hints are tested elsewhere. # ................{ GENERICS ~ single }................ # Note that PEP 585-compliant generics are *NOT* explicitly detected as # PEP 585-compliant due to idiosyncrasies in the CPython implementation # of these generics. Ergo, we intentionally do *NOT* set # "is_pep585_builtin_subscripted=True," below. # Generic subclassing a single shallowly unparametrized builtin # container type. HintPepMetadata( hint=_Pep585GenericUntypevaredShallowSingle, pep_sign=HintSignGeneric, generic_type=_Pep585GenericUntypevaredShallowSingle, is_pep585_generic=True, piths_meta=( # Subclass-specific generic list of string constants. HintPithSatisfiedMetadata( _Pep585GenericUntypevaredShallowSingle(( 'Forgive our Vocation’s vociferous publications', 'Of', )) ), # String constant. HintPithUnsatisfiedMetadata( 'Hourly sybaritical, pub sabbaticals'), # List of string constants. HintPithUnsatisfiedMetadata([ 'Materially ostracizing, itinerant‐', 'Anchoretic digimonks initiating', ]), ), ), # Generic subclassing a single deeply unparametrized builtin container # type. HintPepMetadata( hint=_Pep585GenericUntypevaredDeepSingle, pep_sign=HintSignGeneric, generic_type=_Pep585GenericUntypevaredDeepSingle, is_pep585_generic=True, piths_meta=( # Subclass-specific generic list of list of string constants. HintPithSatisfiedMetadata( _Pep585GenericUntypevaredDeepSingle([ [ 'Intravenous‐averse effigy defamations, traversing', 'Intramurally venal-izing retro-', ], [ 'Versions of a ', "Version 2.2.a‐excursioned discursive Morningrise's ravenous ad-", ], ]) ), # String constant. HintPithUnsatisfiedMetadata('Vent of'), # List of string constants. HintPithUnsatisfiedMetadata([ "Ventral‐entrailed rurality's cinder-", 'Block pluralities of', ]), # Subclass-specific generic list of string constants. HintPithUnsatisfiedMetadata( _Pep585GenericUntypevaredDeepSingle([ 'Block-house stockade stocks, trailer', 'Park-entailed central heating, though those', ]) ), ), ), # Generic subclassing a single parametrized builtin container type. HintPepMetadata( hint=_Pep585GenericTypevaredSingle, pep_sign=HintSignGeneric, generic_type=_Pep585GenericTypevaredSingle, is_pep585_generic=True, is_typevars=True, piths_meta=( # Subclass-specific generic list of string constants. HintPithSatisfiedMetadata(_Pep585GenericTypevaredSingle(( 'Pleasurable, Raucous caucuses', 'Within th-in cannon’s cynosure-ensuring refectories', ))), # String constant. HintPithUnsatisfiedMetadata( 'We there-in leather-sutured scriptured books'), # List of string constants. HintPithUnsatisfiedMetadata([ 'We laboriously let them boringly refactor', 'Of Meme‐hacked faith’s abandonment, retroactively', ]), ), ), # Generic subclassing a single parametrized builtin container, itself # parametrized by the same type variables in the same order. HintPepMetadata( hint=_Pep585GenericTypevaredSingle[S, T], pep_sign=HintSignGeneric, generic_type=_Pep585GenericTypevaredSingle, is_pep585_generic=True, is_typevars=True, piths_meta=( # Subclass-specific generic list of string constants. HintPithSatisfiedMetadata(_Pep585GenericTypevaredSingle(( 'Bandage‐managed', 'Into Faithless redaction’s didact enactment — crookedly', ))), # String constant. HintPithUnsatisfiedMetadata('Down‐bound'), # List of string constants. HintPithUnsatisfiedMetadata([ 'To prayer', 'To Ɯṙaith‐like‐upwreathed ligaments', ]), ), ), # ................{ GENERICS ~ multiple }................ # Generic subclassing multiple unparametrized "collection.abc" abstract # base class (ABCs) *AND* an unsubscripted "collection.abc" ABC. HintPepMetadata( hint=_Pep585GenericUntypevaredMultiple, pep_sign=HintSignGeneric, generic_type=_Pep585GenericUntypevaredMultiple, is_pep585_generic=True, piths_meta=( # Subclass-specific generic 2-tuple of string constants. HintPithSatisfiedMetadata(_Pep585GenericUntypevaredMultiple(( 'Into a viscerally Eviscerated eras’ meditative hallways', 'Interrupting Soul‐viscous, vile‐ly Viceroy‐insufflating', ))), # String constant. HintPithUnsatisfiedMetadata('Initiations'), # 2-tuple of string constants. HintPithUnsatisfiedMetadata(( "Into a fat mendicant’s", 'Endgame‐defendant, dedicate rants', )), ), ), # Generic subclassing multiple parametrized "collections.abc" abstract # base classes (ABCs). HintPepMetadata( hint=_Pep585GenericTypevaredShallowMultiple, pep_sign=HintSignGeneric, generic_type=_Pep585GenericTypevaredShallowMultiple, is_pep585_generic=True, is_typevars=True, piths_meta=( # Subclass-specific generic iterable of string constants. HintPithSatisfiedMetadata( _Pep585GenericTypevaredShallowMultiple(( "Of foliage's everliving antestature —", 'In us, Leviticus‐confusedly drunk', )), ), # String constant. HintPithUnsatisfiedMetadata("In Usufructose truth's"), ), ), # Generic subclassing multiple indirectly parametrized # "collections.abc" abstract base classes (ABCs) *AND* an # unparametrized "collections.abc" ABC. HintPepMetadata( hint=_Pep585GenericTypevaredDeepMultiple, pep_sign=HintSignGeneric, generic_type=_Pep585GenericTypevaredDeepMultiple, is_pep585_generic=True, is_typevars=True, piths_meta=( # Subclass-specific generic iterable of 2-tuples of string # constants. HintPithSatisfiedMetadata( _Pep585GenericTypevaredDeepMultiple(( ( 'Inertially tragicomipastoral, pastel ', 'anticandour — remanding undemanding', ), ( 'Of a', '"hallow be Thy nameless', ), )), ), # String constant. HintPithUnsatisfiedMetadata('Invitations'), ), ), # Nested list of PEP 585-compliant generics. HintPepMetadata( hint=list[_Pep585GenericUntypevaredMultiple], pep_sign=HintSignList, isinstanceable_type=list, is_pep585_builtin_subscripted=True, piths_meta=( # List of subclass-specific generic 2-tuples of string # constants. HintPithSatisfiedMetadata([ _Pep585GenericUntypevaredMultiple(( 'Stalling inevit‐abilities)', 'For carbined', )), _Pep585GenericUntypevaredMultiple(( 'Power-over (than', 'Power-with)', )), ]), # String constant. HintPithUnsatisfiedMetadata( 'that forced triforced, farcically carcinogenic Obelisks'), # List of 2-tuples of string constants. HintPithUnsatisfiedMetadata([ ( 'Obliterating their literate decency', 'Of a cannabis‐enthroning regency', ), ]), ), ), # ................{ LIST }................ # List of ignorable objects. HintPepMetadata( hint=list[object], pep_sign=HintSignList, isinstanceable_type=list, is_pep585_builtin_subscripted=True, piths_meta=( # Empty list, which satisfies all hint arguments by definition. HintPithSatisfiedMetadata([]), # List of arbitrary objects. HintPithSatisfiedMetadata([ 'Of philomathematically bliss‐postulating Seas', 'Of actuarial postponement', 23.75, ]), # String constant. HintPithUnsatisfiedMetadata( 'Of actual change elevating alleviation — that'), ), ), # List of non-"typing" objects. HintPepMetadata( hint=list[str], pep_sign=HintSignList, isinstanceable_type=list, is_pep585_builtin_subscripted=True, piths_meta=( # Empty list, which satisfies all hint arguments by definition. HintPithSatisfiedMetadata([]), # List of strings. HintPithSatisfiedMetadata([ 'Ously overmoist, ov‐ertly', 'Deverginating vertigo‐originating', ]), # String constant. HintPithUnsatisfiedMetadata('Devilet‐Sublet cities waxing'), # List containing exactly one integer. Since list items are # only randomly type-checked, only a list of exactly one item # enables us to match the explicit index at fault below. HintPithUnsatisfiedMetadata( pith=[73,], # Match that the exception message raised for this # object... exception_str_match_regexes=( # Declares the index of a random list item *NOT* # satisfying this hint. r'\b[Ll]ist index \d+ item\b', # Preserves the value of this item unquoted. r'\s73\s', ), ), ), ), # Generic list. HintPepMetadata( hint=list[T], pep_sign=HintSignList, isinstanceable_type=list, is_typevars=True, is_pep585_builtin_subscripted=True, piths_meta=( # Empty list, which satisfies all hint arguments by definition. HintPithSatisfiedMetadata([]), # List of strings. HintPithSatisfiedMetadata([ 'Lesion this ice-scioned', 'Legion', ]), # String constant. HintPithUnsatisfiedMetadata( 'Lest we succumb, indelicately, to'), ), ), # ................{ MAPPING ~ dict }................ # Dictionary of unignorable key-value pairs. HintPepMetadata( hint=dict[int, str], pep_sign=HintSignDict, isinstanceable_type=dict, is_pep585_builtin_subscripted=True, piths_meta=( # Dictionary mapping integers to strings. HintPithSatisfiedMetadata({ 1: 'For taxing', 2: "To a lax and golden‐rendered crucifixion, affix'd", }), # String constant. HintPithUnsatisfiedMetadata( 'To that beep‐prattling, LED‐ and lead-rattling crux'), # Dictionary mapping strings to strings. Since only the first # key-value pair of dictionaries are type-checked, a # dictionary of one key-value pair suffices. HintPithUnsatisfiedMetadata( pith={'Upon his cheek of death.': 'He wandered on'}, # Match that the exception message raised for this object # declares the key violating this hint. exception_str_match_regexes=( r"\bkey str 'Upon his cheek of death\.' ", ), # Match that the exception message raised for this object # does *NOT* declare the value of this key. exception_str_not_match_regexes=( r"\bvalue str 'He wandered on' ", ), ), ), ), # Dictionary of unignorable keys and ignorable values. HintPepMetadata( hint=dict[str, object], pep_sign=HintSignDict, isinstanceable_type=dict, is_pep585_builtin_subscripted=True, piths_meta=( # Dictionary mapping strings to arbitrary objects. HintPithSatisfiedMetadata({ 'Till vast Aornos,': b"seen from Petra's steep", "Hung o'er the low horizon": b'like a cloud;', }), # String constant. HintPithUnsatisfiedMetadata( 'Through Balk, and where the desolated tombs'), # Dictionary mapping bytestrings to arbitrary objects. Since # only the first key-value pair of dictionaries are # type-checked, a dictionary of one key-value pair suffices. HintPithUnsatisfiedMetadata( pith={b'Of Parthian kings': 'scatter to every wind'}, # Match that the exception message raised for this object # declares the key violating this hint. exception_str_match_regexes=( r"\bkey bytes b'Of Parthian kings' ", ), # Match that the exception message raised for this object # does *NOT* declare the value of this key. exception_str_not_match_regexes=( r"\bvalue str 'scatter to every wind' ", ), ), ), ), # Dictionary of ignorable keys and unignorable values. HintPepMetadata( hint=dict[object, str], pep_sign=HintSignDict, isinstanceable_type=dict, is_pep585_builtin_subscripted=True, piths_meta=( # Dictionary mapping arbitrary hashables to strings. HintPithSatisfiedMetadata({ 0xBEEFFADE: 'Their wasting dust, wildly he wandered on', 0xCAFEDEAF: 'Day after day a weary waste of hours,', }), # String constant. HintPithUnsatisfiedMetadata( 'Bearing within his life the brooding care'), # Dictionary mapping arbitrary hashables to bytestrings. Since # only the first key-value pair of dictionaries are # type-checked, a dictionary of one key-value pair suffices. HintPithUnsatisfiedMetadata( pith={'That ever fed on': b'its decaying flame.'}, # Match that the exception message raised for this object # declares both the key *AND* value violating this hint. exception_str_match_regexes=( r"\bkey str 'That ever fed on' ", r"\bvalue bytes b'its decaying flame\.' ", ), ), ), ), # Dictionary of ignorable key-value pairs. HintPepMetadata( hint=dict[object, object], pep_sign=HintSignDict, isinstanceable_type=dict, is_pep585_builtin_subscripted=True, piths_meta=( # Dictionary mapping arbitrary hashables to arbitrary objects. HintPithSatisfiedMetadata({ 'And now his limbs were lean;': b'his scattered hair', 'Sered by the autumn of': b'strange suffering', }), # String constant. HintPithUnsatisfiedMetadata( 'Sung dirges in the wind; his listless hand'), ), ), # Generic dictionary. HintPepMetadata( hint=dict[S, T], pep_sign=HintSignDict, isinstanceable_type=dict, is_pep585_builtin_subscripted=True, is_typevars=True, piths_meta=( # Dictionary mapping string keys to integer values. HintPithSatisfiedMetadata({ 'Less-ons"-chastened': 2, 'Chanson': 1, }), # String constant. HintPithUnsatisfiedMetadata('Swansong.'), ), ), # Nested dictionaries of tuples. HintPepMetadata( hint=dict[tuple[int, float], str], pep_sign=HintSignDict, isinstanceable_type=dict, is_pep585_builtin_subscripted=True, piths_meta=( # Dictionary mapping 2-tuples of integers and floating-point # numbers to strings. HintPithSatisfiedMetadata({ (0xBEEFBABE, 42.42): ( 'Obedient to the sweep of odorous winds'), }), # String constant. HintPithUnsatisfiedMetadata( 'Upon resplendent clouds, so rapidly'), # Dictionary mapping 2-tuples of integers and floating-point # numbers to byte strings. HintPithUnsatisfiedMetadata( pith={ (0xBABEBEEF, 24.24): ( b'Along the dark and ruffled waters fled'), }, # Match that the exception message raised for this object # declares all key-value pairs on the path to the value # violating this hint. exception_str_match_regexes=( r'\bkey tuple \(3133062895, 24.24\)', r"\bvalue bytes b'Along the dark and ruffled waters fled'", ), ), ), ), # Nested dictionaries of nested dictionaries of... you get the idea. HintPepMetadata( hint=dict[int, Mapping[str, MutableMapping[bytes, bool]]], pep_sign=HintSignDict, isinstanceable_type=dict, is_pep585_builtin_subscripted=True, piths_meta=( # Dictionary mapping integers to dictionaries mapping strings to # dictionaries mapping bytes to booleans. HintPithSatisfiedMetadata({ 1: { 'Beautiful bird;': { b'thou voyagest to thine home,': False, }, }, }), # String constant. HintPithUnsatisfiedMetadata( 'Where thy sweet mate will twine her downy neck'), # Dictionary mapping integers to dictionaries mapping strings to # dictionaries mapping bytes to integers. Since only the first # key-value pair of dictionaries are type-checked, dictionaries # of one key-value pairs suffice. HintPithUnsatisfiedMetadata( pith={ 1: { 'With thine,': { b'and welcome thy return with eyes': 1, }, }, }, # Match that the exception message raised for this object # declares all key-value pairs on the path to the value # violating this hint. exception_str_match_regexes=( r'\bkey int 1\b', r"\bkey str 'With thine,' ", r"\bkey bytes b'and welcome thy return with eyes' ", r"\bvalue int 1\b", ), ), ), ), # ................{ MAPPING ~ defaultdict }................ # Default dictionary of unignorable key-value pairs. HintPepMetadata( hint=defaultdict[int, str], pep_sign=HintSignDefaultDict, isinstanceable_type=defaultdict, is_pep585_builtin_subscripted=True, piths_meta=( # Default dictionary mapping integers to strings. HintPithSatisfiedMetadata(default_dict_int_to_str), # String constant. HintPithUnsatisfiedMetadata('High over the immeasurable main.'), # Ordered dictionary mapping strings to strings. Since only the # first key-value pair of dictionaries are type-checked, a # dictionary of one key-value pair suffices. HintPithUnsatisfiedMetadata( pith=default_dict_str_to_str, # Match that the exception message raised for this object # declares the key violating this hint. exception_str_match_regexes=( r"\bkey str 'His eyes pursued its flight\.' ", ), # Match that the exception message raised for this object # does *NOT* declare the value of this key. exception_str_not_match_regexes=( r"\bvalue str 'Thou hast a home,' ", ), ), ), ), # ................{ MAPPING ~ mapping }................ # Mapping of unignorable key-value pairs. HintPepMetadata( hint=Mapping[int, str], pep_sign=HintSignMapping, isinstanceable_type=Mapping, is_pep585_builtin_subscripted=True, piths_meta=( # Dictionary mapping integers to strings. HintPithSatisfiedMetadata({ 1: 'Who ministered with human charity', 2: 'His human wants, beheld with wondering awe', }), # String constant. HintPithUnsatisfiedMetadata( 'Their fleeting visitant. The mountaineer,'), # Dictionary mapping strings to strings. Since only the first # key-value pair of dictionaries are type-checked, a # dictionary of one key-value pair suffices. HintPithUnsatisfiedMetadata( pith={'Encountering on': 'some dizzy precipice'}, # Match that the exception message raised for this object # declares the key violating this hint. exception_str_match_regexes=( r"\bkey str 'Encountering on' ", ), # Match that the exception message raised for this object # does *NOT* declare the value of this key. exception_str_not_match_regexes=( r"\bvalue str 'some dizzy precipice' ", ), ), ), ), # ................{ MAPPING ~ mutablemapping }................ # Mapping of unignorable key-value pairs. HintPepMetadata( hint=MutableMapping[int, str], pep_sign=HintSignMutableMapping, isinstanceable_type=MutableMapping, is_pep585_builtin_subscripted=True, piths_meta=( # Dictionary mapping integers to strings. HintPithSatisfiedMetadata({ 1: "His troubled visage in his mother's robe", 2: 'In terror at the glare of those wild eyes,', }), # String constant. HintPithUnsatisfiedMetadata( 'To remember their strange light in many a dream'), # Dictionary mapping strings to strings. Since only the first # key-value pair of dictionaries are type-checked, a # dictionary of one key-value pair suffices. HintPithUnsatisfiedMetadata( pith={'Of after-times;': 'but youthful maidens, taught'}, # Match that the exception message raised for this object # declares the key violating this hint. exception_str_match_regexes=( r"\bkey str 'Of after-times;' ", ), # Match that the exception message raised for this object # does *NOT* declare the value of this key. exception_str_not_match_regexes=( r"\bvalue str 'but youthful maidens, taught' ", ), ), ), ), # ................{ MAPPING ~ ordereddict }................ # Ordered dictionary of unignorable key-value pairs. HintPepMetadata( hint=OrderedDict[int, str], pep_sign=HintSignOrderedDict, isinstanceable_type=OrderedDict, is_pep585_builtin_subscripted=True, piths_meta=( # Ordered dictionary mapping integers to strings. HintPithSatisfiedMetadata(OrderedDict({ 1: "Of his departure from their father's door.", 2: 'At length upon the lone Chorasmian shore', })), # String constant. HintPithUnsatisfiedMetadata( 'He paused, a wide and melancholy waste'), # Ordered dictionary mapping strings to strings. Since only the # first key-value pair of dictionaries are type-checked, a # dictionary of one key-value pair suffices. HintPithUnsatisfiedMetadata( pith=OrderedDict({ 'Of putrid marshes.': 'A strong impulse urged'}), # Match that the exception message raised for this object # declares the key violating this hint. exception_str_match_regexes=( r"\bkey str 'Of putrid marshes\.' ", ), # Match that the exception message raised for this object # does *NOT* declare the value of this key. exception_str_not_match_regexes=( r"\bvalue str 'A strong impulse urged' ", ), ), ), ), # ................{ REGEX ~ match }................ # Regular expression match of only strings. HintPepMetadata( hint=Match[str], pep_sign=HintSignMatch, isinstanceable_type=Match, is_pep585_builtin_subscripted=True, piths_meta=( # Regular expression match of one or more string constants. HintPithSatisfiedMetadata(re.search( r'\b[a-z]+itiat[a-z]+\b', 'Vitiating novitiate Succubæ – a', )), # String constant. HintPithUnsatisfiedMetadata('Into Elitistly'), ), ), # ................{ REGEX ~ pattern }................ # Regular expression pattern of only strings. HintPepMetadata( hint=Pattern[str], pep_sign=HintSignPattern, isinstanceable_type=Pattern, is_pep585_builtin_subscripted=True, piths_meta=( # Regular expression string pattern. HintPithSatisfiedMetadata( re.compile(r'\b[A-Z]+ITIAT[A-Z]+\b')), # String constant. HintPithUnsatisfiedMetadata('Obsessing men'), ), ), # ................{ SUBCLASS }................ # Any type, semantically equivalent under PEP 484 to the unsubscripted # "Type" singleton. HintPepMetadata( hint=type[Any], pep_sign=HintSignType, isinstanceable_type=type, is_pep585_builtin_subscripted=True, piths_meta=( # Arbitrary class. HintPithSatisfiedMetadata(float), # String constant. HintPithUnsatisfiedMetadata('Coulomb‐lobed lobbyist’s Ģom'), ), ), # "type" superclass, semantically equivalent to the unsubscripted # "Type" singleton. HintPepMetadata( hint=type[type], pep_sign=HintSignType, isinstanceable_type=type, is_pep585_builtin_subscripted=True, piths_meta=( # Arbitrary class. HintPithSatisfiedMetadata(complex), # String constant. HintPithUnsatisfiedMetadata('Had al-'), ), ), # Specific class. HintPepMetadata( hint=type[Class], pep_sign=HintSignType, isinstanceable_type=type, is_pep585_builtin_subscripted=True, piths_meta=( # Subclass of this class. HintPithSatisfiedMetadata(Subclass), # String constant. HintPithUnsatisfiedMetadata('Namely,'), # Non-subclass of this class. HintPithUnsatisfiedMetadata(str), ), ), # Specific class deferred with a forward reference. HintPepMetadata( hint=type[_TEST_PEP585_FORWARDREF_CLASSNAME], pep_sign=HintSignType, isinstanceable_type=type, is_pep585_builtin_subscripted=True, piths_meta=( # Subclass of this class. HintPithSatisfiedMetadata(SubclassSubclass), # String constant. HintPithUnsatisfiedMetadata('Jabbar‐disbarred'), # Non-subclass of this class. HintPithUnsatisfiedMetadata(dict), ), ), # Two or more specific classes. HintPepMetadata( hint=type[Union[Class, OtherClass,]], pep_sign=HintSignType, isinstanceable_type=type, is_pep585_builtin_subscripted=True, piths_meta=( # Arbitrary subclass of one class subscripting this hint. HintPithSatisfiedMetadata(Subclass), # Arbitrary subclass of another class subscripting this hint. HintPithSatisfiedMetadata(OtherSubclass), # String constant. HintPithUnsatisfiedMetadata('Jabberings'), # Non-subclass of any classes subscripting this hint. HintPithUnsatisfiedMetadata(set), ), ), # Generic class. HintPepMetadata( hint=type[T], pep_sign=HintSignType, isinstanceable_type=type, is_pep585_builtin_subscripted=True, is_typevars=True, piths_meta=( # Arbitrary class. HintPithSatisfiedMetadata(int), # String constant. HintPithUnsatisfiedMetadata('Obligation, and'), ), ), # ................{ TUPLE ~ fixed }................ # Empty tuple. Yes, this is ridiculous, useless, and non-orthogonal # with standard sequence syntax, which supports no comparable notion of # an "empty {insert-standard-sequence-here}" (e.g., empty list): e.g., # >>> import typing # >>> List[()] # TypeError: Too few parameters for List; actual 0, expected 1 # >>> List[[]] # TypeError: Parameters to generic types must be types. Got []. HintPepMetadata( hint=tuple[()], pep_sign=HintSignTuple, isinstanceable_type=tuple, is_pep585_builtin_subscripted=True, piths_meta=( # Empty tuple. HintPithSatisfiedMetadata(()), # Non-empty tuple containing arbitrary items. HintPithUnsatisfiedMetadata( pith=( 'They shucked', '(Or huckstered, knightly rupturing veritas)', ), # Match that the raised exception message... exception_str_match_regexes=( # Identifies this tuple as non-empty. r'\bnon-empty\b', ), ), ), ), # Fixed-length tuple of only ignorable child hints. HintPepMetadata( hint=tuple[Any, object,], pep_sign=HintSignTuple, isinstanceable_type=tuple, is_pep585_builtin_subscripted=True, piths_meta=( # Tuple containing arbitrary items. HintPithSatisfiedMetadata(( 'Surseance', 'Of sky, the God, the surly', )), # Tuple containing fewer items than required. HintPithUnsatisfiedMetadata( pith=('Obeisance',), # Match that the raised exception message... exception_str_match_regexes=( # Compares this tuple's length to the expected length. r'\b1 != 2\b', ), ), ), ), # Fixed-length tuple of at least one ignorable child hint. HintPepMetadata( hint=tuple[float, Any, str,], pep_sign=HintSignTuple, isinstanceable_type=tuple, is_pep585_builtin_subscripted=True, piths_meta=( # Tuple containing a floating-point number, string, and integer # (in that exact order). HintPithSatisfiedMetadata(( 20.09, 'Of an apoptosic T.A.R.P.’s torporific‐riven ecocide', "Nightly tolled, pindololy, ol'", )), # String constant. HintPithUnsatisfiedMetadata( 'Jangling (brinkmanship “Ironside”) jingoisms'), # Tuple containing fewer items than required. HintPithUnsatisfiedMetadata( pith=( 999.888, 'Obese, slipshodly muslin‐shod priests had maudlin solo', ), # Match that the raised exception message... exception_str_match_regexes=( # Compares this tuple's length to the expected length. r'\b2 != 3\b', ), ), # Tuple containing a floating-point number, a string, and a # boolean (in that exact order). HintPithUnsatisfiedMetadata( pith=( 75.83, 'Unwholesome gentry ventings', False, ), # Match that the raised exception message... exception_str_match_regexes=( # Declares the index and expected type of a fixed tuple # item *NOT* satisfying this hint. r'\b[Tt]uple index 2 item\b', r'\bstr\b', ), ), ), ), # Nested fixed-length tuple of at least one ignorable child hint. HintPepMetadata( hint=tuple[tuple[float, Any, str,], ...], pep_sign=HintSignTuple, isinstanceable_type=tuple, is_pep585_builtin_subscripted=True, piths_meta=( # Tuple containing tuples containing a floating-point number, # string, and integer (in that exact order). HintPithSatisfiedMetadata(( ( 90.02, 'Father — "Abstracted, OH WE LOVE YOU', 'Farther" — that', ), ( 2.9, 'To languidly Ent‐wine', 'Towards a timely, wines‐enticing gate', ), )), # Tuple containing a tuple containing fewer items than needed. HintPithUnsatisfiedMetadata(( ( 888.999, 'Oboes‐obsoleting tines', ), )), # Tuple containing a tuple containing a floating-point number, # string, and boolean (in that exact order). HintPithUnsatisfiedMetadata( pith=( ( 75.83, 'Vespers’ hymnal seance, invoking', True, ), ), # Match that the raised exception message... exception_str_match_regexes=( # Declares the index and expected type of a random # tuple item of a fixed tuple item *NOT* satisfying # this hint. r'\b[Tt]uple index \d+ item tuple index 2 item\b', r'\bstr\b', ), ), ), ), # Generic fixed-length tuple. HintPepMetadata( hint=tuple[S, T], pep_sign=HintSignTuple, isinstanceable_type=tuple, is_pep585_builtin_subscripted=True, is_typevars=True, piths_meta=( # Tuple containing a floating-point number and string (in that # exact order). HintPithSatisfiedMetadata(( 33.77, 'Legal indiscretions', )), # String constant. HintPithUnsatisfiedMetadata('Leisurely excreted by'), # Tuple containing fewer items than required. HintPithUnsatisfiedMetadata(( 'Market states‐created, stark abscess', )), ), ), # ................{ TUPLE ~ variadic }................ # Variadic tuple. HintPepMetadata( hint=tuple[str, ...], pep_sign=HintSignTuple, isinstanceable_type=tuple, is_pep585_builtin_subscripted=True, piths_meta=( # Tuple containing arbitrarily many string constants. HintPithSatisfiedMetadata(( 'Of a scantly raptured Overture,' 'Ur‐churlishly', )), # String constant. HintPithUnsatisfiedMetadata( 'Of Toll‐descanted grant money'), # Tuple containing exactly one integer. Since tuple items are # only randomly type-checked, only a tuple of exactly one item # enables us to match the explicit index at fault below. HintPithUnsatisfiedMetadata( pith=((53,)), # Match that the raised exception message... exception_str_match_regexes=( # Declares the index and expected type of this tuple's # problematic item. r'\b[Tt]uple index 0 item\b', r'\bstr\b', ), ), ), ), # Generic variadic tuple. HintPepMetadata( hint=tuple[T, ...], pep_sign=HintSignTuple, isinstanceable_type=tuple, is_pep585_builtin_subscripted=True, is_typevars=True, piths_meta=( # Tuple containing arbitrarily many string constants. HintPithSatisfiedMetadata(( 'Loquacious s‐age, salaciously,', 'Of regal‐seeming, freemen‐sucking Hovels, a', )), # String constant. HintPithUnsatisfiedMetadata( 'Concubine enthralling contractually novel'), ), ), # ................{ UNION ~ nested }................ # Nested unions exercising edge cases induced by Python >= 3.8 # optimizations leveraging PEP 572-style assignment expressions. # Nested union of multiple non-"typing" types. HintPepMetadata( hint=list[Union[int, str,]], pep_sign=HintSignList, isinstanceable_type=list, is_pep585_builtin_subscripted=True, piths_meta=( # List containing a mixture of integer and string constants. HintPithSatisfiedMetadata([ 'Un‐seemly preening, pliant templar curs; and', 272, ]), # String constant. HintPithUnsatisfiedMetadata( pith='Un‐seemly preening, pliant templar curs; and', # Match that the exception message raised for this object # declares the types *NOT* satisfied by this object. exception_str_match_regexes=( r'\bint\b', r'\bstr\b', ), # Match that the exception message raised for this object # does *NOT* contain a newline or bullet delimiter. exception_str_not_match_regexes=( r'\n', r'\*', ), ), # List of bytestring items. HintPithUnsatisfiedMetadata( pith=[ b'Blamelessly Slur-chastened rights forthwith, affrighting', b"Beauty's lurid, beleaguered knolls, eland-leagued and", ], # Match that the exception message raised for this # object... exception_str_match_regexes=( # Declares all non-"typing" types *NOT* satisfied by a # random list item *NOT* satisfying this hint. r'\bint\b', r'\bstr\b', # Declares the index of the random list item *NOT* # satisfying this hint. r'\b[Ll]ist index \d+ item\b', ), ), ), ), # Nested union of one non-"typing" type and one "typing" type. HintPepMetadata( hint=Sequence[Union[str, bytes]], pep_sign=HintSignSequence, isinstanceable_type=Sequence, is_pep585_builtin_subscripted=True, piths_meta=( # Sequence of string and bytestring constants. HintPithSatisfiedMetadata(( b'For laconically formulaic, knavish,', u'Or sordidly sellsword‐', f'Horded temerities, bravely unmerited', )), # Integer constant. HintPithUnsatisfiedMetadata( pith=7898797, # Match that the exception message raised for this object # declares the types *NOT* satisfied by this object. exception_str_match_regexes=( r'\bbytes\b', r'\bstr\b', ), # Match that the exception message raised for this object # does *NOT* contain a newline or bullet delimiter. exception_str_not_match_regexes=( r'\n', r'\*', ), ), # Sequence of integer items. HintPithUnsatisfiedMetadata( pith=((144, 233, 377, 610, 987, 1598, 2585, 4183, 6768,)), # Match that the exception message raised for this # object... exception_str_match_regexes=( # Declares all non-"typing" types *NOT* satisfied by a # random tuple item *NOT* satisfying this hint. r'\bbytes\b', r'\bstr\b', # Declares the index of the random tuple item *NOT* # satisfying this hint. r'\b[Tt]uple index \d+ item\b', ), ), ), ), # Nested union of no non-"typing" type and multiple "typing" types. HintPepMetadata( hint=MutableSequence[Union[bytes, Callable]], pep_sign=HintSignMutableSequence, isinstanceable_type=MutableSequence, is_pep585_builtin_subscripted=True, piths_meta=( # Mutable sequence of string and bytestring constants. HintPithSatisfiedMetadata([ b"Canonizing Afrikaans-kennelled Mine canaries,", lambda: 'Of a floridly torrid, hasty love — that league', ]), # String constant. HintPithUnsatisfiedMetadata( pith='Effaced.', # Match that the exception message raised for this object # declares the types *NOT* satisfied by this object. exception_str_match_regexes=( r'\bbytes\b', r'\bCallable\b', ), # Match that the exception message raised for this object # does *NOT* contain a newline or bullet delimiter. exception_str_not_match_regexes=( r'\n', r'\*', ), ), # Mutable sequence of string constants. HintPithUnsatisfiedMetadata( pith=[ 'Of genteel gentle‐folk — that that Ƹsper', 'At my brand‐defaced, landless side', ], # Match that the exception message raised for this # object... exception_str_match_regexes=( # Declares all non-"typing" types *NOT* satisfied by a # random list item *NOT* satisfying this hint. r'\bbytes\b', r'\bCallable\b', # Declares the index of the random list item *NOT* # satisfying this hint. r'\b[Ll]ist index \d+ item\b', ), ), ), ), )) # ....................{ VERSION }.................... # PEP-compliant type hints conditionally dependent on the major version of # Python targeted by the active Python interpreter. # If the active Python interpreter targets at most Python <= 3.11... if IS_PYTHON_AT_MOST_3_11: # ..................{ IMPORTS }.................. # Defer importation of standard abstract base classes (ABCs) deprecated # under Python >= 3.12. from collections.abc import ByteString # ..................{ LISTS }.................. # Add PEP-specific type hint metadata to this list. hints_pep_meta.extend(( # ................{ BYTESTRING }................ # Byte string of integer constants satisfying the builtin "int" # type. However, note that: # * *ALL* byte strings necessarily contain only integer constants, # regardless of whether those byte strings are instantiated as # "bytes" or "bytearray" instances. Ergo, subscripting # "collections.abc.ByteString" by any class other than those # satisfying the standard integer protocol raises a runtime # error from @beartype. Yes, this means that subscripting # "collections.abc.ByteString" conveys no information and is thus # nonsensical. Welcome to PEP 585. # * Python >= 3.12 provides *NO* corresponding analogue. Oddly, # neither the builtin "bytes" type *NOR* the newly introduced # "collections.abc.Buffer" ABC are subscriptable under Python >= # 3.12 despite both roughly corresponding to the deprecated # "collections.abc.ByteString" ABC. Notably: # $ python3.12 # >>> bytes[str] # TypeError: type 'bytes' is not subscriptable # # >>> from collections.abc import Buffer # >>> Buffer[str] # TypeError: type 'Buffer' is not subscriptable HintPepMetadata( hint=ByteString[int], pep_sign=HintSignByteString, isinstanceable_type=ByteString, is_pep585_builtin_subscripted=True, piths_meta=( # Byte string constant. HintPithSatisfiedMetadata(b'Ingratiatingly'), # String constant. HintPithUnsatisfiedMetadata('For an Ǽeons’ æon.'), ), ), # Byte string of integer constants satisfying the stdlib # "numbers.Integral" protocol. HintPepMetadata( hint=ByteString[IntType], pep_sign=HintSignByteString, isinstanceable_type=ByteString, is_pep585_builtin_subscripted=True, piths_meta=( # Byte array initialized from a byte string constant. HintPithSatisfiedMetadata(bytearray(b'Cutting Wit')), # String constant. HintPithUnsatisfiedMetadata( 'Of birch‐rut, smut‐smitten papers and'), ), ), )) # ..................{ RETURN }.................. # Return this list of all PEP-specific type hint metadata. return hints_pep_meta beartype-0.18.5/beartype_test/a00_unit/data/hint/pep/proposal/_data_pep586.py000066400000000000000000000411431461113517100267430ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`586`-compliant **type hint test data.** ''' # ....................{ FIXTURES }.................... def hints_pep586_meta() -> 'List[HintPepMetadata]': ''' Session-scoped fixture returning a list of :pep:`586`-compliant **type hint metadata** (i.e., :class:`beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata` instances describing test-specific :pep:`586`-compliant sample type hints with metadata generically leveraged by various PEP-agnostic unit tests). ''' # ..................{ IMPORTS }.................. # Defer fixture-specific imports. from beartype.typing import List from beartype._data.hint.pep.sign.datapepsigns import ( HintSignList, HintSignLiteral, ) from beartype._util.api.utilapityping import get_typing_attrs from beartype_test.a00_unit.data.data_type import ( MasterlessDecreeVenomlessWhich) from beartype_test.a00_unit.data.hint.util.data_hintmetacls import ( HintPepMetadata, HintPithSatisfiedMetadata, HintPithUnsatisfiedMetadata, ) # ..................{ LOCALS }.................. # List of all PEP-specific type hint metadata to be returned. hints_pep_meta = [] # ..................{ FACTORIES }.................. # For each PEP-specific type hint factory importable from each currently # importable "typing" module... for Literal in get_typing_attrs('Literal'): # Add PEP 586-specific test type hints to this tuple global. hints_pep_meta.extend(( # ..............{ LITERALS }.............. # Literal "None" singleton. Look, this is ridiculous. What you do? HintPepMetadata( hint=Literal[None], pep_sign=HintSignLiteral, is_args=True, piths_meta=( # "None" singleton defined by the same syntax. HintPithSatisfiedMetadata(None), # "None" singleton defined by different syntax but # semantically equal to the "None" singleton. HintPithSatisfiedMetadata( {}.get('Looting Uncouth, ruddy Bȴood and')), # String constant. HintPithUnsatisfiedMetadata( pith='Worthily untrust-', # Match that the exception message raised for this # object embeds the representation of the expected # type. exception_str_match_regexes=(r'\bNone\b',), ), ), ), # Literal arbitrary boolean. (Not that there are many of those...) HintPepMetadata( hint=Literal[True], pep_sign=HintSignLiteral, is_args=True, piths_meta=( # Boolean constant defined by the same syntax. HintPithSatisfiedMetadata(True), # Boolean constant defined by different syntax but # semantically equal to the same boolean. HintPithSatisfiedMetadata(__name__ is __name__), # Boolean constant *NOT* equal to the same boolean. HintPithUnsatisfiedMetadata( pith=False, # Match that the exception message raised for this # object embeds the representation of the expected # literal. exception_str_match_regexes=(r'\bTrue\b',), ), # Integer constant semantically equal to the same boolean # but of a differing type. HintPithUnsatisfiedMetadata( pith=1, # Match that the exception message raised for this # object embeds the representation of the expected # type. exception_str_match_regexes=(r'\bbool\b',), ), ), ), # Literal arbitrary integer. HintPepMetadata( hint=Literal[0x2a], pep_sign=HintSignLiteral, is_args=True, piths_meta=( # Integer constant defined by the same syntax. HintPithSatisfiedMetadata(0x2a), # Integer constant defined by different syntax but # semantically equal to the same integer. HintPithSatisfiedMetadata(42), # Integer constant *NOT* equal to the same integer. HintPithUnsatisfiedMetadata( pith=41, # Match that the exception message raised for this # object embeds the representation of the expected # literal. exception_str_match_regexes=(r'\b42\b',), ), # Floating-point constant semantically equal to the same # integer but of a differing type. HintPithUnsatisfiedMetadata( pith=42.0, # Match that the exception message raised for this # object embeds the representation of the expected # type. exception_str_match_regexes=(r'\bint\b',), ), ), ), # Literal arbitrary byte string. HintPepMetadata( hint=Literal[ b"Worthy, 'vain truthiness of (very invective-elected)"], pep_sign=HintSignLiteral, is_args=True, piths_meta=( # Byte string constant defined by the same syntax. HintPithSatisfiedMetadata( b"Worthy, 'vain truthiness of (very invective-elected)"), # Byte string constant defined by different syntax but # semantically equal to the same byte string. HintPithSatisfiedMetadata( b"Worthy, 'vain truthiness of " b"(very invective-elected)" ), # Byte string constant *NOT* equal to the same byte string. HintPithUnsatisfiedMetadata( pith=b"Thanes within", # Match that the exception message raised for this # object embeds the representation of the expected # literal. exception_str_match_regexes=(r'\btruthiness\b',), ), # Unicode string constant semantically equal to the same # byte string but of a differing type. HintPithUnsatisfiedMetadata( pith=( "Worthy, 'vain truthiness of " "(very invective-elected)" ), # Match that the exception message raised for this # object embeds the representation of the expected # type. exception_str_match_regexes=(r'\bbytes\b',), ), ), ), # Literal arbitrary Unicode string. HintPepMetadata( hint=Literal['Thanklessly classed, nominal'], pep_sign=HintSignLiteral, is_args=True, piths_meta=( # Unicode string constant defined by the same syntax. HintPithSatisfiedMetadata('Thanklessly classed, nominal'), # Unicode string constant defined by different syntax but # semantically equal to the same Unicode string. HintPithSatisfiedMetadata( 'Thanklessly classed, ' 'nominal' ), # Unicode string constant *NOT* equal to the same string. HintPithUnsatisfiedMetadata( pith='Mass and', # Match that the exception message raised for this # object embeds the representation of the expected # literal. exception_str_match_regexes=(r'\bnominal\b',), ), # Byte string constant semantically equal to the same # Unicode string but of a differing type. HintPithUnsatisfiedMetadata( pith=b'Thanklessly classed, nominal', # Match that the exception message raised for this # object embeds the representation of the expected # type. exception_str_match_regexes=(r'\bstr\b',), ), ), ), # Literal arbitrary enumeration member. HintPepMetadata( hint=Literal[ MasterlessDecreeVenomlessWhich. NOMENCLATURE_WEATHER_VANES_OF ], pep_sign=HintSignLiteral, is_args=True, piths_meta=( # Enumeration member accessed by the same syntax. HintPithSatisfiedMetadata( MasterlessDecreeVenomlessWhich. NOMENCLATURE_WEATHER_VANES_OF ), # Enumeration member accessed by different syntax but # semantically equal to the same enumeration member. HintPithSatisfiedMetadata( MasterlessDecreeVenomlessWhich(0)), # Enumeration member *NOT* equal to the same member. HintPithUnsatisfiedMetadata( pith=( MasterlessDecreeVenomlessWhich. NOMINALLY_UNSWAIN_AUTODIDACTIC_IDIOCRACY_LESS_A ), # Match that the exception message raised for this # object embeds the representation of the expected # literal. exception_str_match_regexes=( r'\bNOMENCLATURE_WEATHER_VANES_OF\b',), ), # Integer constant semantically equal to the same index of # this enumeration member but of a differing type. HintPithUnsatisfiedMetadata( pith=0, # Match that the exception message raised for this # object embeds the representation of the expected # type. exception_str_match_regexes=( r'\bMasterlessDecreeVenomlessWhich\b',), ), ), ), # ..............{ LITERALS ~ nested }.............. # List of literal arbitrary Unicode strings. HintPepMetadata( hint=List[Literal[ 'ç‐omically gnomical whitebellied burden’s empathy of']], pep_sign=HintSignList, isinstanceable_type=list, piths_meta=( # List of Unicode string constants semantically equal to # the same Unicode string. HintPithSatisfiedMetadata([ 'ç‐omically gnomical whitebellied burden’s empathy of', ( 'ç‐omically gnomical ' 'whitebellied burden’s ' 'empathy of' ), ]), # List of Unicode string constants *NOT* equal to the same # Unicode string. HintPithUnsatisfiedMetadata( pith=[ 'Earpiece‐piecemealed, mealy straw headpiece‐', 'Earned peace appeasement easements', ], # Match that the exception message raised for this # object embeds the representation of the expected # literal. exception_str_match_regexes=(r'\bgnomical\b',), ), # List of byte string constants. HintPithUnsatisfiedMetadata( pith=[ b'Than', b"Thankful strumpet's", ], # Match that the exception message raised for this # object embeds the representation of the expected # type. exception_str_match_regexes=(r'\bstr\b',), ), ), ), # ..............{ LITERALS ~ union }.............. # Literal union of two or more arbitrary literal objects. HintPepMetadata( hint=Literal[ None, True, 0x2a, b"Worthy, 'vain truthiness of (very invective-elected)", 'Thanklessly classed, nominal', ( MasterlessDecreeVenomlessWhich. NOMENCLATURE_WEATHER_VANES_OF ), ], pep_sign=HintSignLiteral, is_args=True, piths_meta=( # Literal objects subscripting this literal union. HintPithSatisfiedMetadata(None), HintPithSatisfiedMetadata(True), HintPithSatisfiedMetadata(0x2a), HintPithSatisfiedMetadata( b"Worthy, 'vain truthiness of (very invective-elected)" ), HintPithSatisfiedMetadata('Thanklessly classed, nominal'), HintPithSatisfiedMetadata( MasterlessDecreeVenomlessWhich. NOMENCLATURE_WEATHER_VANES_OF ), # Arbitrary object of the same type as one or more literal # objects subscripting this literal union but unequal to # any objects subscripting this literal union. HintPithUnsatisfiedMetadata( pith='Empirism‐Tṙumpeted,', # Match that the exception message raised for this # object embeds the representation of all expected # literals. exception_str_match_regexes=( r'\bNone\b', r'\bTrue\b', r'\b42\b', r'\btruthiness\b', r'\bnominal\b', r'\bNOMENCLATURE_WEATHER_VANES_OF\b', ), ), # Arbitrary object of a differing type from all literal # objects subscripting this literal union. HintPithUnsatisfiedMetadata( pith=42.0, # Match that the exception message raised for this # object embeds the representation of all expected # types. exception_str_match_regexes=( r'\bNone\b', r'\bbool\b', r'\bint\b', r'\bbytes\b', r'\bstr\b', r'\bMasterlessDecreeVenomlessWhich\b', ), ), ), ), )) # ..................{ RETURN }.................. # Return this list of all PEP-specific type hint metadata. return hints_pep_meta beartype-0.18.5/beartype_test/a00_unit/data/hint/pep/proposal/_data_pep589.py000066400000000000000000000361011461113517100267440ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`589`-compliant **type hint test data.** ''' # ....................{ FIXTURES }.................... def hints_pep589_meta() -> 'List[HintPepMetadata]': ''' Session-scoped fixture returning a list of :pep:`589`-compliant **type hint metadata** (i.e., :class:`beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata` instances describing test-specific :pep:`589`-compliant sample type hints with metadata generically leveraged by various PEP-agnostic unit tests). ''' # ..................{ IMPORTS }.................. # Defer fixture-specific imports. from beartype.typing import ( List, Type, Union, ) from beartype._data.hint.pep.sign.datapepsigns import ( HintSignList, HintSignTypedDict, ) from beartype._util.api.utilapityping import get_typing_attrs from beartype_test.a00_unit.data.hint.util.data_hintmetacls import ( HintPepMetadata, HintPithSatisfiedMetadata, HintPithUnsatisfiedMetadata, ) # ..................{ LOCALS }.................. # List of all PEP-specific type hint metadata to be returned. hints_pep_meta = [] # ..................{ FACTORIES }.................. # For each PEP-specific type hint factory importable from each currently # importable "typing" module... for TypedDict in get_typing_attrs('TypedDict'): # ..................{ SUBCLASSES }.................. class ISeemAsInATranceSublimeAndStrange(TypedDict): ''' Arbitrary empty typed dictionary annotated to require *NO* key-value pairs. While patently absurd, this dictionary exercises an uncommon edge case in :pep:`589`. ''' pass class DizzyRavine(ISeemAsInATranceSublimeAndStrange): ''' Arbitrary non-empty typed dictionary annotated to require arbitrary key-value pairs, intentionally subclassing the empty typed dictionary subclass :class:`.ISeemAsInATranceSublimeAndStrange` to trivially exercise subclassability. ''' # Arbitrary key whose value is annotated to be a PEP-noncompliant # instance of an isinstanceable type. and_when: str # Arbitrary key whose value is annotated to be a PEP-compliant union of # either a subclass of an issubclassable type or a PEP-noncompliant # instance of an isinstanceable type. I_gaze_on_thee: Union[bytes, Type[Exception]] #FIXME: Note that even this fails to suffice, thanks to *CRAY-CRAY* #subclassing logic that absolutely no one has ever exercised, but which #we'll nonetheless need to support. And I quoth: # The totality flag only applies to items defined in the body of the # TypedDict definition. Inherited items won't be affected, and # instead use totality of the TypedDict type where they were defined. # This makes it possible to have a combination of required and # non-required keys in a single TypedDict type. #Ergo, we need to additionally declare yet another new class subclassing #"ToMuse" but *NOT* explicitly subclassed with a "total" keyword #parameter. This clearly gets *EXTREMELY* ugly *EXTREMELY* fast, as #we'll now need to iterate over "hint.__mro__" in our code generation #algorithm. Well, I suppose we technically needed to do that anyway... #but still. Yikes! class ToMuse(TypedDict, total=False): ''' Arbitrary non-empty typed dictionary annotated to require zero or more arbitrary key-value pairs. ''' # Arbitrary key whose value is annotated to be a PEP-noncompliant # instance of an isinstanceable type. on_my_own: str # Arbitrary key whose value is annotated to be a PEP-compliant union # of either a subclass of an issubclassable type or a # PEP-noncompliant instance of an isinstanceable type. separate_fantasy: Union[Type[Exception], bytes] # ..................{ LISTS }.................. # Add PEP-specific type hint metadata to this list. hints_pep_meta.extend(( # ................{ TYPEDDICT }................ # Empty typed dictionary. Look, this is ridiculous. What can you do? HintPepMetadata( hint=ISeemAsInATranceSublimeAndStrange, pep_sign=HintSignTypedDict, is_type_typing=False, piths_meta=( # Empty dictionary instantiated with standard Python syntax. HintPithSatisfiedMetadata({}), # Empty dictionary instantiated from this typed dictionary. HintPithSatisfiedMetadata( ISeemAsInATranceSublimeAndStrange()), # String constant. HintPithUnsatisfiedMetadata( pith='Hadithian bodies kindle Bodkin deathbeds', # Match that the exception message raised for this # object embeds the representation of the expected type. exception_str_match_regexes=(r'\bMapping\b',), ), #FIXME: Uncomment *AFTER* deeply type-checking "TypedDict". # # Non-empty dictionary. # HintPithSatisfiedMetadata({ # 'Corinthian bodachean kinslayers lay': ( # 'wedded weal‐kith with in‐'), # }), ), ), # Non-empty totalizing typed dictionary. HintPepMetadata( hint=DizzyRavine, pep_sign=HintSignTypedDict, is_type_typing=False, piths_meta=( # Non-empty dictionary of the expected keys and values. HintPithSatisfiedMetadata({ 'and_when': 'Corrigible‐ragged gun corruptions within', 'I_gaze_on_thee': b"Hatross-ev-olved eleven imp's", }), # Non-empty dictionary of the expected keys and values # instantiated from this typed dictionary. HintPithSatisfiedMetadata(DizzyRavine( and_when=( 'Machiavellian‐costumed, tumid stock fonts of a'), I_gaze_on_thee=RuntimeError, )), # String constant. HintPithUnsatisfiedMetadata( pith='Matross‐elevated elven velvet atrocities of', # Match that the exception message raised for this # object embeds the representation of the expected type. exception_str_match_regexes=(r'\bMapping\b',), ), # #FIXME: Uncomment *AFTER* deeply type-checking "TypedDict". # # Empty dictionary. # HintPithUnsatisfiedMetadata( # pith={}, # # Match that the exception message raised for this object # # embeds the expected number of key-value pairs. # exception_str_match_regexes=(r'\b2\b',), # ), # # Non-empty dictionary of the expected keys but *NOT* values. # HintPithUnsatisfiedMetadata( # pith={ # 'and_when': 'Matricidally', # 'I_gaze_on_thee': ( # 'Hatchet‐cachepotting, ' # 'Scossetting mock misrule by' # ), # }, # # Match that the exception message raised for this object # # embeds: # # * The name of the unsatisfied key. # # * The expected types of this key's value. # exception_str_match_regexes=( # r'\bI_gaze_on_thee\b', # r'\bbytes\b', # ), # ), ), ), # Non-empty non-totalizing typed dictionary. HintPepMetadata( hint=ToMuse, pep_sign=HintSignTypedDict, is_type_typing=False, piths_meta=( # Empty dictionary. HintPithSatisfiedMetadata({}), # Non-empty dictionary defining only one of the expected # keys. HintPithSatisfiedMetadata({ 'on_my_own': ( 'Spurned Court‐upturned, upper gladness, ' 'edifyingly humidifying'), }), # Non-empty dictionary defining *ALL* of the expected keys, # instantiated from this typed dictionary. HintPithSatisfiedMetadata(ToMuse( on_my_own=( 'Sepulchral epic‐âpostatizing home tombs metem‐'), separate_fantasy=b'Macroglia relics', )), # String constant. HintPithUnsatisfiedMetadata( pith=( 'Psychotically tempered Into temporal ' 'afterwork‐met portals portending a' ), # Match that the exception message raised for this # object embeds the representation of the expected type. exception_str_match_regexes=(r'\bMapping\b',), ), # #FIXME: Uncomment *AFTER* deeply type-checking "TypedDict". # # Non-empty dictionary of the expected keys but *NOT* values. # HintPithUnsatisfiedMetadata( # pith={ # 'on_my_own': ( # 'Psyche’s Maidenly‐enladened, ' # 'aidful Lads‐lickspittling Potenc‐ies —', # ), # 'separate_fantasy': ( # 'Psychedelic metal‐metastasized, glib'), # }, # # Match that the exception message raised for this object # # embeds: # # * The name of the unsatisfied key. # # * The expected types of this key's value. # exception_str_match_regexes=( # r'\bseparate_fantasy\b', # r'\bbytes\b', # ), # ), ), ), # ................{ LITERALS ~ nested }................ # List of non-empty totalizing typed dictionaries. HintPepMetadata( hint=List[DizzyRavine], pep_sign=HintSignList, isinstanceable_type=list, piths_meta=( # List of dictionaries of the expected keys and values. HintPithSatisfiedMetadata([ { 'and_when': ( 'Matriculating ‘over‐sized’ ' 'research urchin Haunts of', ), 'I_gaze_on_thee': b"Stands - to", }, { 'and_when': ( 'That resurrected, Erectile reptile’s ' 'pituitary capitulations to', ), 'I_gaze_on_thee': b"Strand our under-", }, ]), # List of string constants. HintPithUnsatisfiedMetadata( pith=[ 'D-as K-apital, ' 'notwithstanding Standard adiós‐', ], # Match that the exception message raised for this # object embeds the representation of the expected type. exception_str_match_regexes=(r'\bMapping\b',), ), # #FIXME: Uncomment *AFTER* deeply type-checking "TypedDict". # # List of empty dictionaries. # HintPithUnsatisfiedMetadata( # pith=[{}, {},], # # Match that the exception message raised for this object # # embeds the expected number of key-value pairs. # exception_str_match_regexes=(r'\b2\b',), # ), # # List of non-empty dictionaries, only one of which fails to # # define both the expected keys and values. # HintPithUnsatisfiedMetadata( # pith=[ # { # 'and_when': ( # 'Diased capitalization of (or into)'), # 'I_gaze_on_thee': ( # b'Witheringly dithering, dill husks of'), # }, # { # 'and_when': ( # 'Will, like Whitewash‐ed, musky'), # 'I_gaze_on_thee': 'Likenesses injecting', # }, # ], # # Match that the exception message raised for this object # # embeds: # # * The index of the unsatisfied dictionary. # # * The name of the unsatisfied key. # # * The expected types of this key's value. # exception_str_match_regexes=( # r'\b1\b', # r'\bI_gaze_on_thee\b', # r'\bbytes\b', # ), # ), ), ), )) # ..................{ RETURN }.................. # Return this list of all PEP-specific type hint metadata. return hints_pep_meta beartype-0.18.5/beartype_test/a00_unit/data/hint/pep/proposal/_data_pep593.py000066400000000000000000001100041461113517100267320ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`593`-compliant **type hint test data.** ''' # ....................{ ADDERS }.................... def hints_pep593_meta() -> 'List[HintPepMetadata]': ''' Session-scoped fixture returning a list of :pep:`593`-compliant **type hint metadata** (i.e., :class:`beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata` instances describing test-specific :pep:`593`-compliant sample type hints with metadata generically leveraged by various PEP-agnostic unit tests). ''' # ..................{ IMPORTS }.................. # Defer fixture-specific imports. from beartype.typing import ( List, Sequence, TypeVar, Union, ) from beartype.vale import ( Is, IsAttr, IsEqual, IsInstance, IsSubclass, ) from beartype._data.hint.pep.sign.datapepsigns import ( HintSignAnnotated, HintSignList, HintSignUnion, ) from beartype._util.api.utilapityping import get_typing_attrs from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_10 from beartype_test.a00_unit.data.data_type import ( Class, Subclass, SubclassSubclass, ) from beartype_test.a00_unit.data.hint.util.data_hintmetacls import ( HintPepMetadata, HintPithSatisfiedMetadata, HintPithUnsatisfiedMetadata, ) from functools import partial # ..................{ CLASSES }.................. class TruthSeeker(object): def __call__(self, obj: object) -> bool: ''' Tester method returning :data:`True` only if the passed object evaluates to :data:`True` when coerced into a boolean and whose first parameter is ignorable. ''' return bool(obj) # ..................{ CALLABLES }.................. def is_true(ignorable_arg, obj): ''' Tester function returning :data:`True` only if the passed object evaluates to :data:`True` when coerced into a boolean and whose first parameter is ignorable. ''' return bool(obj) # Partial of the is_true() tester defined above, effectively ignoring the # "ignorable_arg" parameter accepted by that tester. is_true_partial = partial( is_true, 'Drank its inspiring radiance, and the wind') # ..................{ VALIDATORS ~ is }.................. # PEP 484-compliant union of all builtin scalar types. Number = Union[int, float, complex] # Generic beartype validators defined as lambda functions. IsNonEmpty = Is[lambda obj: bool(obj)] # Numeric beartype validators defined as lambda functions. IsNonNegative = Is[lambda number: number >= 0] IsIntNonZero = Is[lambda number: isinstance(number, int) and number != 0] # Textual beartype validators defined as lambda functions. IsLengthy = Is[lambda text: len(text) > 30] IsSentence = Is[lambda text: text and text[-1] == '.'] # Textual beartype validators defined as non-lambda functions. def _is_quoted(text): return '"' in text or "'" in text def _is_exceptional(obj): raise ValueError(f'Colonial. {repr(obj)}') IsQuoted = Is[_is_quoted] IsExceptional = Is[_is_exceptional] # Textual beartype validator synthesized from the above validators via the # domain-specific language (DSL) implemented by those validators. IsLengthyOrUnquotedSentence = IsLengthy | (IsSentence & ~IsQuoted) # ..................{ VALIDATORS ~ isattr }.................. # Arbitrary list to validate equality against below. AMPLY_IMPISH = ['Amply imp-ish', 'blandishments to'] class CathecticallyEnsconceYouIn(object): ''' Arbitrary class defining an arbitrary attribute whose value has *no* accessible attributes but satisfies a validator tested below. ''' def __init__(self) -> None: ''' Initialize this object by defining this attribute. ''' # Initialize this attribute to a shallow copy of this list rather # than this list itself to properly test equality comparison. self.this_mobbed_triste_of = AMPLY_IMPISH[:] # Beartype-specific validator validating equality against that attribute. IsAttrThisMobbedTristeOf = IsAttr[ 'this_mobbed_triste_of', IsEqual[AMPLY_IMPISH]] # Instance of this class satisfying this validator. BOSS_EMBOSSED_ORDERING = CathecticallyEnsconceYouIn() # Instance of this class *NOT* satisfying this validator. SORDIDLY_FLABBY_WRMCASTINGS = CathecticallyEnsconceYouIn() SORDIDLY_FLABBY_WRMCASTINGS.this_mobbed_triste_of = [ 'An atomical caroller', 'carousing Thanatos', '(nucl‐eating',] # ..................{ LOCALS }.................. # List of all PEP-specific type hint metadata to be returned. hints_pep_meta = [] # ..................{ FACTORIES }.................. # For each PEP-specific type hint factory importable from each currently # importable "typing" module... for Annotated in get_typing_attrs('Annotated'): # print(f'Exercising PEP 593 {repr(Annotated)}...') # ..................{ LOCALS }.................. # Local variables requiring an "Annotated" type hint factory. # Annotated of an isinstanceable type annotated by one beartype-specific # validator defined as a lambda function. AnnotatedStrIsLength = Annotated[str, IsLengthy] # Validator matching any sequence of strings that is itself *NOT* a # string. Technically, a string is itself a sequence of characters, # which are themselves strings of length 1 in Python. Pragmatically, a # string is rarely considered to be a "sequence of strings" in the # common sense of that term. Validators, save us from the sins of Guido! SequenceNonstrOfStr = Annotated[Sequence[str], ~IsInstance[str]] # ................{ TUPLES }................ # Add PEP 593-specific test type hints to this tuple global. hints_pep_meta.extend(( # ..............{ ANNOTATED }.............. # Annotated of an arbitrary isinstanceable type annotated by an # arbitrary hashable object. HintPepMetadata( hint=Annotated[str, int], pep_sign=HintSignAnnotated, piths_meta=( # String constant. HintPithSatisfiedMetadata( 'Towards a timely, wines‐enticing gate'), # List of string constants. HintPithUnsatisfiedMetadata([ 'Of languished anger’s sap‐spated rushings',]), ), ), # Annotated of an arbitrary nested type hint. HintPepMetadata( hint=Annotated[List[str], int], pep_sign=HintSignAnnotated, piths_meta=( # List of string constants. HintPithSatisfiedMetadata([ 'MINERVA‐unnerving, verve‐sapping enervations', 'Of a magik-stoned Shinto rivery', ]), # String constant. HintPithUnsatisfiedMetadata('Of a Spicily sated',), ), ), # Unhashable annotated of an isinstanceable type annotated by an # unhashable mutable container. HintPepMetadata( hint=Annotated[str, []], pep_sign=HintSignAnnotated, piths_meta=( # String constant. HintPithSatisfiedMetadata( 'Papally Ľust‐besmirched Merchet laws'), # List of string constants. HintPithUnsatisfiedMetadata([ "Of our ôver‐crowdedly cowed crowd's opinion‐",]), ), ), # ..............{ ANNOTATED ~ beartype : is }.............. # Annotated of the root "object" superclass annotated by a beartype # validator defined as a lambda function. HintPepMetadata( hint=Annotated[object, Is[ lambda obj: hasattr(obj, 'this_mobbed_triste_of')]], pep_sign=HintSignAnnotated, piths_meta=( # Objects defining attributes with the above name. HintPithSatisfiedMetadata(BOSS_EMBOSSED_ORDERING), HintPithSatisfiedMetadata(SORDIDLY_FLABBY_WRMCASTINGS), # String constant. HintPithUnsatisfiedMetadata( 'Her timid steps to gaze upon a form'), ), ), # Annotated of the root "object" superclass annotated by a beartype # validator defined as a callable object. HintPepMetadata( hint=Annotated[object, Is[TruthSeeker()]], pep_sign=HintSignAnnotated, piths_meta=( # Objects evaluating to "True" when coerced into booleans. HintPithSatisfiedMetadata(True), HintPithSatisfiedMetadata( 'Leaped in the boat, he spread his cloak aloft'), # Empty string. HintPithUnsatisfiedMetadata(''), ), ), # Annotated of the root "object" superclass annotated by a beartype # validator defined as a partial function. HintPepMetadata( hint=Annotated[object, Is[is_true_partial]], pep_sign=HintSignAnnotated, piths_meta=( # Objects evaluating to "True" when coerced into booleans. HintPithSatisfiedMetadata(True), HintPithSatisfiedMetadata( 'Swept strongly from the shore, blackening the waves.'), # Empty string. HintPithUnsatisfiedMetadata(''), ), ), # Annotated of an isinstanceable type annotated by a beartype # validator defined as a lambda function. HintPepMetadata( hint=AnnotatedStrIsLength, pep_sign=HintSignAnnotated, piths_meta=( # String constant satisfying this validator. HintPithSatisfiedMetadata( 'To Ɯṙaith‐like‐upwreathed ligaments'), # Byte-string constant *NOT* an instance of the expected # type. HintPithUnsatisfiedMetadata( pith=b'Down-bound', # Match that the exception message raised for this # object embeds the code for this validator's lambda # function. exception_str_match_regexes=( r'Is\[.*\blen\(text\)\s*>\s*30\b.*\]',), ), # String constant violating this validator. HintPithUnsatisfiedMetadata('To prayer'), ), ), # Annotated of an isinstanceable type annotated by two or more # beartype validators all defined as functions, specified with # comma-delimited list syntax. HintPepMetadata( hint=Annotated[str, IsLengthy, IsSentence, IsQuoted], pep_sign=HintSignAnnotated, piths_meta=( # String constant satisfying these validators. HintPithSatisfiedMetadata( '"Into piezo‐electrical, dun‐dappled lights" and...'), # Byte-string constant *NOT* an instance of the expected # type. HintPithUnsatisfiedMetadata( pith=b'Joy.', # Match that the exception message raised... exception_str_match_regexes=( # Embeds the code for this validator's lambdas. r'Is\[.*\blen\(text\)\s*>\s*30\b.*\]', r"Is\[.*\btext and text\[-1\]\s*==\s*'\.'.*\]", # Embeds the name of this validator's named # function. r'Is\[.*\b_is_quoted\b.*\]', ), ), # String constant violating only the first of these # validators. HintPithUnsatisfiedMetadata( pith='"Conduct my friar’s wheel"...', # Match that the exception message raised documents the # exact validator violated by this string. exception_str_match_regexes=( r'\bviolates\b.*\bIs\[.*\blen\(text\)\s*>\s*30\b.*\]',) ), ), ), # Annotated of an isinstanceable type annotated by one beartype # validator synthesized from all possible operators. HintPepMetadata( hint=Annotated[str, IsLengthyOrUnquotedSentence], pep_sign=HintSignAnnotated, piths_meta=( # String constant satisfying these validators. HintPithSatisfiedMetadata( 'Dialectical, eclectic mind‐toys'), # Byte-string constant *NOT* an instance of the expected # type. HintPithUnsatisfiedMetadata( pith=b'Of Cycladic impoverishment, cyclically unreeling', # Match that the exception message raised... exception_str_match_regexes=( # Embeds the code for this validator's lambdas. r'Is\[.*\blen\(text\)\s*>\s*30\b.*\]', r"Is\[.*\btext and text\[-1\]\s*==\s*'\.'.*\]", # Embeds the name of this validator's named # function. r'Is\[.*\b_is_quoted\b.*\]', ), ), # String constant violating all of these validators. HintPithUnsatisfiedMetadata( pith='Stay its course, and', # Match that the exception message raised documents the # first validator violated by this string. exception_str_match_regexes=( r'\bviolates\b.*\bIs\[.*\blen\(text\)\s*>\s*30\b.*\]',) ), ), ), # ..............{ ANNOTATED ~ beartype : is : & }.............. # Annotated of an isinstanceable type annotated by two or more # beartype validators all defined as functions, specified with # "&"-delimited operator syntax. HintPepMetadata( hint=Annotated[str, IsLengthy & IsSentence & IsQuoted], pep_sign=HintSignAnnotated, piths_meta=( # String constant satisfying these validators. HintPithSatisfiedMetadata( '"Into piezo‐electrical, dun‐dappled lights" and...'), # Byte-string constant *NOT* an instance of the expected # type. HintPithUnsatisfiedMetadata( pith=b'Joy.', # Match that the exception message raised... exception_str_match_regexes=( # Embeds the code for this validator's lambdas. r'Is\[.*\blen\(text\)\s*>\s*30\b.*\]', r"Is\[.*\btext and text\[-1\]\s*==\s*'\.'.*\]", # Embeds the name of this validator's named # function. r'Is\[.*\b_is_quoted\b.*\]', ), ), # String constant violating only the first of these # validators. HintPithUnsatisfiedMetadata( pith='"Conduct my friar’s wheel"...', # Match that the exception message raised documents the # exact validator violated by this string. exception_str_match_regexes=( r'\bviolates\b.*\bIs\[.*\blen\(text\)\s*>\s*30\b.*\]',) ), ), ), # Annotated of an isinstanceable type annotated by two or more # beartype validators all defined as functions, specified with # "&"-delimited operator syntax such that the first such validator # short-circuits all subsequent such validators, which all # intentionally raise exceptions to prove they are silently ignored. # # Note this hint is *NOT* safely satisfiable. Ergo, we # intentionally do *NOT* validate this hint to be satisfied. HintPepMetadata( hint=Annotated[str, IsLengthy & IsExceptional], pep_sign=HintSignAnnotated, piths_meta=( # Byte-string constant *NOT* an instance of the expected # type. HintPithUnsatisfiedMetadata( b'Lone ignorance concentrations a-'), # String constant violating the first validator. HintPithUnsatisfiedMetadata('Long a'), ), ), # ..............{ ANNOTATED ~ beartype : is : | }.............. # Annotated of an isinstanceable type annotated by two or more # beartype validators all defined as functions, specified with # "|"-delimited operator syntax such that the first such validator # short-circuits all subsequent such validators, which all # intentionally raise exceptions to prove they are silently ignored. # # Note this hint is *NOT* safely unsatisfiable. Ergo, we # intentionally do *NOT* validate this hint to be unsatisfied. HintPepMetadata( hint=Annotated[str, IsLengthy | IsExceptional], pep_sign=HintSignAnnotated, piths_meta=( # String constant satisfying the first validator. HintPithSatisfiedMetadata( "Longhouse-ignifluous, " "superfluousness-rambling academic's" ), # Byte-string constant *NOT* an instance of the expected # type. HintPithUnsatisfiedMetadata(b'Intra-convivial loci of'), ), ), # ..............{ ANNOTATED ~ beartype : is : nest }.............. # Annotated of an annotated of an isinstanceable type, each # annotated by a beartype validator defined as a function. HintPepMetadata( hint=Annotated[Annotated[str, IsLengthy], IsSentence], pep_sign=HintSignAnnotated, piths_meta=( # String constant satisfying these validators. HintPithSatisfiedMetadata( 'Antisatellite‐dendroidal, Θṙbital Cemetery orbs — ' 'of Moab.' ), # Byte-string constant *NOT* an instance of the expected # type. HintPithUnsatisfiedMetadata(b'Then, and'), # String constant violating only the first of these # validators. HintPithUnsatisfiedMetadata('Though a...'), ), ), # List of annotateds of isinstanceable types annotated by one # beartype validator defined as a lambda function. HintPepMetadata( hint=List[AnnotatedStrIsLength], pep_sign=HintSignList, isinstanceable_type=list, is_pep585_builtin_subscripted=List is list, piths_meta=( # List of string constants satisfying this validator. HintPithSatisfiedMetadata([ 'An‐atomically Island‐stranded, adrift land)', 'That You randily are That worm‐tossed crabapple of', ]), # String constant *NOT* an instance of the expected type. HintPithUnsatisfiedMetadata( pith="Our Sturm‐sapped disorder's convolution of", # Match that the exception message raised for this # object embeds the code for this validator's lambda # function. exception_str_match_regexes=( r'Is\[.*\blen\(text\)\s*>\s*30\b.*\]',), ), # List of string constants violating this validator. HintPithUnsatisfiedMetadata([ 'Volubly liable,', 'Drang‐rang aloofment –', 'ruthlessly', ]), ), ), # List of lists of annotateds of an ignorable type hint annotated by # a type variable bounded by a beartype validator defined as a # lambda function. HintPepMetadata( hint=List[List[TypeVar('AWhirlwindSweptItOn', bound=( Annotated[object, IsIntNonZero]))]], pep_sign=HintSignList, isinstanceable_type=list, is_pep585_builtin_subscripted=List is list, is_typevars=True, piths_meta=( # List of lists of non-zero integer constants. HintPithSatisfiedMetadata([[16, 17, 20], [21, 64, 65, 68]]), # String constant *NOT* an instance of the expected type. HintPithUnsatisfiedMetadata( 'The waves arose. Higher and higher still'), # List of lists of non-integers and zeroes. HintPithUnsatisfiedMetadata([ [ 'Their fierce necks writhed', "beneath the tempest's scourge", ], [0, 0, 0, 0], ]), ), ), # List of lists of annotateds of an ignorable type hint annotated by # a type variable bounded by two beartype validators, one defined as # a lambda function and one not. HintPepMetadata( hint=List[List[TypeVar('WithFierceGusts', bound=( Annotated[object, IsInstance[int], IsNonEmpty]))]], pep_sign=HintSignList, isinstanceable_type=list, is_pep585_builtin_subscripted=List is list, is_typevars=True, piths_meta=( # List of lists of non-zero integer constants. HintPithSatisfiedMetadata([[16, 17, 20], [21, 64, 65, 68]]), # String constant *NOT* an instance of the expected type. HintPithUnsatisfiedMetadata( 'The waves arose. Higher and higher still'), # List of lists of non-integers and zeroes. HintPithUnsatisfiedMetadata([ [ 'Their fierce necks writhed', "beneath the tempest's scourge", ], [0, 0, 0, 0], ]), ), ), # ..............{ ANNOTATED ~ beartype : isattr }.............. # Annotated of an isinstanceable type annotated by one beartype # attribute validator. HintPepMetadata( hint=Annotated[ CathecticallyEnsconceYouIn, IsAttrThisMobbedTristeOf], pep_sign=HintSignAnnotated, piths_meta=( # Instance of this class satisfying this validator. HintPithSatisfiedMetadata(BOSS_EMBOSSED_ORDERING), # String constant *NOT* an instance of this class. HintPithUnsatisfiedMetadata( pith='An atoll nuclear broilers newly cleared of', # Match that the exception message raised for this # object embeds the name of the expected attribute. exception_str_match_regexes=( r"IsAttr\[.*'this_mobbed_triste_of',.*\]",), ), # Instance of this class *NOT* satisfying this validator. HintPithUnsatisfiedMetadata(SORDIDLY_FLABBY_WRMCASTINGS), ), ), # ..............{ ANNOTATED ~ beartype : isequal }.............. # Annotated of an isinstanceable type annotated by one beartype # equality validator. HintPepMetadata( hint=Annotated[List[str], IsEqual[AMPLY_IMPISH]], pep_sign=HintSignAnnotated, piths_meta=( # Exact object subscripting this validator. HintPithSatisfiedMetadata(AMPLY_IMPISH), # Object *NOT* subscripting this validator but equal to # this object. HintPithSatisfiedMetadata(AMPLY_IMPISH[:]), # String constant *NOT* an instance of the expected type. HintPithUnsatisfiedMetadata( pith='May Your coarsest, Incessantly cast‐off jobs of a', # Match that the exception message raised for this # object embeds a string in the expected list. exception_str_match_regexes=(r"'Amply imp-ish'",), ), # List of integer constants *NOT* instances of the expected # subtype. HintPithUnsatisfiedMetadata([1, 2, 3, 6, 7, 14, 21, 42,]), # List of string constants violating this validator. HintPithUnsatisfiedMetadata( ['Hectic,', 'receptacle‐hybernacling caste so',]), ), ), # ..............{ ANNOTATED ~ beartype : isinstance }.............. # Annotated of an isinstanceable type annotated by one beartype type # instance validator. HintPepMetadata( hint=Annotated[Class, ~IsInstance[SubclassSubclass]], pep_sign=HintSignAnnotated, piths_meta=( # Instance of the class subscripting this validator. HintPithSatisfiedMetadata(Class()), # Instance of the subclass subclassing the class # subscripting this validator. HintPithSatisfiedMetadata(Subclass()), # Instance of the subsubclass subclassing the subclass # subclassing the class subscripting this validator. HintPithUnsatisfiedMetadata(SubclassSubclass()), # Class subscripting this validator. HintPithUnsatisfiedMetadata(Class), # String constant *NOT* an instance of the expected type. HintPithUnsatisfiedMetadata( pith=( 'Architrave‐contravening, ' 'indigenously chitinous tactilities) of' ), # Match that the exception message raised for this # object embeds a classname in the expected list. exception_str_match_regexes=(r'\bClass\b',), ), ), ), # Type hint matching *ANY* sequence of strings, defined as the union # of a beartype validator matching any sequence of strings that is # *NOT* itself a string with a string. Although odd, this exercises # an obscure edge case in code generation. HintPepMetadata( hint=Union[SequenceNonstrOfStr, str], pep_sign=HintSignUnion, piths_meta=( # String constant. HintPithSatisfiedMetadata( 'Caught the impatient wandering of his gaze.'), # Tuple of string constants. HintPithSatisfiedMetadata(( 'Swayed with the undulations of the tide.', 'A restless impulse urged him to embark', )), # Byte string constant. HintPithUnsatisfiedMetadata( pith=b'It had been long abandoned, for its sides', # Match that the exception message raised for this # object embeds a classname in the expected list. exception_str_match_regexes=(r'\bbytes\b',), ), # Tuple of byte string constants. HintPithUnsatisfiedMetadata( pith=( b'Gaped wide with many a rift,', b'and its frail joints', ), # Match that the exception message raised for this # object embeds a classname in the expected list. exception_str_match_regexes=(r'\btuple\b',), ), ), ), # ..............{ ANNOTATED ~ beartype : issubclass }.............. # Annotated of an isinstanceable type annotated by one beartype type # inheritance validator. HintPepMetadata( hint=Annotated[type, IsSubclass[Class]], pep_sign=HintSignAnnotated, piths_meta=( # Class subscripting this validator. HintPithSatisfiedMetadata(Class), # Class subclassing the class subscripting this validator. HintPithSatisfiedMetadata(Subclass), # Class *NOT* subclassing the class subscripting this # validator. HintPithUnsatisfiedMetadata(str), # String constant *NOT* an instance of the expected type. HintPithUnsatisfiedMetadata( pith='May Your coarsest, Incessantly cast‐off jobs of a', # Match that the exception message raised for this # object embeds a classname in the expected list. exception_str_match_regexes=(r'\bClass\b',), ), ), ), )) # ..................{ VERSION }.................. # If the active Python interpreter targets Python >= 3.10, the # "typing.Annotated" type factory supports the "|" operator. In this # case, defined unions of annotateds with this operator. if IS_PYTHON_AT_LEAST_3_10: # Add PEP 593-specific test type hints to this tuple global. hints_pep_meta.extend(( # ..............{ ANNOTATED ~ beartype : is : nes}.............. # List of lists of annotateds of a union of isinstanceable types # annotated by a type variable bounded by a union of beartype # validators defined as lambda functions. HintPepMetadata( hint=List[List[TypeVar('TheStrainingBoat', bound=( Annotated[Number, IsNonNegative] | Annotated[str, IsNonEmpty] ))]], pep_sign=HintSignList, isinstanceable_type=list, is_pep585_builtin_subscripted=List is list, is_typevars=True, piths_meta=( # List of lists of positive number constants. HintPithSatisfiedMetadata([[11, 0.11], [1, 110, 1101100]]), # List of lists of non-empty string constants. HintPithSatisfiedMetadata([ ['The straining boat.', '—A whirlwind swept it on,'], ['With fierce gusts and', 'precipitating force,'], ]), # String constant *NOT* an instance of the expected type. HintPithUnsatisfiedMetadata( 'Through the white ridges of the chafèd sea.'), # List of lists of negative numbers and empty strings. HintPithUnsatisfiedMetadata([[-1, '', -0.4], ['', -5, '']]), ), ), )) # Else, the active Python interpreter targets Python < 3.10. In this # case, the "typing.Annotated" type factory fails to support the "|" # operator. # ..................{ RETURN }.................. # Return this list of all PEP-specific type hint metadata. return hints_pep_meta def hints_pep593_ignorable_deep() -> list: ''' List of :pep:`593`-compliant **deeply ignorable type hints** (i.e., ignorable only on the non-trivial basis of their nested child type hints). ''' # ..................{ IMPORTS }.................. from beartype.typing import ( Any, List, NewType, Optional, Union, ) from beartype._util.api.utilapityping import get_typing_attrs # ..................{ LOCALS }.................. # List of all PEP-specific deeply ignorable type hints to be returned. hints_pep_ignorable_deep = [] # ..................{ LISTS }.................. # For each PEP-specific type hint factory importable from each currently # importable "typing" module... for Annotated in get_typing_attrs('Annotated'): # ................{ SETS }................ # Add PEP 593-specific deeply ignorable test type hints to that global. hints_pep_ignorable_deep.extend(( # Annotated of shallowly ignorable type hints. Annotated[Any, int], Annotated[object, int], # Annotated of ignorable unions and optionals. Annotated[Union[Any, float, str,], int], Annotated[Optional[Any], int], # Unions and optionals of ignorable annotateds. Union[complex, int, Annotated[Any, int]], Optional[Annotated[object, int]], # Deeply ignorable PEP 484-, 585-, and 593-compliant type hint # exercising numerous edge cases broken under prior releases. Union[str, List[int], NewType('MetaType', Annotated[object, 53])], )) # ..................{ RETURN }.................. # Return this list. return hints_pep_ignorable_deep beartype-0.18.5/beartype_test/a00_unit/data/hint/pep/proposal/_data_pep604.py000066400000000000000000000240411461113517100267300ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`604`-compliant **type hint test data.** ''' # ....................{ FIXTURES }.................... def hints_pep604_meta() -> 'List[HintPepMetadata]': ''' Session-scoped fixture returning a list of :pep:`604`-compliant **type hint metadata** (i.e., :class:`beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata` instances describing test-specific :pep:`604`-compliant sample type hints with metadata generically leveraged by various PEP-agnostic unit tests). ''' # ..................{ IMPORTS ~ early }.................. # Defer early-time imports. from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_10 # ..................{ LOCALS }.................. # List of all PEP-specific type hint metadata to be returned. hints_pep_meta = [] # If the active Python interpreter targets Python < 3.10, this interpreter # fails to support PEP 604. In this case, return the empty list. if not IS_PYTHON_AT_LEAST_3_10: return hints_pep_meta # Else, the active Python interpreter targets Python >= 3.10 and thus # supports PEP 604. # ..................{ IMPORTS ~ version }.................. # Defer version-specific imports. from beartype._data.hint.pep.sign.datapepsigns import ( HintSignList, HintSignUnion, ) from beartype_test.a00_unit.data.hint.util.data_hintmetacls import ( HintPepMetadata, HintPithSatisfiedMetadata, HintPithUnsatisfiedMetadata, ) # ..................{ TUPLES }.................. # Add PEP 604-specific test type hints to this tuple global. hints_pep_meta.extend(( # ................{ NEW UNION }................ # Union of one non-"typing" type and an originative "typing" type, # exercising a prominent edge case when raising human-readable # exceptions describing the failure of passed parameters or returned # values to satisfy this union. # # Interestingly, Python preserves this union as a PEP 604-compliant # new-style union rather than implicitly coercing this into a PEP # 484-compliant old-style union: e.g., # >>> int | list[str] # int | list[str] HintPepMetadata( hint=int | list[str], pep_sign=HintSignUnion, is_type_typing=False, piths_meta=( # Integer constant. HintPithSatisfiedMetadata(87), # List of string items. HintPithSatisfiedMetadata([ 'Into, my myopic mandrake‐manhandling, ' 'panhandling slakes of', 'Televisual, dis‐informative Lakes, unsaintly, of', ]), # Floating-point constant. HintPithUnsatisfiedMetadata( pith=10100.00101, # Match that the exception message raised for this object # declares the types *NOT* satisfied by this object. exception_str_match_regexes=( r'\blist\b', r'\bint\b', ), # Match that the exception message raised for this object # does *NOT* contain a newline or bullet delimiter. exception_str_not_match_regexes=( r'\n', r'\*', ), ), # List of integers. HintPithUnsatisfiedMetadata( pith=([1, 10, 271, 36995]), # Match that the exception message raised for this object... exception_str_match_regexes=( # Contains a bullet point declaring the non-"typing" # type *NOT* satisfied by this object. r'\n\*\s.*\bint\b', # Contains a bullet point declaring the index of the # random list item *NOT* satisfying this hint. r'\n\*\s.*\b[Ll]ist index \d+ item\b', ), ), ), ), # ................{ NEW UNION ~ nested }................ # Nested unions exercising edge cases induced by Python >= 3.8 # optimizations leveraging PEP 572-style assignment expressions. # Nested union of multiple non-"typing" types. HintPepMetadata( hint=list[int | str], pep_sign=HintSignList, isinstanceable_type=list, is_pep585_builtin_subscripted=True, piths_meta=( # List containing a mixture of integer and string constants. HintPithSatisfiedMetadata([ 'Telemarketing affirmative‐mined Ketamine’s', 470, ]), # String constant. HintPithUnsatisfiedMetadata( pith='Apolitically', # Match that the exception message raised for this object # declares the types *NOT* satisfied by this object. exception_str_match_regexes=( r'\bint\b', r'\bstr\b', ), # Match that the exception message raised for this object # does *NOT* contain a newline or bullet delimiter. exception_str_not_match_regexes=( r'\n', r'\*', ), ), # List of bytestring items. HintPithUnsatisfiedMetadata( pith=[ b'Apoplectic hints of', b'Stenographically', ], # Match that the exception message raised for this object... exception_str_match_regexes=( # Declares all non-"typing" types *NOT* satisfied by a # random list item *NOT* satisfying this hint. r'\bint\b', r'\bstr\b', # Declares the index of the random list item *NOT* # satisfying this hint. r'\b[Ll]ist index \d+ item\b', ), ), ), ), # ................{ NEW UNION ~ optional }................ # Optional isinstance()-able "typing" type. HintPepMetadata( hint=tuple[str, ...] | None, # Note that although Python >= 3.10 distinguishes equivalent PEP # 484-compliant "typing.Union[...]" and "typing.Optional[...]" type # hints via differing machine-readable representations, the same # does *NOT* apply to PEP 604-compliant |-style unions, which remain # PEP 604-compliant and thus unions rather than optional. This # includes PEP 604-compliant |-style unions including the "None" # singleton signifying an optional type hint. Go figure. pep_sign=HintSignUnion, is_type_typing=False, piths_meta=( # None singleton. HintPithSatisfiedMetadata(None), # Tuple of string items. HintPithSatisfiedMetadata(( 'Stentorian tenor of', 'Stunted numbness (in', )), # Floating-point constant. HintPithUnsatisfiedMetadata( pith=2397.7932, # Match that the exception message raised for this object # declares the types *NOT* satisfied by this object. exception_str_match_regexes=( r'\bNoneType\b', r'\btuple\b', ), # Match that the exception message raised for this object # does *NOT* contain a newline or bullet delimiter. exception_str_not_match_regexes=( r'\n', r'\*', ), ), ), ), )) # ..................{ RETURN }.................. # Return this list of all PEP-specific type hint metadata. return hints_pep_meta def hints_pep604_ignorable_deep() -> list: ''' List of :pep:`604`-compliant **deeply ignorable type hints** (i.e., ignorable only on the non-trivial basis of their nested child type hints). ''' # ..................{ IMPORTS }.................. from beartype.typing import Any from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_10 # If the active Python interpreter targets Python < 3.10, this interpreter # fails to support PEP 604. In this case, return the empty list. if not IS_PYTHON_AT_LEAST_3_10: return [] # Else, this interpreter supports PEP 604. # ..................{ RETURN }.................. # Return this list of all PEP-specific deeply ignorable type hints. return [ # New-style unions containing any ignorable type hint. # # Note that including *ANY* "typing"-based type hint (including # "typing.Any") in an "|"-style union causes Python to implicitly # produce a PEP 484- rather than PEP 604-compliant union (e.g., # "typing.Union[Any, float, str]" in this case). Since that is more or # less fine in this context, we intentionally list such a deeply # ignorable hint here. Any | float | str, complex | int | object, ] beartype-0.18.5/beartype_test/a00_unit/data/hint/pep/proposal/_data_pep675.py000066400000000000000000000101101461113517100267300ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`675`-compliant **type hint test data.** ''' # ....................{ FIXTURES }.................... def hints_pep675_meta() -> 'List[HintPepMetadata]': ''' Session-scoped fixture returning a list of :pep:`675`-compliant **type hint metadata** (i.e., :class:`beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata` instances describing test-specific :pep:`675`-compliant sample type hints with metadata generically leveraged by various PEP-agnostic unit tests). ''' # ..................{ IMPORTS }.................. # Defer fixture-specific imports. from beartype._data.hint.pep.sign.datapepsigns import ( HintSignLiteralString) from beartype._util.api.utilapityping import get_typing_attrs from beartype_test.a00_unit.data.hint.util.data_hintmetacls import ( HintPepMetadata, HintPithSatisfiedMetadata, HintPithUnsatisfiedMetadata, ) # ..................{ LOCALS }.................. # List of all PEP-specific type hint metadata to be returned. hints_pep_meta = [] # ..................{ FACTORIES }.................. # For each PEP-specific type hint factory importable from each currently # importable "typing" module... for LiteralString in get_typing_attrs('LiteralString'): # ..................{ LISTS }.................. # Add PEP-specific type hint metadata to this list. hints_pep_meta.extend(( # ................{ PEP 675 }................ # Literal string type hint. HintPepMetadata( hint=LiteralString, pep_sign=HintSignLiteralString, piths_meta=( # Literal string statically defined from a literal string. HintPithSatisfiedMetadata( 'All overgrown with azure moss and flowers'), # Literal string dynamically defined from the concatenation # of multiple literal strings, inducing type inference in # PEP 675. HintPithSatisfiedMetadata( 'So sweet, ' + 'the sense faints picturing them! ' + 'Thou' ), # Non-literal string dynamically defined from a literal # integer. Ideally, @beartype should reject this # non-literal; pragmatically, runtime type-checkers are # incapable of doing so in the general case. For example, # str() could have been shadowed by a user-defined function # of the same name returning a valid literal string. HintPithSatisfiedMetadata(str(0xFEEDFACE)), # Literal byte string. HintPithUnsatisfiedMetadata( pith=b"For whose path the Atlantic's level powers", # Match that the exception message raised for this # object declares the types *NOT* satisfied by this # object. exception_str_match_regexes=( r'\bstr\b', ), # Match that the exception message raised for this # object does *NOT* contain a newline or bullet # delimiter. exception_str_not_match_regexes=( r'\n', r'\*', ), ), ), ), )) # ..................{ RETURN }.................. # Return this list of all PEP-specific type hint metadata. return hints_pep_meta beartype-0.18.5/beartype_test/a00_unit/data/hint/pep/proposal/_data_pep695.py000066400000000000000000000161201461113517100267410ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`695`-compliant **type hint test data.** ''' # ....................{ FIXTURES }.................... def hints_pep695_meta() -> 'List[HintPepMetadata]': ''' List of :pep:`695`-compliant **type hint metadata** (i.e., :class:`beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata` instances describing test-specific :pep:`695`-compliant sample type hints with metadata generically leveraged by various PEP-agnostic unit tests). ''' # ..................{ IMPORTS }.................. # Defer fixture-specific imports. from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_12 # List of all PEP-specific type hint metadata to be returned. hints_pep_meta = [] # If the active Python interpreter targets Python < 3.12, this interpreter # fails to support PEP 695. In this case, return the empty list. if not IS_PYTHON_AT_LEAST_3_12: return hints_pep_meta # Else, the active Python interpreter targets Python >= 3.12 and thus # supports PEP 695. # ..................{ IMPORTS ~ version }.................. # Defer version-specific imports. from beartype._data.hint.pep.sign.datapepsigns import ( HintSignPep695TypeAlias, ) from beartype_test.a00_unit.data.hint.util.data_hintmetacls import ( HintPepMetadata, HintPithSatisfiedMetadata, HintPithUnsatisfiedMetadata, ) # ..................{ LOCALS }.................. # Simple type alias whose value is a standard type hint containing *NO* # syntax or semantics unique to PEP 695-compliant type aliases (e.g., *NO* # forward references, recursion, or type variables). type AliasSimple = int | list[str] # Generic type alias whose value is a union of two or more generic type # hints parametrized by the same type variable subscripting this alias. type AliasGeneric[T] = list[T] | set[T] # ..................{ LISTS }.................. # Add PEP 604-specific test type hints to this list. hints_pep_meta.extend(( # ................{ TYPE ALIAS }................ # Simple type alias whose value is a standard type hint containing *NO* # syntax or semantics unique to PEP 695-compliant type aliases (e.g., # *NO* forward references, recursion, or type variables). HintPepMetadata( hint=AliasSimple, pep_sign=HintSignPep695TypeAlias, is_type_typing=True, is_typing=False, piths_meta=( # Integer constant. HintPithSatisfiedMetadata(0xFACE0FFF), # List of string items. HintPithSatisfiedMetadata([ 'His inmost sense suspended in its web', 'Of many-coloured woof and shifting hues.', ]), # Floating-point constant. HintPithUnsatisfiedMetadata( pith=70700.00707, # Match that the exception message raised for this object # declares the types *NOT* satisfied by this object. exception_str_match_regexes=( r'\blist\b', r'\bint\b', ), # Match that the exception message raised for this object # does *NOT* contain a newline or bullet delimiter. exception_str_not_match_regexes=( r'\n', r'\*', ), ), ), ), # Generic type alias whose value is a union of two or more generic type # hints parametrized by the same type variable subscripting this alias. HintPepMetadata( hint=AliasGeneric, pep_sign=HintSignPep695TypeAlias, is_type_typing=True, is_typevars=True, is_typing=False, piths_meta=( # List of arbitrary objects all of the same type. HintPithSatisfiedMetadata([ 'Knowledge and truth and virtue were her theme,', 'And lofty hopes of divine liberty,', ]), # Set of arbitrary objects all of the same type. HintPithSatisfiedMetadata({ b'Thoughts the most dear to him, and poesy,', b'Herself a poet. Soon the solemn mood', }), # Integer constant. HintPithUnsatisfiedMetadata( pith=0xFACECAFE, # Match that the exception message raised for this object # declares the types *NOT* satisfied by this object. exception_str_match_regexes=( r'\blist\b', r'\bset\b', ), # Match that the exception message raised for this object # does *NOT* contain a newline or bullet delimiter. exception_str_not_match_regexes=( r'\n', r'\*', ), ), ), ), )) # ..................{ RETURN }.................. # Return this list of all PEP-specific type hint metadata. return hints_pep_meta def hints_pep695_ignorable_deep() -> list: ''' List of :pep:`695`-compliant **deeply ignorable type hints** (i.e., ignorable only on the non-trivial basis of their nested child type hints). ''' # ..................{ IMPORTS }.................. from beartype.typing import Any from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_12 # If the active Python interpreter targets Python < 3.12, this interpreter # fails to support PEP 695. In this case, return the empty list. if not IS_PYTHON_AT_LEAST_3_12: return [] # Else, this interpreter supports PEP 695. # ..................{ LOCALS }.................. # Type alias whose value is shallowly ignorable. Note that, although this # value is shallowly ignorable, deciding whether or not a type alias is # ignorable requires deep inspection into the value of that alias. Ergo, # *ALL* type aliases that are ignorable are only deeply ignorable; there # exist *NO* shallowly ignorable type aliases. type AliasIgnorableShallow = object # Type alias whose value is deeply ignorable. type AliasIgnorableDeep = float | str | Any # ..................{ RETURN }.................. # Return this list of all PEP-specific deeply ignorable type hints. return [ AliasIgnorableShallow, AliasIgnorableDeep, ] beartype-0.18.5/beartype_test/a00_unit/data/hint/pep/proposal/data_pep484.py000066400000000000000000003212141461113517100266010ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`484`-compliant **type hint test data.** Caveats ------- Note that: * The :pep:`484`-compliant annotated builtin containers created and returned by the :func:`typing.NamedTuple` and :func:`typing.TypedDict` factory functions are *mostly* indistinguishable from PEP-noncompliant types and thus intentionally tested in the :mod:`beartype_test.a00_unit.data.hint.nonpep.proposal._data_nonpep484` submodule rather than here despite being standardized by :pep:`484`. * The ``typing.Supports*`` family of abstract base classes (ABCs) are intentionally tested in the :mod:`beartype_test.a00_unit.data.hint.pep.proposal._data_pep544` submodule rather than here despite being specified by :pep:`484` and available under Python < 3.8. Why? Because the implementation of these ABCs under Python < 3.8 is unusable at runtime, which is nonsensical and awful, but that's :mod:`typing` for you. What you goin' do? ''' # ....................{ IMPORTS }.................... import contextlib, re from beartype._cave._cavefast import ( RegexMatchType, RegexCompiledType, ) from beartype._data.hint.datahinttyping import S, T from beartype_test.a00_unit.data.data_type import ( Class, Subclass, SubclassSubclass, OtherClass, OtherSubclass, context_manager_factory, default_dict_int_to_str, default_dict_str_to_str, ) from collections.abc import ( Callable as CallableABC, Hashable as HashableABC, Mapping as MappingABC, MutableMapping as MutableMappingABC, MutableSequence as MutableSequenceABC, Sequence as SequenceABC, Sized as SizedABC, ) from typing import ( Any, AnyStr, BinaryIO, Callable, Container, ContextManager, DefaultDict, Dict, ForwardRef, Generic, Hashable, IO, Iterable, List, Match, Mapping, MutableMapping, MutableSequence, NewType, OrderedDict, Pattern, Sequence, Sized, TextIO, Tuple, Type, TypeVar, Optional, Union, ) # ....................{ TYPEVARS ~ unbounded }.................... T_BOUNDED = TypeVar('T_BOUNDED', bound=int) ''' Arbitrary **bounded type variable** (i.e., type variable parametrized by a PEP-compliant child type hint passed as the ``bound`` keyword argument). ''' T_CONSTRAINED = TypeVar('T_CONSTRAINED', str, bytes) ''' Arbitrary **constrained type variable** (i.e., type variable parametrized by two or more PEP-compliant child type hints passed as positional arguments). ''' # ....................{ GENERICS ~ io }.................... PEP484_GENERICS_IO = frozenset((BinaryIO, IO, TextIO,)) ''' Frozen set of all :pep:`484`-compliant :mod:`typing` IO generic base classes. ''' # ....................{ GENERICS ~ single }.................... class Pep484GenericTypevaredSingle(Generic[S, T]): ''' :pep:`484`-compliant user-defined generic subclassing a single parametrized :mod:`typing` type. ''' pass # ....................{ PRIVATE ~ generics : single }.................... class _Pep484GenericUnsubscriptedSingle(List): ''' :pep:`484`-compliant user-defined generic subclassing a single unsubscripted :mod:`typing` type. ''' pass class _Pep484GenericUntypevaredShallowSingle(List[str]): ''' :pep:`484`-compliant user-defined generic subclassing a single unparametrized :mod:`typing` type. ''' pass class _Pep484GenericUntypevaredDeepSingle(List[List[str]]): ''' :pep:`484`-compliant user-defined generic subclassing a single unparametrized :mod:`typing` type, itself subclassing a single unparametrized :mod:`typing` type. ''' pass # ....................{ PRIVATE ~ generics : multiple }.................... class _Pep484GenericUntypevaredMultiple( CallableABC, ContextManager[str], Sequence[str]): ''' :pep:`484`-compliant user-defined generic subclassing multiple unparametrized :mod:`typing` types *and* a non-:mod:`typing` abstract base class (ABC). ''' # ..................{ INITIALIZERS }.................. def __init__(self, sequence: tuple) -> None: ''' Initialize this generic from the passed tuple. ''' assert isinstance(sequence, tuple), f'{repr(sequence)} not tuple.' self._sequence = sequence # ..................{ ABCs }.................. # Define all protocols mandated by ABCs subclassed by this generic above. def __call__(self) -> int: return len(self) def __contains__(self, obj: object) -> bool: return obj in self._sequence def __enter__(self) -> object: return self def __exit__(self, *args, **kwargs) -> bool: return False def __getitem__(self, index: int) -> object: return self._sequence[index] def __iter__(self) -> bool: return iter(self._sequence) def __len__(self) -> bool: return len(self._sequence) def __reversed__(self) -> object: return self._sequence.reverse() class _Pep484GenericTypevaredShallowMultiple(Iterable[T], Container[T]): ''' :pep:`484`-compliant user-defined generic subclassing multiple directly parametrized :mod:`typing` types. ''' # ..................{ INITIALIZERS }.................. def __init__(self, iterable: tuple) -> None: ''' Initialize this generic from the passed tuple. ''' assert isinstance(iterable, tuple), f'{repr(iterable)} not tuple.' self._iterable = iterable # ..................{ ABCs }.................. # Define all protocols mandated by ABCs subclassed by this generic above. def __contains__(self, obj: object) -> bool: return obj in self._iterable def __iter__(self) -> bool: return iter(self._iterable) class _Pep484GenericTypevaredDeepMultiple( SizedABC, Iterable[Tuple[S, T]], Container[Tuple[S, T]]): ''' :pep:`484`-compliant user-defined generic subclassing multiple indirectly parametrized :mod:`typing` types *and* a non-:mod:`typing` abstract base class (ABC). ''' # ..................{ INITIALIZERS }.................. def __init__(self, iterable: tuple) -> None: ''' Initialize this generic from the passed tuple. ''' assert isinstance(iterable, tuple), f'{repr(iterable)} not tuple.' self._iterable = iterable # ..................{ ABCs }.................. # Define all protocols mandated by ABCs subclassed by this generic above. def __contains__(self, obj: object) -> bool: return obj in self._iterable def __iter__(self) -> bool: return iter(self._iterable) def __len__(self) -> bool: return len(self._iterable) # ....................{ PRIVATE ~ forwardref }.................... _TEST_PEP484_FORWARDREF_CLASSNAME = ( 'beartype_test.a00_unit.data.data_type.Subclass') ''' Fully-qualified classname of an arbitrary class guaranteed to be importable. ''' _TEST_PEP484_FORWARDREF_TYPE = Subclass ''' Arbitrary class referred to by :data:`_PEP484_FORWARDREF_CLASSNAME`. ''' # ....................{ FIXTURES }.................... def hints_pep484_meta() -> 'List[HintPepMetadata]': ''' List of :pep:`484`-compliant **type hint metadata** (i.e., :class:`beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintPepMetadata` instances describing test-specific :pep:`484`-compliant sample type hints with metadata generically leveraged by various PEP-agnostic unit tests). ''' # ..................{ IMPORTS }.................. # Defer fixture-specific imports. from beartype import BeartypeConf from beartype.door import ( CallableTypeHint, NewTypeTypeHint, TypeVarTypeHint, UnionTypeHint, ) from beartype.roar import BeartypeDecorHintPep585DeprecationWarning from beartype._data.hint.pep.sign.datapepsigns import ( HintSignAny, HintSignByteString, HintSignCallable, HintSignContextManager, HintSignDefaultDict, HintSignDict, HintSignForwardRef, HintSignGeneric, HintSignHashable, HintSignList, HintSignMapping, HintSignMatch, HintSignMutableMapping, HintSignMutableSequence, HintSignNewType, HintSignNone, HintSignOptional, HintSignOrderedDict, HintSignPattern, HintSignSequence, HintSignSized, HintSignTuple, HintSignType, HintSignTypeVar, HintSignUnion, ) from beartype._util.py.utilpyversion import ( IS_PYTHON_AT_MOST_3_11, IS_PYTHON_AT_LEAST_3_10, IS_PYTHON_AT_LEAST_3_9, ) from beartype_test.a00_unit.data.hint.util.data_hintmetacls import ( HintPepMetadata, HintPithSatisfiedMetadata, HintPithUnsatisfiedMetadata, ) from collections import ( OrderedDict as OrderedDictType, defaultdict, ) # ..................{ LOCALS }.................. # List of all PEP-specific type hint metadata to be returned. hints_pep_meta = [] # Type of warning emitted by the @beartype decorator for PEP 484-compliant # type hints obsoleted by PEP 585, defined as either... PEP585_DEPRECATION_WARNING = ( # If the active Python interpreter targets Python >= 3.9 and thus # supports PEP 585 deprecating these hints, this warning type; BeartypeDecorHintPep585DeprecationWarning if IS_PYTHON_AT_LEAST_3_9 else # Else, the active Python interpreter targets Python < 3.9 and thus # fails to support PEP 585. In this case, "None". None ) # True only if unsubscripted typing attributes (i.e., public attributes of # the "typing" module without arguments) are parametrized by one or more # type variables under the active Python interpreter. # # This boolean is true for Python interpreters targeting Python < 3.9. # Prior to Python 3.9, the "typing" module parametrized most unsubscripted # typing attributes by default. Python 3.9 halted that barbaric practice by # leaving unsubscripted typing attributes unparametrized by default. _IS_TYPEVARS_HIDDEN = not IS_PYTHON_AT_LEAST_3_9 # True only if unsubscripted typing attributes (i.e., public attributes of # the "typing" module without arguments) are actually subscripted by one or # more type variables under the active Python interpreter. # # This boolean is true for Python interpreters targeting 3.6 < Python < # 3.9, oddly. (We don't make the rules. We simply complain about them.) _IS_ARGS_HIDDEN = False # ..................{ LISTS }.................. # Add PEP-specific type hint metadata to this list. hints_pep_meta.extend(( # ................{ UNSUBSCRIPTED }................ # Note that the PEP 484-compliant unsubscripted "NoReturn" type hint is # permissible *ONLY* as a return annotation and *MUST* thus be # exercised independently with special-purposed unit tests. # Unsubscripted "Any" singleton. HintPepMetadata( hint=Any, pep_sign=HintSignAny, is_ignorable=True, ), # Unsubscripted "Hashable" attribute. HintPepMetadata( hint=Hashable, pep_sign=HintSignHashable, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=HashableABC, piths_meta=( # String constant. HintPithSatisfiedMetadata( "Oh, importunate Θ Fortuna'd afforded"), # Tuple of string constants. HintPithSatisfiedMetadata(( 'Us vis‐a‐vis conduit fjords', 'Of weal‐th, and well‐heeled,', )), # List of string constants. HintPithUnsatisfiedMetadata([ 'Oboes‐obsoleting tines', 'Of language', ]), ), ), # Unsubscripted "Sized" attribute. HintPepMetadata( hint=Sized, pep_sign=HintSignSized, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=SizedABC, piths_meta=( # String constant. HintPithSatisfiedMetadata('Faire, a'), # Tuple of string constants. HintPithSatisfiedMetadata(( 'Farthing scrap', 'Of comfort’s ‘om’‐Enwrapped, rapt appeal — that', )), # Boolean constant. HintPithUnsatisfiedMetadata(False), ), ), # ................{ UNSUBSCRIPTED ~ forwardref }................ # Forward references defined below are *ONLY* intended to shallowly # exercise support for types of forward references across the codebase; # they are *NOT* intended to deeply exercise resolution of forward # references to undeclared classes, which requires more finesse. # # See the "data_hintref" submodule for the latter. # Unsubscripted forward reference defined as a simple string. HintPepMetadata( hint=_TEST_PEP484_FORWARDREF_CLASSNAME, pep_sign=HintSignForwardRef, is_type_typing=False, piths_meta=( # Instance of the class referred to by this reference. HintPithSatisfiedMetadata(_TEST_PEP484_FORWARDREF_TYPE()), # String object. HintPithUnsatisfiedMetadata( 'Empirical Ṗath after‐mathematically harvesting agro‐'), ), ), # Unsubscripted forward reference defined as a typing object. HintPepMetadata( hint=ForwardRef(_TEST_PEP484_FORWARDREF_CLASSNAME), pep_sign=HintSignForwardRef, piths_meta=( # Instance of the class referred to by this reference. HintPithSatisfiedMetadata(_TEST_PEP484_FORWARDREF_TYPE()), # String object. HintPithUnsatisfiedMetadata('Silvicultures of'), ), ), # ................{ UNSUBSCRIPTED ~ none }................ # Unsubscripted "None" singleton, which transparently reduces to # "types.NoneType". While not explicitly defined by the "typing" module, # PEP 484 explicitly supports this singleton: # When used in a type hint, the expression None is considered # equivalent to type(None). HintPepMetadata( hint=None, pep_sign=HintSignNone, is_type_typing=False, piths_meta=( # "None" singleton. HintPithSatisfiedMetadata(None), # String constant. HintPithUnsatisfiedMetadata('Betossing Bilious libidos, and'), ), ), # ................{ UNSUBSCRIPTED ~ typevar : unbound }................ # Unbounded type variable. HintPepMetadata( hint=T, pep_sign=HintSignTypeVar, typehint_cls=TypeVarTypeHint, #FIXME: Remove after fully supporting type variables. is_ignorable=True, is_typing=False, piths_meta=( # Builtin "int" class itself. HintPithSatisfiedMetadata(int), # String constant. HintPithSatisfiedMetadata('Oblate weapon Stacks (actually'), # By definition, all objects satisfy all unbounded type # variables. Ergo, we define *NO* "HintPithSatisfiedMetadata" # objects here. ), ), # ................{ UNSUBSCRIPTED ~ typevar : bound }................ # Constrained type variable declared by the "typing" module. HintPepMetadata( hint=AnyStr, pep_sign=HintSignTypeVar, typehint_cls=TypeVarTypeHint, #FIXME: Remove after fully supporting type variables. is_ignorable=True, piths_meta=( # String constant. HintPithSatisfiedMetadata('We were mysteries, unwon'), # Byte string constant. HintPithSatisfiedMetadata(b'We donned apportionments'), # Integer constant. HintPithUnsatisfiedMetadata(0x8BADF00D), # <-- 2343432205 # List of string constants. HintPithUnsatisfiedMetadata([ 'Of Politico‐policed diction maledictions,', 'Of that numeral addicts’ “—Additive game,” self‐', ]), ), ), # User-defined constrained type variable. HintPepMetadata( hint=T_CONSTRAINED, pep_sign=HintSignTypeVar, typehint_cls=TypeVarTypeHint, #FIXME: Remove after fully supporting type variables. is_ignorable=True, is_typing=False, piths_meta=( # String constant. HintPithSatisfiedMetadata('Prim (or'), # Byte string constant. HintPithSatisfiedMetadata( b'Primely positional) Quality inducements'), # Integer constant. HintPithUnsatisfiedMetadata(0xABADBABE), # <-- 2880289470 # List of string constants. HintPithUnsatisfiedMetadata([ 'Into lavishly crested, crestfallen ', 'epaulette‐cross‐pollinated st‐Ints,', ]), ), ), # User-defined bounded type variable. HintPepMetadata( hint=T_BOUNDED, pep_sign=HintSignTypeVar, typehint_cls=TypeVarTypeHint, #FIXME: Remove after fully supporting type variables. is_ignorable=True, is_typing=False, piths_meta=( # Integer constant. HintPithSatisfiedMetadata(0x0B00B135), # <-- 184594741 # String constant. HintPithUnsatisfiedMetadata( 'Light‐expectorating, aspectant ' 'thujone‐inspecting enswathement of' ), # List of integer constants. HintPithUnsatisfiedMetadata([0xBAAAAAAD, 0xBADDCAFE,]), ), ), # ................{ CALLABLE }................ # Callable accepting no parameters and returning a string. HintPepMetadata( hint=Callable[[], str], pep_sign=HintSignCallable, warning_type=PEP585_DEPRECATION_WARNING, typehint_cls=CallableTypeHint, isinstanceable_type=CallableABC, piths_meta=( # Lambda function returning a string constant. HintPithSatisfiedMetadata(lambda: 'Eudaemonia.'), # String constant. HintPithUnsatisfiedMetadata('...grant we heal'), ), ), # ................{ CONTEXTMANAGER }................ # Context manager yielding strings. HintPepMetadata( hint=ContextManager[str], pep_sign=HintSignContextManager, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=contextlib.AbstractContextManager, piths_meta=( # Context manager. HintPithSatisfiedMetadata( pith=lambda: context_manager_factory( 'We were mysteries, unwon'), is_context_manager=True, is_pith_factory=True, ), # String constant. HintPithUnsatisfiedMetadata('We donned apportionments'), ), ), # ................{ GENERATOR }................ # Note that testing generators requires creating generators, which # require a different syntax to that of standard callables; ergo, # generator type hints are tested elsewhere. # ................{ GENERICS ~ single }................ # Generic subclassing a single unsubscripted "typing" type. HintPepMetadata( hint=_Pep484GenericUnsubscriptedSingle, pep_sign=HintSignGeneric, warning_type=PEP585_DEPRECATION_WARNING, generic_type=_Pep484GenericUnsubscriptedSingle, is_type_typing=False, piths_meta=( # Subclass-specific generic list of string constants. HintPithSatisfiedMetadata(_Pep484GenericUnsubscriptedSingle(( 'Ibid., incredibly indelible, edible craws a', 'Of a liturgically upsurging, Θṙgiast‐ic holiness, and', ))), # String constant. HintPithUnsatisfiedMetadata( 'To pare their geognostic screeds'), # List of string constants. HintPithUnsatisfiedMetadata([ 'Of voluntary simplicities, Creed‐crinkled cities', 'Of a liberal quiet, and', ]), ), ), # Generic subclassing a single shallowly unparametrized "typing" type. HintPepMetadata( hint=_Pep484GenericUntypevaredShallowSingle, pep_sign=HintSignGeneric, warning_type=PEP585_DEPRECATION_WARNING, generic_type=_Pep484GenericUntypevaredShallowSingle, is_type_typing=False, piths_meta=( # Subclass-specific generic list of string constants. HintPithSatisfiedMetadata( _Pep484GenericUntypevaredShallowSingle(( 'Forgive our Vocation’s vociferous publications', 'Of', )) ), # String constant. HintPithUnsatisfiedMetadata( 'Hourly sybaritical, pub sabbaticals'), # List of string constants. HintPithUnsatisfiedMetadata([ 'Materially ostracizing, itinerant‐', 'Anchoretic digimonks initiating', ]), ), ), # Generic subclassing a single deeply unparametrized "typing" type. HintPepMetadata( hint=_Pep484GenericUntypevaredDeepSingle, pep_sign=HintSignGeneric, warning_type=PEP585_DEPRECATION_WARNING, generic_type=_Pep484GenericUntypevaredDeepSingle, is_type_typing=False, piths_meta=( # Subclass-specific generic list of list of string constants. HintPithSatisfiedMetadata( _Pep484GenericUntypevaredDeepSingle([ [ 'Intravenous‐averse effigy defamations, traversing', 'Intramurally venal-izing retro-', ], [ 'Versions of a ', "Version 2.2.a‐excursioned discursive Morningrise's ravenous ad-", ], ]) ), # String constant. HintPithUnsatisfiedMetadata('Vent of'), # List of string constants. HintPithUnsatisfiedMetadata([ "Ventral‐entrailed rurality's cinder-", 'Block pluralities of', ]), # Subclass-specific generic list of string constants. HintPithUnsatisfiedMetadata( _Pep484GenericUntypevaredDeepSingle([ 'Block-house stockade stocks, trailer', 'Park-entailed central heating, though those', ]) ), ), ), # Generic subclassing a single parametrized "typing" type. HintPepMetadata( hint=Pep484GenericTypevaredSingle, pep_sign=HintSignGeneric, generic_type=Pep484GenericTypevaredSingle, is_typevars=True, is_type_typing=False, piths_meta=( # Subclass-specific generic. HintPithSatisfiedMetadata(Pep484GenericTypevaredSingle()), # String constant. HintPithUnsatisfiedMetadata( 'An arterially giving, triage nature — ' 'like this agat‐adzing likeness' ), ), ), # Generic subclassing a single parametrized "typing" type, itself # parametrized by the same type variables in the same order. HintPepMetadata( hint=Pep484GenericTypevaredSingle[S, T], pep_sign=HintSignGeneric, generic_type=Pep484GenericTypevaredSingle, is_typevars=True, is_type_typing=True, is_typing=False, piths_meta=( # Subclass-specific generic. HintPithSatisfiedMetadata(Pep484GenericTypevaredSingle()), # String constant. HintPithUnsatisfiedMetadata( 'Token welfare’s malformed keening fare, keenly despaired' ), ), ), # ................{ GENERICS ~ multiple }................ # Generic subclassing multiple unparametrized "typing" types *AND* a # non-"typing" abstract base class (ABC). HintPepMetadata( hint=_Pep484GenericUntypevaredMultiple, pep_sign=HintSignGeneric, warning_type=PEP585_DEPRECATION_WARNING, generic_type=_Pep484GenericUntypevaredMultiple, is_type_typing=False, piths_meta=( # Subclass-specific generic 2-tuple of string constants. HintPithSatisfiedMetadata(_Pep484GenericUntypevaredMultiple(( 'Into a viscerally Eviscerated eras’ meditative hallways', 'Interrupting Soul‐viscous, vile‐ly Viceroy‐insufflating', ))), # String constant. HintPithUnsatisfiedMetadata('Initiations'), # 2-tuple of string constants. HintPithUnsatisfiedMetadata(( "Into a fat mendicant’s", 'Endgame‐defendant, dedicate rants', )), ), ), # Generic subclassing multiple parametrized "typing" types. HintPepMetadata( hint=_Pep484GenericTypevaredShallowMultiple, pep_sign=HintSignGeneric, warning_type=PEP585_DEPRECATION_WARNING, generic_type=_Pep484GenericTypevaredShallowMultiple, # is_args=False, is_typevars=True, is_type_typing=False, piths_meta=( # Subclass-specific generic iterable of string constants. HintPithSatisfiedMetadata( _Pep484GenericTypevaredShallowMultiple(( "Of foliage's everliving antestature —", 'In us, Leviticus‐confusedly drunk', )), ), # String constant. HintPithUnsatisfiedMetadata("In Usufructose truth's"), ), ), # Generic subclassing multiple indirectly parametrized "typing" types # *AND* a non-"typing" abstract base class (ABC). HintPepMetadata( hint=_Pep484GenericTypevaredDeepMultiple, pep_sign=HintSignGeneric, warning_type=PEP585_DEPRECATION_WARNING, generic_type=_Pep484GenericTypevaredDeepMultiple, # is_args=False, is_typevars=True, is_type_typing=False, piths_meta=( # Subclass-specific generic iterable of 2-tuples of string # constants. HintPithSatisfiedMetadata( _Pep484GenericTypevaredDeepMultiple(( ( 'Inertially tragicomipastoral, pastel anticandour —', 'remanding undemanding', ), ( 'Of a', '"hallow be Thy nameless', ), )), ), # String constant. HintPithUnsatisfiedMetadata('Invitations'), ), ), # Nested list of generics. HintPepMetadata( hint=List[_Pep484GenericUntypevaredMultiple], pep_sign=HintSignList, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=list, piths_meta=( # List of subclass-specific generic 2-tuples of string # constants. HintPithSatisfiedMetadata([ _Pep484GenericUntypevaredMultiple(( 'Stalling inevit‐abilities)', 'For carbined', )), _Pep484GenericUntypevaredMultiple(( 'Power-over (than', 'Power-with)', )), ]), # String constant. HintPithUnsatisfiedMetadata( 'that forced triforced, farcically carcinogenic Obelisks'), # List of 2-tuples of string constants. HintPithUnsatisfiedMetadata([ ( 'Obliterating their literate decency', 'Of a cannabis‐enthroning regency', ), ]), ), ), # ................{ LIST }................ # Unsubscripted "List" attribute. HintPepMetadata( hint=List, pep_sign=HintSignList, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=list, is_args=_IS_ARGS_HIDDEN, is_typevars=_IS_TYPEVARS_HIDDEN, piths_meta=( # Empty list, which satisfies all hint arguments by definition. HintPithSatisfiedMetadata([]), # Listing containing arbitrary items. HintPithSatisfiedMetadata([ 'Of an Autos‐respirating, ăutonomies‐gnashing machineries‐', 'Laxity, and taxonomic attainment', 3, ]), # String constant. HintPithUnsatisfiedMetadata('Of acceptance.'), # Tuple containing arbitrary items. HintPithUnsatisfiedMetadata(( 'Of their godliest Tellurion’s utterance —“Șuper‐ior!”;', '3. And Utter‐most, gutterly gut‐rending posts, glutton', 3.1415, )), ), ), # List of ignorable items. HintPepMetadata( hint=List[object], pep_sign=HintSignList, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=list, piths_meta=( # Empty list, which satisfies all hint arguments by definition. HintPithSatisfiedMetadata([]), # List of arbitrary objects. HintPithSatisfiedMetadata([ 'Of philomathematically bliss‐postulating Seas', 'Of actuarial postponement', 23.75, ]), # String constant. HintPithUnsatisfiedMetadata( 'Of actual change elevating alleviation — that'), ), ), # List of unignorable items. HintPepMetadata( hint=List[str], pep_sign=HintSignList, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=list, piths_meta=( # Empty list, which satisfies all hint arguments by definition. HintPithSatisfiedMetadata([]), # List of strings. HintPithSatisfiedMetadata([ 'Ously overmoist, ov‐ertly', 'Deverginating vertigo‐originating', ]), # String constant. HintPithUnsatisfiedMetadata('Devilet‐Sublet cities waxing'), # List containing exactly one integer. Since list items are # only randomly type-checked, only a list of exactly one item # enables us to match the explicit index at fault below. HintPithUnsatisfiedMetadata( pith=[1010011010,], # <-- oh, we've done it now # Match that the exception message raised for this object... exception_str_match_regexes=( # Declares the index of the random list item violating # this hint. r'\b[Ll]ist index \d+ item\b', # Preserves the value of this item as is. r'\s1010011010\s', ), ), ), ), # Generic list. HintPepMetadata( hint=List[T], pep_sign=HintSignList, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=list, is_typevars=True, piths_meta=( # Empty list, which satisfies all hint arguments by definition. HintPithSatisfiedMetadata([]), # List of strings. HintPithSatisfiedMetadata([ 'Lesion this ice-scioned', 'Legion', ]), # String constant. HintPithUnsatisfiedMetadata( 'Lest we succumb, indelicately, to'), ), ), # ................{ MAPPING ~ dict }................ # Unsubscripted "Dict" attribute. HintPepMetadata( hint=Dict, pep_sign=HintSignDict, warning_type=PEP585_DEPRECATION_WARNING, is_args=_IS_ARGS_HIDDEN, is_typevars=_IS_TYPEVARS_HIDDEN, isinstanceable_type=dict, piths_meta=( # Dictionary containing arbitrary key-value pairs. HintPithSatisfiedMetadata({ 'Of': 'our disappointment’s purse‐anointed ire', 'Offloading': '1. Coffer‐bursed statehood ointments;', }), # Set containing arbitrary items. HintPithUnsatisfiedMetadata({ '2. Disjointly jade‐ and Syndicate‐disbursed retirement funds,', 'Untiringly,' }), ), ), # Dictionary of unignorable key-value pairs. HintPepMetadata( hint=Dict[int, str], pep_sign=HintSignDict, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=dict, piths_meta=( # Dictionary mapping integers to strings. HintPithSatisfiedMetadata({ 1: 'For taxing', 2: "To a lax and golden‐rendered crucifixion, affix'd", }), # String constant. HintPithUnsatisfiedMetadata( 'To that beep‐prattling, LED‐ and lead-rattling crux'), # Dictionary mapping strings to strings. Since only the first # key-value pair of dictionaries are type-checked, a # dictionary of one key-value pair suffices. HintPithUnsatisfiedMetadata( pith={'Upon his cheek of death.': 'He wandered on'}, # Match that the exception message raised for this object # declares the key violating this hint. exception_str_match_regexes=( r"\bkey str 'Upon his cheek of death\.' ", ), # Match that the exception message raised for this object # does *NOT* declare the value of this key. exception_str_not_match_regexes=( r"\bvalue str 'He wandered on' ", ), ), ), ), # Dictionary of unignorable keys and ignorable values. HintPepMetadata( hint=Dict[str, object], pep_sign=HintSignDict, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=dict, piths_meta=( # Dictionary mapping strings to arbitrary objects. HintPithSatisfiedMetadata({ 'Till vast Aornos,': b"seen from Petra's steep", "Hung o'er the low horizon": b'like a cloud;', }), # String constant. HintPithUnsatisfiedMetadata( 'Through Balk, and where the desolated tombs'), # Dictionary mapping bytestrings to arbitrary objects. Since # only the first key-value pair of dictionaries are # type-checked, a dictionary of one key-value pair suffices. HintPithUnsatisfiedMetadata( pith={b'Of Parthian kings': 'scatter to every wind'}, # Match that the exception message raised for this object # declares the key violating this hint. exception_str_match_regexes=( r"\bkey bytes b'Of Parthian kings' ", ), # Match that the exception message raised for this object # does *NOT* declare the value of this key. exception_str_not_match_regexes=( r"\bvalue str 'scatter to every wind' ", ), ), ), ), # Dictionary of ignorable keys and unignorable values. HintPepMetadata( hint=Dict[object, str], pep_sign=HintSignDict, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=dict, piths_meta=( # Dictionary mapping arbitrary hashables to strings. HintPithSatisfiedMetadata({ 0xBEEFFADE: 'Their wasting dust, wildly he wandered on', 0xCAFEDEAF: 'Day after day a weary waste of hours,', }), # String constant. HintPithUnsatisfiedMetadata( 'Bearing within his life the brooding care'), # Dictionary mapping arbitrary hashables to bytestrings. Since # only the first key-value pair of dictionaries are # type-checked, a dictionary of one key-value pair suffices. HintPithUnsatisfiedMetadata( pith={'That ever fed on': b'its decaying flame.'}, # Match that the exception message raised for this object # declares both the key *AND* value violating this hint. exception_str_match_regexes=( r"\bkey str 'That ever fed on' ", r"\bvalue bytes b'its decaying flame\.' ", ), ), ), ), # Dictionary of ignorable key-value pairs. HintPepMetadata( hint=Dict[object, object], pep_sign=HintSignDict, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=dict, piths_meta=( # Dictionary mapping arbitrary hashables to arbitrary objects. HintPithSatisfiedMetadata({ 'And now his limbs were lean;': b'his scattered hair', 'Sered by the autumn of': b'strange suffering', }), # String constant. HintPithUnsatisfiedMetadata( 'Sung dirges in the wind; his listless hand'), ), ), # Generic dictionary. HintPepMetadata( hint=Dict[S, T], pep_sign=HintSignDict, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=dict, is_typevars=True, piths_meta=( # Dictionary mapping string keys to integer values. HintPithSatisfiedMetadata({ 'Less-ons"-chastened': 2, 'Chanson': 1, }), # String constant. HintPithUnsatisfiedMetadata('Swansong.'), ), ), # Nested dictionaries of tuples. HintPepMetadata( hint=Dict[Tuple[int, float], str], pep_sign=HintSignDict, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=dict, piths_meta=( # Dictionary mapping 2-tuples of integers and floating-point # numbers to strings. HintPithSatisfiedMetadata({ (0xBEEFBABE, 42.42): ( 'Obedient to the sweep of odorous winds'), }), # String constant. HintPithUnsatisfiedMetadata( 'Upon resplendent clouds, so rapidly'), # Dictionary mapping 2-tuples of integers and floating-point # numbers to byte strings. HintPithUnsatisfiedMetadata( pith={ (0xBABEBEEF, 24.24): ( b'Along the dark and ruffled waters fled'), }, # Match that the exception message raised for this object # declares all key-value pairs on the path to the value # violating this hint. exception_str_match_regexes=( r'\bkey tuple \(3133062895, 24.24\)', r"\bvalue bytes b'Along the dark and ruffled waters fled'", ), ), ), ), # Nested dictionaries of nested dictionaries of... you get the idea. HintPepMetadata( hint=Dict[int, Mapping[str, MutableMapping[bytes, bool]]], pep_sign=HintSignDict, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=dict, piths_meta=( # Dictionary mapping integers to dictionaries mapping strings to # dictionaries mapping bytes to booleans. HintPithSatisfiedMetadata({ 1: { 'Beautiful bird;': { b'thou voyagest to thine home,': False, }, }, }), # String constant. HintPithUnsatisfiedMetadata( 'Where thy sweet mate will twine her downy neck'), # Dictionary mapping integers to dictionaries mapping strings to # dictionaries mapping bytes to integers. Since only the first # key-value pair of dictionaries are type-checked, dictionaries # of one key-value pairs suffice. HintPithUnsatisfiedMetadata( pith={ 1: { 'With thine,': { b'and welcome thy return with eyes': 1, }, }, }, # Match that the exception message raised for this object # declares all key-value pairs on the path to the value # violating this hint. exception_str_match_regexes=( r'\bkey int 1\b', r"\bkey str 'With thine,' ", r"\bkey bytes b'and welcome thy return with eyes' ", r"\bvalue int 1\b", ), ), ), ), # ................{ MAPPING ~ defaultdict }................ # Unsubscripted "DefaultDict" attribute. HintPepMetadata( hint=DefaultDict, pep_sign=HintSignDefaultDict, warning_type=PEP585_DEPRECATION_WARNING, is_args=_IS_ARGS_HIDDEN, is_typevars=_IS_TYPEVARS_HIDDEN, isinstanceable_type=defaultdict, piths_meta=( # Default dictionary containing arbitrary key-value pairs. HintPithSatisfiedMetadata(default_dict_int_to_str), # Set containing arbitrary items. HintPithUnsatisfiedMetadata({ 'It rose as he approached, and with strong wings', 'Scaling the upward sky, bent its bright course', }), ), ), # Default dictionary of unignorable key-value pairs. HintPepMetadata( hint=DefaultDict[int, str], pep_sign=HintSignDefaultDict, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=defaultdict, piths_meta=( # Default dictionary mapping integers to strings. HintPithSatisfiedMetadata(default_dict_int_to_str), # String constant. HintPithUnsatisfiedMetadata('High over the immeasurable main.'), # Ordered dictionary mapping strings to strings. Since only the # first key-value pair of dictionaries are type-checked, a # dictionary of one key-value pair suffices. HintPithUnsatisfiedMetadata( pith=default_dict_str_to_str, # Match that the exception message raised for this object # declares the key violating this hint. exception_str_match_regexes=( r"\bkey str 'His eyes pursued its flight\.' ", ), # Match that the exception message raised for this object # does *NOT* declare the value of this key. exception_str_not_match_regexes=( r"\bvalue str 'Thou hast a home,' ", ), ), ), ), # ................{ MAPPING ~ mapping }................ # Unsubscripted "Mapping" attribute. HintPepMetadata( hint=Mapping, pep_sign=HintSignMapping, warning_type=PEP585_DEPRECATION_WARNING, is_args=_IS_ARGS_HIDDEN, is_typevars=_IS_TYPEVARS_HIDDEN, isinstanceable_type=MappingABC, piths_meta=( # Dictionary containing arbitrary key-value pairs. HintPithSatisfiedMetadata({ 'Hung like dead bone': b'within its withered skin;', b'Life, and the lustre': 'that consumed it, shone', }), # Set containing arbitrary items. HintPithUnsatisfiedMetadata({ 'As in a furnace burning secretly', 'From his dark eyes alone. The cottagers,', }), ), ), # Mapping of unignorable key-value pairs. HintPepMetadata( hint=Mapping[int, str], pep_sign=HintSignMapping, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=MappingABC, piths_meta=( # Dictionary mapping integers to strings. HintPithSatisfiedMetadata({ 1: 'Who ministered with human charity', 2: 'His human wants, beheld with wondering awe', }), # String constant. HintPithUnsatisfiedMetadata( 'Their fleeting visitant. The mountaineer,'), # Dictionary mapping strings to strings. Since only the first # key-value pair of dictionaries are type-checked, a # dictionary of one key-value pair suffices. HintPithUnsatisfiedMetadata( pith={'Encountering on': 'some dizzy precipice'}, # Match that the exception message raised for this object # declares the key violating this hint. exception_str_match_regexes=( r"\bkey str 'Encountering on' ", ), # Match that the exception message raised for this object # does *NOT* declare the value of this key. exception_str_not_match_regexes=( r"\bvalue str 'some dizzy precipice' ", ), ), ), ), # ................{ MAPPING ~ mutablemapping }................ # Unsubscripted "MutableMapping" attribute. HintPepMetadata( hint=MutableMapping, pep_sign=HintSignMutableMapping, warning_type=PEP585_DEPRECATION_WARNING, is_args=_IS_ARGS_HIDDEN, is_typevars=_IS_TYPEVARS_HIDDEN, isinstanceable_type=MutableMappingABC, piths_meta=( # Dictionary containing arbitrary key-value pairs. HintPithSatisfiedMetadata({ 'That spectral form,': b'deemed that the Spirit of wind', b'With lightning eyes,': 'and eager breath, and feet', }), # Set containing arbitrary items. HintPithUnsatisfiedMetadata({ 'Disturbing not the drifted snow, had paused', 'In its career: the infant would conceal', }), ), ), # Mapping of unignorable key-value pairs. HintPepMetadata( hint=MutableMapping[int, str], pep_sign=HintSignMutableMapping, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=MutableMappingABC, piths_meta=( # Dictionary mapping integers to strings. HintPithSatisfiedMetadata({ 1: "His troubled visage in his mother's robe", 2: 'In terror at the glare of those wild eyes,', }), # String constant. HintPithUnsatisfiedMetadata( 'To remember their strange light in many a dream'), # Dictionary mapping strings to strings. Since only the first # key-value pair of dictionaries are type-checked, a # dictionary of one key-value pair suffices. HintPithUnsatisfiedMetadata( pith={'Of after-times;': 'but youthful maidens, taught'}, # Match that the exception message raised for this object # declares the key violating this hint. exception_str_match_regexes=( r"\bkey str 'Of after-times;' ", ), # Match that the exception message raised for this object # does *NOT* declare the value of this key. exception_str_not_match_regexes=( r"\bvalue str 'but youthful maidens, taught' ", ), ), ), ), # ................{ MAPPING ~ ordereddict }................ # Unsubscripted "OrderedDict" attribute. HintPepMetadata( hint=OrderedDict, pep_sign=HintSignOrderedDict, warning_type=PEP585_DEPRECATION_WARNING, is_args=_IS_ARGS_HIDDEN, is_typevars=_IS_TYPEVARS_HIDDEN, isinstanceable_type=OrderedDictType, piths_meta=( # Ordered dictionary containing arbitrary key-value pairs. HintPithSatisfiedMetadata(OrderedDictType({ 'By nature,': b' would interpret half the woe', b'That wasted him,': 'would call him with false names', })), # Set containing arbitrary items. HintPithUnsatisfiedMetadata({ 'Brother, and friend, would press his pallid hand', 'At parting, and watch, dim through tears, the path', }), ), ), # Ordered dictionary of unignorable key-value pairs. HintPepMetadata( hint=OrderedDict[int, str], pep_sign=HintSignOrderedDict, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=OrderedDictType, piths_meta=( # Ordered dictionary mapping integers to strings. HintPithSatisfiedMetadata(OrderedDictType({ 1: "Of his departure from their father's door.", 2: 'At length upon the lone Chorasmian shore', })), # String constant. HintPithUnsatisfiedMetadata( 'He paused, a wide and melancholy waste'), # Ordered dictionary mapping strings to strings. Since only the # first key-value pair of dictionaries are type-checked, a # dictionary of one key-value pair suffices. HintPithUnsatisfiedMetadata( pith=OrderedDictType({ 'Of putrid marshes.': 'A strong impulse urged'}), # Match that the exception message raised for this object # declares the key violating this hint. exception_str_match_regexes=( r"\bkey str 'Of putrid marshes\.' ", ), # Match that the exception message raised for this object # does *NOT* declare the value of this key. exception_str_not_match_regexes=( r"\bvalue str 'A strong impulse urged' ", ), ), ), ), # ................{ NEWTYPE }................ # New type encapsulating a non-ignorable type. HintPepMetadata( hint=NewType('TotallyNotAStr', str), pep_sign=HintSignNewType, typehint_cls=NewTypeTypeHint, # "typing.NewType" type hints are always declared by that module. is_typing=True, # If the active Python interpreter targets: # * Python >= 3.10, "typing.NewType" type hints are instances of # that class -- which is thus declared by the "typing" module. # * Else, "typing.NewType" type hints are merely pure-Python # closures of the pure-Python function type -- which is *NOT* # declared by the "typing" module. is_type_typing=IS_PYTHON_AT_LEAST_3_10, piths_meta=( # String constant. HintPithSatisfiedMetadata('Ishmælite‐ish, aberrant control'), # Tuple of string constants. HintPithUnsatisfiedMetadata(( 'Of Common Street‐harrying barrens', 'Of harmony, harm’s abetting Harlem bedlam, and', )), ), ), # New type encapsulating a new type encapsulating a non-ignorable type. HintPepMetadata( hint=NewType('NewTypeBytes', NewType('TotallyNotABytes', bytes)), pep_sign=HintSignNewType, typehint_cls=NewTypeTypeHint, # "typing.NewType" type hints are always declared by that module. is_typing=True, # If the active Python interpreter targets: # * Python >= 3.10, "typing.NewType" type hints are instances of # that class -- which is thus declared by the "typing" module. # * Else, "typing.NewType" type hints are merely pure-Python # closures of the pure-Python function type -- which is *NOT* # declared by the "typing" module. is_type_typing=IS_PYTHON_AT_LEAST_3_10, piths_meta=( # Bytestring constant. HintPithSatisfiedMetadata( b"His rest and food. Nature's most secret steps"), # Tuple of bytestring constants. HintPithUnsatisfiedMetadata(( b"He like her shadow has pursued, where'er", b'The red volcano overcanopies', )), ), ), # ................{ REGEX ~ match }................ # Regular expression match of either strings or byte strings. HintPepMetadata( hint=Match, pep_sign=HintSignMatch, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=RegexMatchType, is_args=_IS_ARGS_HIDDEN, is_typevars=_IS_TYPEVARS_HIDDEN, piths_meta=( # Regular expression match of one or more string constants. HintPithSatisfiedMetadata(re.search( r'\b[a-z]+ance[a-z]+\b', 'æriferous Elements’ dance, entranced', )), # String constant. HintPithUnsatisfiedMetadata( 'Formless, demiurgic offerings, preliminarily,'), ), ), # Regular expression match of only strings. HintPepMetadata( hint=Match[str], pep_sign=HintSignMatch, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=RegexMatchType, piths_meta=( # Regular expression match of one or more string constants. HintPithSatisfiedMetadata(re.search( r'\b[a-z]+itiat[a-z]+\b', 'Vitiating novitiate Succubæ – a', )), # String constant. HintPithUnsatisfiedMetadata('Into Elitistly'), ), ), # ................{ REGEX ~ pattern }................ # Regular expression pattern of either strings or byte strings. HintPepMetadata( hint=Pattern, pep_sign=HintSignPattern, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=RegexCompiledType, is_args=_IS_ARGS_HIDDEN, is_typevars=_IS_TYPEVARS_HIDDEN, piths_meta=( # Regular expression string pattern. HintPithSatisfiedMetadata( re.compile(r'\b[A-Z]+ANCE[A-Z]+\b')), # String constant. HintPithUnsatisfiedMetadata('Legal indiscretions'), ), ), # Regular expression pattern of only strings. HintPepMetadata( hint=Pattern[str], pep_sign=HintSignPattern, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=RegexCompiledType, piths_meta=( # Regular expression string pattern. HintPithSatisfiedMetadata( re.compile(r'\b[A-Z]+ITIAT[A-Z]+\b')), # String constant. HintPithUnsatisfiedMetadata('Obsessing men'), ), ), # ................{ TUPLE }................ # Unsubscripted "Tuple" attribute. Note that this attribute is *NOT* # parametrized by one or more type variables under any Python version, # unlike most other unsubscripted "typing" attributes originating from # container types. Non-orthogonality, thy name is the "typing" module. HintPepMetadata( hint=Tuple, pep_sign=HintSignTuple, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=tuple, piths_meta=( # Tuple containing arbitrary items. HintPithSatisfiedMetadata(( 'a Steely dittied', 'Steel ‘phallus’ ballast', )), # List containing arbitrary items. HintPithUnsatisfiedMetadata([ 'In this Tellus‐cloistered, pre‐mature pop nomenclature', 'Of irremediable Media mollifications', ]), ), ), # ................{ TUPLE ~ fixed }................ # Empty tuple. Yes, this is ridiculous, useless, and non-orthogonal # with standard sequence syntax, which supports no comparable notion of # an "empty {insert-type-here}" (e.g., empty list). For example: # >>> from typing import List # >>> List[()] # TypeError: Too few parameters for List; actual 0, expected 1 # >>> List[[]] # TypeError: Parameters to generic types must be types. Got []. HintPepMetadata( hint=Tuple[()], pep_sign=HintSignTuple, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=tuple, piths_meta=( # Empty tuple. HintPithSatisfiedMetadata(()), # Non-empty tuple containing arbitrary items. HintPithUnsatisfiedMetadata( pith=( 'They shucked', '(Or huckstered, knightly rupturing veritas)', ), # Match that the exception message raised for this object... exception_str_match_regexes=( # Identify this tuple as non-empty. r'\bnon-empty\b', ), ), ), ), # Fixed-length tuple of only ignorable child hints. HintPepMetadata( hint=Tuple[Any, object,], pep_sign=HintSignTuple, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=tuple, piths_meta=( # Tuple containing arbitrary items. HintPithSatisfiedMetadata(( 'Surseance', 'Of sky, the God, the surly', )), # Tuple containing fewer items than required. HintPithUnsatisfiedMetadata( pith=('Obeisance',), # Match that the exception message raised for this object... exception_str_match_regexes=( # Compare this tuple's length to the expected length. r'\b1 != 2\b', ), ), ), ), # Fixed-length tuple of at least one ignorable child hint. HintPepMetadata( hint=Tuple[float, Any, str,], pep_sign=HintSignTuple, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=tuple, piths_meta=( # Tuple containing a floating-point number, string, and integer # (in that exact order). HintPithSatisfiedMetadata(( 20.09, 'Of an apoptosic T.A.R.P.’s torporific‐riven ecocide', "Nightly tolled, pindololy, ol'", )), # String constant. HintPithUnsatisfiedMetadata( 'Jangling (brinkmanship “Ironside”) jingoisms'), # Tuple containing fewer items than required. HintPithUnsatisfiedMetadata( pith=( 999.888, 'Obese, slipshodly muslin‐shod priests had maudlin solo', ), # Match that the exception message raised for this object... exception_str_match_regexes=( # Compare this tuple's length to the expected length. r'\b2 != 3\b', ), ), # Tuple containing a floating-point number, a string, and a # boolean (in that exact order). HintPithUnsatisfiedMetadata( pith=( 75.83, 'Unwholesome gentry ventings', False, ), # Match that the exception message raised for this object... exception_str_match_regexes=( # Declares the index and expected type of this fixed # tuple item *NOT* satisfying this hint. r'\b[Tt]uple index 2 item\b', r'\bstr\b', ), ), ), ), # Nested fixed-length tuple of at least one ignorable child hint. HintPepMetadata( hint=Tuple[Tuple[float, Any, str,], ...], pep_sign=HintSignTuple, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=tuple, piths_meta=( # Tuple containing tuples containing a floating-point number, # string, and integer (in that exact order). HintPithSatisfiedMetadata(( ( 90.02, 'Father — "Abstracted, OH WE LOVE YOU', 'Farther" — that', ), ( 2.9, 'To languidly Ent‐wine', 'Towards a timely, wines‐enticing gate', ), )), # Tuple containing a tuple containing fewer items than # required. HintPithUnsatisfiedMetadata(( ( 888.999, 'Oboes‐obsoleting tines', ), )), # Tuple containing a tuple containing a floating-point number, # string, and boolean (in that exact order). HintPithUnsatisfiedMetadata( pith=( ( 75.83, 'Vespers’ hymnal seance, invoking', True, ), ), # Match that the exception message raised for this # object... exception_str_match_regexes=( # Declares the index and expected type of a rondom # tuple item of a fixed tuple item *NOT* satisfying # this hint. r'\b[Tt]uple index \d+ item tuple index 2 item\b', r'\bstr\b', ), ), ), ), # Generic fixed-length tuple. HintPepMetadata( hint=Tuple[S, T], pep_sign=HintSignTuple, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=tuple, is_typevars=True, piths_meta=( # Tuple containing a floating-point number and string (in that # exact order). HintPithSatisfiedMetadata(( 33.77, 'Legal indiscretions', )), # String constant. HintPithUnsatisfiedMetadata('Leisurely excreted by'), # Tuple containing fewer items than required. HintPithUnsatisfiedMetadata(( 'Market states‐created, stark abscess', )), ), ), # ................{ TUPLE ~ variadic }................ # Variadic tuple. HintPepMetadata( hint=Tuple[str, ...], pep_sign=HintSignTuple, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=tuple, piths_meta=( # Tuple containing arbitrarily many string constants. HintPithSatisfiedMetadata(( 'Of a scantly raptured Overture,' 'Ur‐churlishly', )), # String constant. HintPithUnsatisfiedMetadata( 'Of Toll‐descanted grant money'), # Tuple containing exactly one integer. Since tuple items are # only randomly type-checked, only a tuple of exactly one item # enables us to match the explicit index at fault below. HintPithUnsatisfiedMetadata( pith=((53,)), # Match that the exception message raised for this # object... exception_str_match_regexes=( # Declares the index and expected type of a random # tuple item *NOT* satisfying this hint. r'\b[Tt]uple index \d+ item\b', r'\bstr\b', ), ), ), ), # Generic variadic tuple. HintPepMetadata( hint=Tuple[T, ...], pep_sign=HintSignTuple, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=tuple, is_typevars=True, piths_meta=( # Tuple containing arbitrarily many string constants. HintPithSatisfiedMetadata(( 'Loquacious s‐age, salaciously,', 'Of regal‐seeming, freemen‐sucking Hovels, a', )), # String constant. HintPithUnsatisfiedMetadata( 'Concubine enthralling contractually novel'), ), ), # ................{ TYPE }................ # Unsubscripted "Type" singleton. HintPepMetadata( hint=Type, pep_sign=HintSignType, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=type, is_args=_IS_ARGS_HIDDEN, is_typevars=_IS_TYPEVARS_HIDDEN, piths_meta=( # Transitive superclass of all superclasses. HintPithSatisfiedMetadata(object), # Arbitrary class. HintPithSatisfiedMetadata(str), # String constant. HintPithUnsatisfiedMetadata('Samely:'), ), ), # Any type, semantically equivalent under PEP 484 to the unsubscripted # "Type" singleton. HintPepMetadata( hint=Type[Any], pep_sign=HintSignType, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=type, piths_meta=( # Arbitrary class. HintPithSatisfiedMetadata(bool), # String constant. HintPithUnsatisfiedMetadata('Coulomb‐lobed lobbyist’s Ģom'), ), ), # "type" superclass, semantically equivalent to the unsubscripted # "Type" singleton. HintPepMetadata( hint=Type[type], pep_sign=HintSignType, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=type, piths_meta=( # Arbitrary class. HintPithSatisfiedMetadata(complex), # String constant. HintPithUnsatisfiedMetadata('Had al-'), ), ), # Specific class. HintPepMetadata( hint=Type[Class], pep_sign=HintSignType, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=type, piths_meta=( # Subclass of this class. HintPithSatisfiedMetadata(Subclass), # String constant. HintPithUnsatisfiedMetadata('Namely,'), # Non-subclass of this class. HintPithUnsatisfiedMetadata(str), ), ), # Specific class deferred with a forward reference. HintPepMetadata( hint=Type[_TEST_PEP484_FORWARDREF_CLASSNAME], pep_sign=HintSignType, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=type, piths_meta=( # Subclass of this class. HintPithSatisfiedMetadata(SubclassSubclass), # String constant. HintPithUnsatisfiedMetadata('Jabbar‐disbarred'), # Non-subclass of this class. HintPithUnsatisfiedMetadata(dict), ), ), # Two or more specific classes. HintPepMetadata( hint=Type[Union[Class, OtherClass,]], pep_sign=HintSignType, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=type, piths_meta=( # Arbitrary subclass of one class subscripting this hint. HintPithSatisfiedMetadata(Subclass), # Arbitrary subclass of another class subscripting this hint. HintPithSatisfiedMetadata(OtherSubclass), # String constant. HintPithUnsatisfiedMetadata('Jabberings'), # Non-subclass of any classes subscripting this hint. HintPithUnsatisfiedMetadata(set), ), ), # Generic class. HintPepMetadata( hint=Type[T], pep_sign=HintSignType, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=type, is_typevars=True, piths_meta=( # Arbitrary class. HintPithSatisfiedMetadata(int), # String constant. HintPithUnsatisfiedMetadata('Obligation, and'), ), ), # ................{ UNION }................ # Note that unions of one argument (e.g., "Union[str]") *CANNOT* be # listed here, as the "typing" module implicitly reduces these unions # to only that argument (e.g., "str") on our behalf. # # Thanks. Thanks alot, "typing". # #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: The Python < 3.7.0-specific implementations of "Union" # are defective, in that they silently filter out various subscripted # arguments that they absolutely should *NOT*, including "bool": e.g., # $ python3.6 # >>> import typing # >>> Union[bool, float, int, Sequence[ # ... Union[bool, float, int, Sequence[str]]]] # Union[float, int, Sequence[Union[float, int, Sequence[str]]]] # For this reason, these arguments *MUST* be omitted below. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Ignorable unsubscripted "Union" attribute. HintPepMetadata( hint=Union, pep_sign=HintSignUnion, typehint_cls=UnionTypeHint, is_ignorable=True, ), # Union of one non-"typing" type and an originative "typing" type, # exercising a prominent edge case when raising human-readable # exceptions describing the failure of passed parameters or returned # values to satisfy this union. HintPepMetadata( hint=Union[int, Sequence[str]], pep_sign=HintSignUnion, typehint_cls=UnionTypeHint, warning_type=PEP585_DEPRECATION_WARNING, piths_meta=( # Integer constant. HintPithSatisfiedMetadata(21), # Sequence of string items. HintPithSatisfiedMetadata(( 'To claim all ͼarth a number, penumbraed' 'By blessed Pendragon’s flagon‐bedraggling constancies', )), # Floating-point constant. # # Note that a string constant is intentionally *NOT* listed # here, as strings are technically sequences of strings of # length one commonly referred to as Unicode code points or # simply characters. HintPithUnsatisfiedMetadata( pith=802.11, # Match that the exception message raised for this object # declares the types *NOT* satisfied by this object. exception_str_match_regexes=( r'\bSequence\b', r'\bint\b', ), # Match that the exception message raised for this object # does *NOT* contain a newline or bullet delimiter. exception_str_not_match_regexes=( r'\n', r'\*', ), ), # Tuple of integers. HintPithUnsatisfiedMetadata( pith=(1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89,), # Match that the exception message raised for this # object... exception_str_match_regexes=( # Contains a bullet point declaring the non-"typing" # type *NOT* satisfied by this object. r'\n\*\s.*\bint\b', # Contains a bullet point declaring the index of the # random tuple item *NOT* satisfying this hint. r'\n\*\s.*\b[Tt]uple index \d+ item\b', ), ), ), ), # Union of three non-"typing" types and an originative "typing" type of # a union of three non-"typing" types and an originative "typing" type, # exercising a prominent edge case when raising human-readable # exceptions describing the failure of passed parameters or returned # values to satisfy this union. HintPepMetadata( hint=Union[dict, float, int, Sequence[Union[dict, float, int, MutableSequence[str]]]], pep_sign=HintSignUnion, warning_type=PEP585_DEPRECATION_WARNING, typehint_cls=UnionTypeHint, piths_meta=( # Empty dictionary. HintPithSatisfiedMetadata({}), # Floating-point number constant. HintPithSatisfiedMetadata(777.777), # Integer constant. HintPithSatisfiedMetadata(777), # Sequence of dictionary, floating-point number, integer, and # sequence of string constant items. HintPithSatisfiedMetadata(( # Non-empty dictionary. { 'Of': 'charnal memories,', 'Or': 'coterminously chordant‐disarmed harmonies', }, # Floating-point number constant. 666.666, # Integer constant. 666, # Mutable sequence of string constants. [ 'Ansuded scientifically pontifical grapheme‐', 'Denuded hierography, professedly, to emulate ascen-', ], )), # Complex number constant. HintPithUnsatisfiedMetadata( pith=356+260j, # Match that the exception message raised for this object # declares the types *NOT* satisfied by this object. exception_str_match_regexes=( r'\bSequence\b', r'\bdict\b', r'\bfloat\b', r'\bint\b', ), # Match that the exception message raised for this object # does *NOT* contain a newline or bullet delimiter. exception_str_not_match_regexes=( r'\n', r'\*', ), ), # Sequence of bytestring items. HintPithUnsatisfiedMetadata( pith=(b"May they rest their certainties' Solicitousness to",), # Match that the exception message raised for this # object... exception_str_match_regexes=( # Contains a bullet point declaring one of the # non-"typing" types *NOT* satisfied by this object. r'\n\*\s.*\bint\b', # Contains a bullet point declaring the index of the # random tuple item *NOT* satisfying this hint. r'\n\*\s.*\b[Tt]uple index \d+ item\b', ), ), # Sequence of mutable sequences of bytestring items. HintPithUnsatisfiedMetadata( pith=([b'Untaint these ties',],), # Match that the exception message raised for this # object... exception_str_match_regexes=( # Contains an unindented bullet point declaring one of # the non-"typing" types unsatisfied by this object. r'\n\*\s.*\bfloat\b', # Contains an indented bullet point declaring one of # the non-"typing" types unsatisfied by this object. r'\n\s+\*\s.*\bint\b', # Contains an unindented bullet point declaring the # index of the random tuple item *NOT* satisfying # this hint. r'\n\*\s.*\b[Tt]uple index \d+ item\b', # Contains an indented bullet point declaring the index # of the random list item *NOT* satisfying this hint. r'\n\s+\*\s.*\b[Ll]ist index \d+ item\b', ), ), ), ), # Union of one non-"typing" type and one concrete generic. HintPepMetadata( hint=Union[str, Iterable[Tuple[S, T]]], pep_sign=HintSignUnion, typehint_cls=UnionTypeHint, is_typevars=True, warning_type=PEP585_DEPRECATION_WARNING, piths_meta=( # String constant. HintPithSatisfiedMetadata( "O'er the wide aëry wilderness: thus driven"), # Iterable of 2-tuples of arbitrary items. HintPithSatisfiedMetadata([ ('By the bright shadow', 'of that lovely dream,',), (b'Beneath the cold glare', b'of the desolate night,'), ]), # Integer constant. HintPithUnsatisfiedMetadata( pith=0xCAFEFEED, # Match that the exception message raised for this object # declares the types *NOT* satisfied by this object. exception_str_match_regexes=( r'\bstr\b', r'\bIterable\b', ), # Match that the exception message raised for this object # does *NOT* contain a newline or bullet delimiter. exception_str_not_match_regexes=( r'\n', r'\*', ), ), ), ), # Union of *ONLY* subscripted type hints, exercising an edge case. HintPepMetadata( hint=Union[List[str], Tuple[bytes, ...]], pep_sign=HintSignUnion, typehint_cls=UnionTypeHint, warning_type=PEP585_DEPRECATION_WARNING, piths_meta=( # List of string items. HintPithSatisfiedMetadata( ['Through tangled swamps', 'and deep precipitous dells,']), # Tuples of bytestring items. HintPithSatisfiedMetadata( (b'Startling with careless step', b'the moonlight snake,')), # Integer constant. HintPithUnsatisfiedMetadata( pith=0xFEEDBEEF, # Match that the exception message raised for this object # declares the types *NOT* satisfied by this object. exception_str_match_regexes=( r'\blist\b', r'\btuple\b', ), # Match that the exception message raised for this object # does *NOT* contain a newline or bullet delimiter. exception_str_not_match_regexes=( r'\n', r'\*', ), ), ), ), # ................{ UNION ~ nested }................ # Nested unions exercising edge cases induced by Python >= 3.8 # optimizations leveraging PEP 572-style assignment expressions. # Nested union of multiple non-"typing" types. HintPepMetadata( hint=List[Union[int, str,]], pep_sign=HintSignList, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=list, piths_meta=( # List containing a mixture of integer and string constants. HintPithSatisfiedMetadata([ 'Un‐seemly preening, pliant templar curs; and', 272, ]), # String constant. HintPithUnsatisfiedMetadata( pith='Un‐seemly preening, pliant templar curs; and', # Match that the exception message raised for this object # declares the types *NOT* satisfied by this object. exception_str_match_regexes=( r'\bint\b', r'\bstr\b', ), # Match that the exception message raised for this object # does *NOT* contain a newline or bullet delimiter. exception_str_not_match_regexes=( r'\n', r'\*', ), ), # List of bytestring items. HintPithUnsatisfiedMetadata( pith=[ b'Blamelessly Slur-chastened rights forthwith, affrighting', b"Beauty's lurid, beleaguered knolls, eland-leagued and", ], # Match that the exception message raised for this # object... exception_str_match_regexes=( # Declares all non-"typing" types *NOT* satisfied by a # random list item *NOT* satisfying this hint. r'\bint\b', r'\bstr\b', # Declares the index of the random list item *NOT* # satisfying this hint. r'\b[Ll]ist index \d+ item\b', ), ), ), ), # Nested union of one non-"typing" type and one "typing" type. HintPepMetadata( hint=Sequence[Union[str, bytes]], pep_sign=HintSignSequence, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=SequenceABC, piths_meta=( # Sequence of string and bytestring constants. HintPithSatisfiedMetadata(( b'For laconically formulaic, knavish,', u'Or sordidly sellsword‐', f'Horded temerities, bravely unmerited', )), # Integer constant. HintPithUnsatisfiedMetadata( pith=7898797, # Match that the exception message raised for this object # declares the types *NOT* satisfied by this object. exception_str_match_regexes=( r'\bbytes\b', r'\bstr\b', ), # Match that the exception message raised for this object # does *NOT* contain a newline or bullet delimiter. exception_str_not_match_regexes=( r'\n', r'\*', ), ), # Sequence of integer items. HintPithUnsatisfiedMetadata( pith=((144, 233, 377, 610, 987, 1598, 2585, 4183, 6768,)), # Match that the exception message raised for this object... exception_str_match_regexes=( # Declares all non-"typing" types *NOT* satisfied by a # random tuple item *NOT* satisfying this hint. r'\bbytes\b', r'\bstr\b', # Declares the index of the random tuple item *NOT* # satisfying this hint. r'\b[Tt]uple index \d+ item\b', ), ), ), ), # Nested union of *NO* isinstanceable type and multiple "typing" types. HintPepMetadata( hint=MutableSequence[Union[bytes, Callable]], pep_sign=HintSignMutableSequence, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=MutableSequenceABC, piths_meta=( # Mutable sequence of string and bytestring constants. HintPithSatisfiedMetadata([ b"Canonizing Afrikaans-kennelled Mine canaries,", lambda: 'Of a floridly torrid, hasty love — that league', ]), # String constant. HintPithUnsatisfiedMetadata( pith='Effaced.', # Match that the exception message raised for this object # declares the types *NOT* satisfied by this object. exception_str_match_regexes=( r'\bbytes\b', r'\bCallable\b', ), # Match that the exception message raised for this object # does *NOT* contain a newline or bullet delimiter. exception_str_not_match_regexes=( r'\n', r'\*', ), ), # Mutable sequence of string constants. HintPithUnsatisfiedMetadata( pith=[ 'Of genteel gentle‐folk — that that Ƹsper', 'At my brand‐defaced, landless side', ], # Match that the exception message raised for this # object... exception_str_match_regexes=( # Declares all non-"typing" types *NOT* satisfied by a # random list item *NOT* satisfying this hint. r'\bbytes\b', r'\bCallable\b', # Declares the index of the random list item *NOT* # satisfying this hint. r'\b[Ll]ist index \d+ item\b', ), ), ), ), # ................{ UNION ~ optional }................ # Ignorable unsubscripted "Optional" attribute. HintPepMetadata( hint=Optional, pep_sign=HintSignOptional, typehint_cls=UnionTypeHint, is_ignorable=True, ), # Optional isinstance()-able "typing" type. HintPepMetadata( hint=Optional[Sequence[str]], # Subscriptions of the "Optional" attribute reduce to # fundamentally different unsubscripted typing attributes depending # on Python version. Specifically, under: # * Python >= 3.9, the "Optional" and "Union" attributes are # distinct. # * Python < 3.9, the "Optional" and "Union" attributes are *NOT* # distinct. The "typing" module implicitly reduces *ALL* # subscriptions of the "Optional" attribute by the corresponding # "Union" attribute subscripted by both that argument and # "type(None)". Ergo, there effectively exists *NO* # "Optional" attribute under older Python versions. pep_sign=( HintSignOptional if IS_PYTHON_AT_LEAST_3_9 else HintSignUnion), warning_type=PEP585_DEPRECATION_WARNING, typehint_cls=UnionTypeHint, piths_meta=( # None singleton. HintPithSatisfiedMetadata(None), # Sequence of string items. HintPithSatisfiedMetadata(( 'Of cuticular currents (...wide, wildly articulate,', 'And canting free, physico-stipulatingly) -', )), # Floating-point constant. # # Note that a string constant is intentionally *NOT* listed # here, as strings are technically sequences of strings of # length one commonly referred to as Unicode code points or # simply characters. HintPithUnsatisfiedMetadata( pith=802.2, # Match that the exception message raised for this object # declares the types *NOT* satisfied by this object. exception_str_match_regexes=( r'\bNoneType\b', r'\bSequence\b', ), # Match that the exception message raised for this object # does *NOT* contain a newline or bullet delimiter. exception_str_not_match_regexes=( r'\n', r'\*', ), ), ), ), # ................{ UNION ~ tower }................ # Type hints pertaining to the implicit numeric tower (i.e., optional # PEP 484-compliant (sub)standard in which type hints defined as broad # numeric types implicitly match all narrower numeric types as well by # enabling the "beartype.BeartypeConf.is_pep484_tower" parameter). When # enabled, @beartype implicitly expands: # * "float" to "float | int". # * "complex" to "complex | float | int". # # See also the "_data_nonpep484" submodule, which defines additional # PEP 484-compliant raw types pertaining to the implicit numeric tower # (e.g., "float", "complex"). # Implicit numeric tower type *AND* an arbitrary type hint outside the # implicit numeric tower with with the implicit numeric tower disabled. HintPepMetadata( hint=Union[float, Sequence[str]], pep_sign=HintSignUnion, warning_type=PEP585_DEPRECATION_WARNING, typehint_cls=UnionTypeHint, piths_meta=( # Floating-point constant. HintPithSatisfiedMetadata(42.4242424242424242), # Sequence of string items. HintPithSatisfiedMetadata(( 'No sister-flower would be forgiven', 'If it disdained its brother;', )), # Integer constant. HintPithUnsatisfiedMetadata( pith=0xBABEBABE, # <-- 3133061822 # Match that the exception message raised for this object # declares the types *NOT* satisfied by this object. exception_str_match_regexes=( r'\bfloat\b', r'\bSequence\b', ), # Match that the exception message raised for this object # does *NOT* contain a newline or bullet delimiter. exception_str_not_match_regexes=( r'\n', r'\*', ), ), ), ), # Implicit numeric tower type *AND* an arbitrary type hint outside the # implicit numeric tower with with the implicit numeric tower enabled. HintPepMetadata( hint=Union[float, Sequence[str]], conf=BeartypeConf(is_pep484_tower=True), pep_sign=HintSignUnion, warning_type=PEP585_DEPRECATION_WARNING, typehint_cls=UnionTypeHint, piths_meta=( # Floating-point constant. HintPithSatisfiedMetadata(24.2424242424242424), # Integer constant. HintPithSatisfiedMetadata(0xABBAABBA), # <-- 2881137594 # Sequence of string items. HintPithSatisfiedMetadata(( 'And the sunlight clasps the earth', 'And the moonbeams kiss the sea:', )), # Complex constant. HintPithUnsatisfiedMetadata( pith=42 + 24j, # Match that the exception message raised for this object # declares the types *NOT* satisfied by this object. exception_str_match_regexes=( r'\bfloat\b', r'\bSequence\b', ), # Match that the exception message raised for this object # does *NOT* contain a newline or bullet delimiter. exception_str_not_match_regexes=( r'\n', r'\*', ), ), ), ), )) # ....................{ VERSION }.................... # PEP-compliant type hints conditionally dependent on the major version of # Python targeted by the active Python interpreter. # If the active Python interpreter targets at least Python <= 3.9... if IS_PYTHON_AT_LEAST_3_9: hints_pep_meta.append( # ..............{ GENERICS ~ user }.............. # Subscripted generic subclassing a single unsubscripted "typing" # type. Note that these types constitute an edge case supported # *ONLY* under Python >= 3.9, which implements these tests in an # ambiguous (albeit efficient) manner effectively indistinguishable # from PEP 585-compliant type hints. HintPepMetadata( hint=_Pep484GenericUnsubscriptedSingle[str], pep_sign=HintSignGeneric, warning_type=PEP585_DEPRECATION_WARNING, generic_type=_Pep484GenericUnsubscriptedSingle, is_type_typing=False, piths_meta=( # Subclass-specific generic list of string constants. HintPithSatisfiedMetadata( _Pep484GenericUnsubscriptedSingle(( 'Volubly vi‐brant libations', 'To blubber‐lubed Bacchus — hustling', )) ), # String constant. HintPithUnsatisfiedMetadata('O’ the frock'), # List of string constants. HintPithUnsatisfiedMetadata([ 'O’ Friday’s squealing — Sounding', 'Freedom’s unappealing, Passive delights', ]), ), ) ) # If the active Python interpreter targets at most Python <= 3.11... if IS_PYTHON_AT_MOST_3_11: # ..................{ IMPORTS }.................. # Defer importation of standard PEP 484-specific type hint factories # deprecated under Python >= 3.12. from collections.abc import ByteString as ByteStringABC from typing import ByteString # ..................{ LISTS }.................. # Add Python <= 3.11-specific type hint metadata to this list. hints_pep_meta.append( # ................{ UNSUBSCRIPTED }................ # Unsubscripted "ByteString" singleton. Bizarrely, note that: # * "collections.abc.ByteString" is subscriptable under PEP 585. # * "typing.ByteString" is *NOT* subscriptable under PEP 484. # # Since neither PEP 484 nor 585 comment on "ByteString" in # detail (or at all, really), this non-orthogonality remains # inexplicable, frustrating, and utterly unsurprising. We elect # to merely shrug. HintPepMetadata( hint=ByteString, pep_sign=HintSignByteString, warning_type=PEP585_DEPRECATION_WARNING, isinstanceable_type=ByteStringABC, piths_meta=( # Byte string constant. HintPithSatisfiedMetadata( b'By nautical/particle consciousness'), # Byte array initialized from a byte string constant. HintPithSatisfiedMetadata( bytearray( b"Hour's straight fates, (distemperate-ly)")), # String constant. HintPithUnsatisfiedMetadata( 'At that atom-nestled canticle'), ), ) ) # ..................{ RETURN }.................. # Return this list of all PEP-specific type hint metadata. return hints_pep_meta def hints_pep484_ignorable_shallow() -> list: ''' List of :pep:`544`-compliant **shallowly ignorable type hints** (i.e., ignorable on the trivial basis of their machine-readable representations). ''' # Return this list of all PEP-specific shallowly ignorable type hints. return [ # The "Any" catch-all. By definition, *ALL* objects annotated as "Any" # unconditionally satisfy this catch-all and thus semantically reduce to # unannotated objects. Any, # The root "object" superclass, which *ALL* objects annotated as # "object" unconditionally satisfy under isinstance()-based type # covariance and thus semantically reduce to unannotated objects. # "object" is equivalent to the "typing.Any" type hint singleton. object, # The "Generic" superclass imposes no constraints and is thus also # semantically synonymous with the ignorable PEP-noncompliant # "beartype.cave.AnyType" and hence "object" types. Since PEP 484 # stipulates that *ANY* unsubscripted subscriptable PEP-compliant # singleton including "typing.Generic" semantically expands to that # singelton subscripted by an implicit "Any" argument, "Generic" # semantically expands to the implicit "Generic[Any]" singleton. Generic, ] def hints_pep484_ignorable_deep() -> list: ''' List of :pep:`544`-compliant **deeply ignorable type hints** (i.e., ignorable only on the non-trivial basis of their nested child type hints). ''' # Return this list of all PEP-specific shallowly ignorable type hints. return [ # Parametrizations of the "typing.Generic" abstract base class (ABC). Generic[S, T], # New type aliasing any ignorable type hint. NewType('TotallyNotAny', Any), NewType('TotallyNotObject', object), # Optionals containing any ignorable type hint. Optional[Any], Optional[object], # Unions containing any ignorable type hint. Union[Any, float, str,], Union[complex, int, object,], ] beartype-0.18.5/beartype_test/a00_unit/data/hint/util/000077500000000000000000000000001461113517100225415ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/hint/util/__init__.py000066400000000000000000000000001461113517100246400ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/hint/util/data_hintmetacls.py000066400000000000000000000555041461113517100264300ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Testing-specific **type hint metadata class hierarchy** (i.e., hierarchy of classes encapsulating sample type hints instantiated by the :mod:`beartype_test.a00_unit.data.hint` submodules). ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype.typing import ( Iterable, Optional, Type, ) from beartype._conf.confcls import ( BEARTYPE_CONF_DEFAULT, BeartypeConf, ) from collections.abc import Iterable as IterableABC # ....................{ CLASSES ~ hint : [un]satisfied }.................... class HintPithSatisfiedMetadata(object): ''' **Type hint satisfied pith metadata** (i.e., dataclass whose instance variables describe an object satisfying a type hint when either passed as a parameter *or* returned as a value annotated by that hint). Attributes ---------- pith : object Arbitrary object *not* satisfying this hint when either passed as a parameter *or* returned as a value annotated by this hint. is_context_manager : bool If this pith is a **context manager** (i.e., object defining both the ``__exit__`` and ``__enter__`` dunder methods required to satisfy the context manager protocol), this boolean is either: * :data:`True` if callers should preserve this context manager as is (e.g., by passing this context manager to the decorated callable). * :data:`False` if callers should safely open and close this context manager to its context *and* replace this context manager with that context (e.g., by passing this context to the decorated callable). If this pith is *not* a context manager, this boolean is ignored. Defaults to :data:`False`. is_pith_factory : bool :data:`True` only if this pith is actually a **pith factory** (i.e., callable accepting *no* parameters and dynamically creating and returning the value to be used as the desired pith, presumably by passing this value to the decorated callable). Defaults to :data:`False`. ''' # ..................{ INITIALIZERS }.................. def __init__( self, # Mandatory parameters. pith: object, # Optional parameters. is_context_manager: bool = False, is_pith_factory: bool = False, ) -> None: assert isinstance(is_context_manager, bool), ( f'{repr(is_context_manager)} not boolean.') assert isinstance(is_pith_factory, bool), ( f'{repr(is_pith_factory)} not boolean.') # Classify all passed parameters. self.pith = pith self.is_context_manager = is_context_manager self.is_pith_factory = is_pith_factory # ..................{ STRINGIFIERS }.................. def __repr__(self) -> str: return '\n'.join(( f'{self.__class__.__name__}(', f' pith={repr(self.pith)},', f' is_context_manager={repr(self.is_context_manager)},', f' is_pith_factory={repr(self.is_pith_factory)},', f')', )) class HintPithUnsatisfiedMetadata(HintPithSatisfiedMetadata): ''' **Type hint unsatisfied pith metadata** (i.e., dataclass whose instance variables describe an object *not* satisfying a type hint when either passed as a parameter *or* returned as a value annotated by that hint). Attributes ---------- exception_str_match_regexes : Iterable[str] Iterable of zero or more r''-style uncompiled regular expression strings, each matching a substring of the exception message expected to be raised by wrapper functions when either passed or returning this ``pith``. Defaults to the empty tuple. exception_str_not_match_regexes : Iterable[str] Iterable of zero or more r''-style uncompiled regular expression strings, each *not* matching a substring of the exception message expected to be raised by wrapper functions when either passed or returning this ``pith``. Defaults to the empty tuple. ''' # ..................{ INITIALIZERS }.................. def __init__( self, *args, # Optional parameters. exception_str_match_regexes: Iterable[str] = (), exception_str_not_match_regexes: Iterable[str] = (), **kwargs ) -> None: assert isinstance(exception_str_match_regexes, IterableABC), ( f'{repr(exception_str_match_regexes)} not iterable.') assert isinstance(exception_str_not_match_regexes, IterableABC), ( f'{repr(exception_str_not_match_regexes)} not iterable.') assert all( isinstance(exception_str_match_regex, str) for exception_str_match_regex in exception_str_match_regexes ), f'{repr(exception_str_match_regexes)} not iterable of regexes.' assert all( isinstance(exception_str_not_match_regex, str) for exception_str_not_match_regex in ( exception_str_not_match_regexes) ), f'{repr(exception_str_not_match_regexes)} not iterable of regexes.' # Initialize our superclass with all variadic parameters. super().__init__(*args, **kwargs) # Classify all remaining passed parameters. self.exception_str_not_match_regexes = exception_str_not_match_regexes # Classify the tuple of all r''-style uncompiled regular expression # strings appended by the tuple of all mandatory such strings. self.exception_str_match_regexes = ( exception_str_match_regexes + _EXCEPTION_STR_MATCH_REGEXES_MANDATORY ) # ..................{ STRINGIFIERS }.................. def __repr__(self) -> str: return '\n'.join(( f'{self.__class__.__name__}(', f' pith={repr(self.pith)},', f' is_context_manager={repr(self.is_context_manager)},', f' is_pith_factory={repr(self.is_pith_factory)},', f' exception_str_match_regexes={repr(self.exception_str_match_regexes)},', f' exception_str_not_match_regexes={repr(self.exception_str_not_match_regexes)},', f')', )) # ....................{ CLASSES ~ hint : superclass }.................... class HintNonpepMetadata(object): ''' **PEP-noncompliant type hint metadata** (i.e., dataclass whose instance variables describe a type hint that is either PEP-noncompliant or *mostly* indistinguishable from a PEP-noncompliant type hint with metadata applicable to various testing scenarios). Examples of PEP-compliant type hints *mostly* indistinguishable from PEP-noncompliant type hints include: * :func:`typing.NamedTuple`, a high-level factory function deferring to the lower-level :func:`collections.namedtuple` factory function creating and returning :class:`tuple` instances annotated by PEP-compliant type hints. * :func:`typing.TypedDict`, a high-level factory function creating and returning :class:`dict` instances annotated by PEP-compliant type hints. Attributes ---------- hint : object Type hint to be tested. conf : BeartypeConf **Beartype configuration** (i.e., self-caching dataclass encapsulating all settings configuring type-checking for this type hint). warning_type : Optional[Type[Warning]] Either: * If the :func:`beartype.beartype` decorator unconditionally emits a non-fatal warning when this type hint annotates *any* parameter or return of *any* callable, the type of that warning. * Else, :data:`None`. is_ignorable : bool :data:`True` only if this hint is safely ignorable by the :func:`beartype.beartype` decorator. Defaults to :data:`False`. is_needs_cls_stack : bool :data:`True` only if this hint is **type stack-dependent** (i.e., if :mod:`beartype` requires the tuple of all classes lexically declaring the class variables or methods annotated by this hint to generate code type-checking this hint). Defaults to :data:`False`. is_supported : bool :data:`True` only if this hint is currently supported by the :func:`beartype.beartype` decorator. Defaults to :data:`False`. piths_meta : Iterable[HintPithSatisfiedMetadata] Iterable of zero or more **(un)satisfied metadata objects** (i.e., :class:`HintPithSatisfiedMetadata` and :class:`HintPithUnsatisfiedMetadata` instances), each describing an arbitrary object either satisfying or violating this hint when passed as a parameter *or* returned as a value annotated by this hint. Defaults to the empty tuple. ''' # ..................{ INITIALIZERS }.................. def __init__( self, *, # Mandatory keyword-only parameters. hint: object, # Optional keyword-only parameters. conf: BeartypeConf = BEARTYPE_CONF_DEFAULT, warning_type: Optional[Type[Warning]] = None, is_ignorable: bool = False, is_needs_cls_stack: bool = False, is_supported: bool = True, piths_meta: Iterable[HintPithSatisfiedMetadata] = (), ) -> None: # Validate passed non-variadic parameters. assert isinstance(conf, BeartypeConf), ( f'{repr(conf)} not configuration.') assert isinstance(warning_type, _NoneTypeOrType), ( f'{repr(warning_type)} neither class nor "None".') assert isinstance(is_ignorable, bool), ( f'{repr(is_ignorable)} not bool.') assert isinstance(is_needs_cls_stack, bool), ( f'{repr(is_needs_cls_stack)} not bool.') assert isinstance(is_supported, bool), ( f'{repr(is_supported)} not bool.') assert isinstance(piths_meta, IterableABC), ( f'{repr(piths_meta)} not iterable.') assert all( isinstance(piths_meta, HintPithSatisfiedMetadata) for piths_meta in piths_meta ), ( f'{repr(piths_meta)} not iterable of ' f'"HintPithSatisfiedMetadata" and ' f'"HintPithUnsatisfiedMetadata" instances.') # Classify all passed parameters. self.hint = hint self.conf = conf self.warning_type = warning_type self.is_ignorable = is_ignorable self.is_needs_cls_stack = is_needs_cls_stack self.is_supported = is_supported self.piths_meta = piths_meta # ..................{ STRINGIFIERS }.................. def __repr__(self) -> str: return '\n'.join(( f'{self.__class__.__name__}(', f' hint={repr(self.hint)},', f' conf={repr(self.conf)},', f' warning_type={repr(self.warning_type)},', f' is_ignorable={repr(self.is_ignorable)},', f' is_needs_cls_stack={repr(self.is_needs_cls_stack)},', f' is_supported={repr(self.is_supported)},', f' piths_meta={repr(self.piths_meta)},', f')', )) # ....................{ CLASSES ~ hint : subclass }.................... class HintPepMetadata(HintNonpepMetadata): ''' **PEP-compliant type hint metadata** (i.e., dataclass whose instance variables describe a PEP-compliant type hint with metadata applicable to various testing scenarios). Attributes ---------- pep_sign : HintSign **Sign** (i.e., arbitrary object uniquely identifying this PEP-compliant type hint) if this hint is uniquely identified by such a sign *or* ``None`` otherwise. Examples of PEP-compliant type hints *not* uniquely identified by such attributes include those reducing to standard builtins on instantiation such as: * :class:`typing.NamedTuple` reducing to :class:`tuple`. * :class:`typing.TypedDict` reducing to :class:`dict`. is_args : bool, optional :data:`True` only if this hint is subscripted by one or more **arguments** (i.e., PEP-compliant type hints that are *not* type variables) and/or **type variables** (i.e., :class:`typing.TypeVar` instances). Defaults to :data:`True` only if the machine-readable representation of this hint contains one or more ``[`` delimiters. is_pep585_builtin_subscripted : bool, optional :data:`True` only if this hint is a :pep:`585`-compliant builtin. If :data:`True`, then :attr:`is_type_typing` *must* be :data:`False`. Defaults to the negation of :attr:`is_pep585_generic` if non-:data:`None` *or* :data:`False` otherwise (i.e., if :attr:`is_pep585_generic` is :data:`None`). is_pep585_generic : bool, optional :data:`True` only if this hint is a :pep:`585`-compliant generic. If :data:`True`, then :attr:`is_type_typing` *must* be :data:`False`. Defaults to :data:`False`. is_typevars : bool, optional :data:`True` only if this hint is subscripted by one or more **type variables** (i.e., :class:`typing.TypeVar` instances). Defaults to :data:`False`. is_type_typing : bool, optional :data:`True` only if this hint's class is defined by the :mod:`typing` module. If ``True``, then :attr:`is_pep585_builtin_subscripted` and :attr:`is_pep585_generic` *must* both be ``False``. Defaults to either: * If either :attr:`is_pep585_builtin_subscripted` *or* :attr:`is_pep585_generic` are :data:`True`, :data:`False`. * Else, :data:`True`. is_typing : bool, optional :data:`True` only if this hint itself is defined by the :mod:`typing` module. Defaults to :attr:`is_type_typing`. isinstanceable_type : Optional[type] **Origin type** (i.e., non-:mod:`typing` class such that *all* objects satisfying this hint are instances of this class) originating this hint if this hint originates from a non-:mod:`typing` class *or* :data:`None` otherwise (i.e., if this hint does *not* originate from such a class). Defaults to :data:`None`. generic_type : Optional[type] Subscripted origin type associated with this hint if any *or* :data:`None` otherwise (i.e., if this hint is associated with *no* such type). Defaults to either: * If this hint is subscripted, :attr:`isinstanceable_type`. * Else, :data:`None`. typehint_cls : Optional[Type[beartype.door.TypeHint]] Concrete :class:`beartype.door.TypeHint` subclass responsible for handling this hint if any *or* :data:`None` otherwise (e.g., if the :mod:`beartype.door` submodule has yet to support this hint). All remaining keyword arguments are passed as is to the superclass :meth:`HintNonpepMetadata.__init__` method. ''' # ..................{ INITIALIZERS }.................. def __init__( self, *, # Mandatory keyword-only parameters. pep_sign: 'beartype._data.hint.pep.sign.datapepsigncls.HintSign', # Optional keyword-only parameters. is_args: Optional[bool] = None, is_pep585_builtin_subscripted: Optional[bool] = None, is_pep585_generic: Optional[bool] = None, is_typevars: bool = False, is_type_typing: Optional[bool] = None, is_typing: Optional[bool] = None, isinstanceable_type: Optional[type] = None, generic_type: Optional[type] = None, typehint_cls: Optional[Type['beartype.door.TypeHint']] = None, **kwargs ) -> None: # Defer test-specific imports. from beartype._data.hint.pep.sign.datapepsigncls import HintSign from beartype.door import TypeHint # Validate passed non-variadic parameters. assert isinstance(is_typevars, bool), ( f'{repr(is_typevars)} not bool.') assert isinstance(pep_sign, HintSign), f'{repr(pep_sign)} not sign.' assert isinstance(isinstanceable_type, _NoneTypeOrType), ( f'{repr(isinstanceable_type)} neither class nor "None".') # Initialize our superclass with all remaining variadic parameters. super().__init__(**kwargs) # Machine-readable representation of this hint. hint_repr = repr(self.hint) # Conditionally default all unpassed parameters. if is_args is None: # Default this parameter to true only if the machine-readable # representation of this hint contains "[": e.g., "List[str]". is_args = '[' in hint_repr if is_pep585_builtin_subscripted is None: # Default this parameter to true only if... is_pep585_builtin_subscripted = ( # This hint originates from an origin type *AND*... isinstanceable_type is not None and # The machine-readable representation of this hint is prefixed # by the unqualified name of this origin type (e.g., "list[str] # " is prefixed by "list"), suggesting this hint to be a PEP # 585-compliant builtin. hint_repr.startswith(isinstanceable_type.__name__) ) # print(f'is_pep585_builtin_subscripted: {is_pep585_builtin_subscripted}') # print(f'hint_repr: {hint_repr}') # print(f'isinstanceable_type.__name__: {isinstanceable_type.__name__}') if is_pep585_generic is None: # Default this parameter to false, because we can't think of # anything better. is_pep585_generic = False if is_type_typing is None: # Default this parameter to the negation of all PEP 585-compliant # boolean parameters. By definition, PEP 585-compliant type hints # are *NOT* defined by the "typing" module and vice versa. is_type_typing = not ( is_pep585_builtin_subscripted or is_pep585_generic) if is_typing is None: # Default this parameter to true only if this hint's class is # defined by the "typing" module. is_typing = is_type_typing if generic_type is None: # Default this parameter to this hint's type origin only if this # hint is subscripted. generic_type = isinstanceable_type if is_args else None # Defer validating parameters defaulting to "None" until *AFTER* # initializing these parameters above. assert isinstance(is_args, bool), ( f'{repr(is_args)} not bool.') assert isinstance(is_pep585_builtin_subscripted, bool), ( f'{repr(is_pep585_builtin_subscripted)} not bool.') assert isinstance(is_pep585_generic, bool), ( f'{repr(is_pep585_generic)} not bool.') assert isinstance(is_type_typing, bool), ( f'{repr(is_type_typing)} not bool.') assert isinstance(is_typing, bool), ( f'{repr(is_typing)} not bool.') assert isinstance(generic_type, _NoneTypeOrType), ( f'{repr(generic_type)} neither class nor "None".') assert isinstance(generic_type, _NoneTypeOrType), ( f'{repr(generic_type)} neither class nor "None".') assert ( typehint_cls is None or ( isinstance(typehint_cls, type) and issubclass(typehint_cls, TypeHint), ) ), ( f'{repr(typehint_cls)} neither ' f'"beartype.door.TypeHint" subclass nor "None".' ) # Validate that the "is_pep585_builtin_subscripted" and "is_type_typing" parameters # are *NOT* both true. Note, however, that both can be false (e.g., for # PEP 484-compliant user-defined generics). assert not ( (is_pep585_builtin_subscripted or is_pep585_generic) and is_type_typing), ( f'Mutually incompatible boolean parameters ' f'is_type_typing={repr(is_type_typing)} and either ' f'is_pep585_builtin_subscripted={repr(is_pep585_builtin_subscripted)} or ' f'is_pep585_generic={repr(is_pep585_generic)} enabled.' ) # Classify all passed parameters. self.generic_type = generic_type self.is_args = is_args self.is_pep585_builtin_subscripted = is_pep585_builtin_subscripted self.is_pep585_generic = is_pep585_generic self.is_typevars = is_typevars self.is_type_typing = is_type_typing self.is_typing = is_typing self.isinstanceable_type = isinstanceable_type self.pep_sign = pep_sign self.typehint_cls = typehint_cls # ..................{ STRINGIFIERS }.................. def __repr__(self) -> str: return '\n'.join(( f'{self.__class__.__name__}(', f' hint={repr(self.hint)},', f' conf={repr(self.conf)},', f' pep_sign={repr(self.pep_sign)},', f' typehint_cls={repr(self.typehint_cls)},', f' generic_type={repr(self.generic_type)},', f' isinstanceable_type={repr(self.isinstanceable_type)},', f' is_args={repr(self.is_args)},', f' is_ignorable={repr(self.is_ignorable)},', f' is_needs_cls_stack={repr(self.is_needs_cls_stack)},', f' is_pep585_builtin_subscripted={repr(self.is_pep585_builtin_subscripted)},', f' is_pep585_generic={repr(self.is_pep585_generic)},', f' is_supported={repr(self.is_supported)},', f' is_typevars={repr(self.is_typevars)},', f' is_type_typing={repr(self.is_type_typing)},', f' is_typing={repr(self.is_typing)},', f' piths_meta={repr(self.piths_meta)},', f')', )) # ....................{ PRIVATE ~ constants }.................... _EXCEPTION_STR_MATCH_REGEXES_MANDATORY = ( # Ensure *ALL* exception messages contain the substring "type hint". # Exception messages *NOT* containing this substring are overly ambiguous # and thus effectively erroneous. r'\btype hint\b', ) ''' Tuple of all **mandatory exception matching regexes** (i.e., r''-style uncompiled regular expression strings, each unconditionally matching a substring of the exception message expected to be raised by wrapper functions when either passed or returning *any* possible pith). ''' _NoneTypeOrType = (type, type(None)) ''' 2-tuple matching both classes and the :data:`None` singleton. ''' beartype-0.18.5/beartype_test/a00_unit/data/hint/util/data_hintmetautil.py000066400000000000000000000213211461113517100266120ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' :mod:`pytest` **PEP-agnostic type hint utilities.** ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from pytest import fixture # ....................{ CLASSES }.................... class HintPithMetadata(object): ''' Dataclass encapsulating all relevant type hint- and pith-specific metadata iteratively yielded by each iteration of the :func:`iter_hints_piths_meta` generator. Attributes ---------- hint_meta : HintNonpepMetadata Metadata describing the currently iterated type hint. pith_meta : HintPithSatisfiedMetadata Metadata describing this ``pith``. pith : object Object either satisfying or violating this hint. ''' # ..................{ INITIALIZERS }.................. def __init__( self, hint_meta: 'HintNonpepMetadata', pith_meta: 'HintPithSatisfiedMetadata', pith: object, ) -> None: ''' Initialize this dataclass. Parameters ---------- hint_meta : HintNonpepMetadata Metadata describing the currently iterated type hint. pith_meta : Union[HintPithSatisfiedMetadata, HintPithUnsatisfiedMetadata] Metadata describing this ``pith``. pith : object Object either satisfying or violating this hint. ''' # Classify all passed parameters. For simplicity, avoid validating # these parameters; we simply *CANNOT* be bothered at the moment. self.hint_meta = hint_meta self.pith_meta = pith_meta self.pith = pith # ....................{ ITERATORS }.................... @fixture(scope='session') def iter_hints_piths_meta(hints_meta) -> ( 'Callable[[], Iterable[HintPithMetadata]]'): ''' Session-scoped fixture yielding a **PEP-agnostic type hint-pith metadata generator** (i.e., factory function creating and returning a generator iteratively yielding :class:`.HintPithMetadata` instances, each describing a sample type hint exercising an edge case in the :mod:`beartype` codebase paired with a related object either satisfying or violating this hint -- including both PEP-compliant and -noncompliant type hints). Caveats ------- This fixture *cannot* be directly iterated. Instead, this fixture must be first called and then iterated by callers. Why? Because :mod:`pytest.fixture`. By design, pytest ignores all ``yield`` statements in a fixture except the first. Ergo, the only means of defining a fixture generator is to define a fixture returning a generator closure. See also `this relevant StackOverflow answer `__. .. _answer: https://stackoverflow.com/a/66397211/2809027 Parameters ---------- hints_meta : List[beartype_test.a00_unit.data.hint.util.data_hintmetacls.HintNonpepMetadata] List of PEP-agnostic type hint metadata describing sample PEP-agnostic type hints exercising edge cases in the :mod:`beartype` codebase. Yields ------ Callable[[], Iterable[HintPithMetadata]] PEP-agnostic type hint-pith metadata generator. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype._util.utilobject import is_object_context_manager from beartype_test.a00_unit.data.hint.util.data_hintmetacls import ( HintPithSatisfiedMetadata, HintPithUnsatisfiedMetadata, ) from beartype_test._util.pytcontext import noop_context_manager # Tuple of two arbitrary values used to trivially iterate twice below. RANGE_2 = (None, None) # ....................{ CLOSURE }.................... def hints_piths_meta() -> 'Iterable[HintPithMetadata]': ''' **PEP-agnostic type hint-pith metadata generator** (i.e., factory function creating and returning a generator iteratively yielding :class:`.HintPithMetadata` instances, each describing a sample type hint exercising an edge case in the :mod:`beartype` codebase paired with a related object either satisfying or violating this hint -- including both PEP-compliant and -noncompliant type hints). ''' # For each predefined type hint and associated metadata... for hint_meta in hints_meta: # print(f'Type-checking type hint {repr(hint_meta.hint)}...') # If this hint is currently unsupported, continue to the next. if not hint_meta.is_supported: continue # Else, this hint is currently supported. # Repeat the following logic twice. Why? To exercise memoization # across repeated @beartype decorations on different callables # annotated by the same hints. for _ in RANGE_2: # For each pith either satisfying or violating this hint... for pith_meta in hint_meta.piths_meta: # Assert this metadata is an instance of the desired dataclass. assert isinstance(pith_meta, HintPithSatisfiedMetadata) # Pith to be type-checked against this hint, defined as... pith = ( # If this pith is actually a pith factory (i.e., callable # accepting *NO* parameters and dynamically creating and # returning the value to be used as the desired pith), call # this factory and localize its return value. pith_meta.pith() if pith_meta.is_pith_factory else # Else, localize this pith as is. pith_meta.pith ) # print(f'Type-checking PEP type hint {repr(hint_meta.hint)} against {repr(pith)}...') # Context manager under which to validate this pith against # this hint, defined as either... pith_context_manager = ( # This pith itself if both... pith if ( # This pith is a context manager *AND*... is_object_context_manager(pith) and # This pith should be safely opened and closed as a # context rather than preserved as a context manager... not pith_meta.is_context_manager ) else # Else, the noop context manager yielding this pith. noop_context_manager(pith) ) # With this pith safely opened and closed as a context... with pith_context_manager as pith_context: # If this pith does *NOT* satisfy this hint... if isinstance(pith_meta, HintPithUnsatisfiedMetadata): # Assert that iterables of uncompiled regular # expression expected to match and *NOT* match this # message are *NOT* strings, as commonly occurs when # accidentally omitting a trailing comma in tuples # containing only one string: e.g., # * "('This is a tuple, yo.',)" is a 1-tuple containing # one string. # * "('This is a string, bro.')" is a string *NOT* # contained in a 1-tuple. assert not isinstance( pith_meta.exception_str_match_regexes, str) assert not isinstance( pith_meta.exception_str_not_match_regexes, str) # Yield this metadata to the caller. yield HintPithMetadata( hint_meta=hint_meta, pith_meta=pith_meta, pith=pith_context, ) # ....................{ YIELD }.................... # Yield this closure. yield hints_piths_meta beartype-0.18.5/beartype_test/a00_unit/data/kind/000077500000000000000000000000001461113517100215475ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/kind/data_kindmap.py000066400000000000000000000107211461113517100245360ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **dictionary data** submodule. This submodule predefines low-level dictionary singletons exercising known edge cases on behalf of higher-level unit test submodules. ''' # ....................{ DICTIONARIES }.................... THE_SONG_OF_HIAWATHA = { 'By the shore': 'of Gitche Gumee', 'By the shining': 'Big-Sea-Water', 'At the': 'doorway of his wigwam', 'In the': 'pleasant Summer morning', 'Hiawatha': 'stood and waited.', } ''' Arbitrary dictionary to be merged. ''' THE_SONG_OF_HIAWATHA_SINGING_IN_THE_SUNSHINE = { 'By the shore': 'of Gitche Gumee', 'By the shining': ['Big-Sea-Water',], 'At the': 'doorway of his wigwam', 'In the': ['pleasant', 'Summer morning',], 'Hiawatha': 'stood and waited.', 'All the air': ['was', 'full of freshness,',], 'All the earth': 'was bright and joyous,', 'And': ['before him,', 'through the sunshine,',], 'Westward': 'toward the neighboring forest', 'Passed in': ['golden swarms', 'the Ahmo,',], 'Passed the': 'bees, the honey-makers,', 'Burning,': ['singing', 'in the sunshine.',], } ''' Arbitrary dictionary to be merged, intentionally containing two key-value collisions with the :data:`.THE_SONG_OF_HIAWATHA` dictionary *and* unhashable values. ''' FROM_THE_BROW_OF_HIAWATHA = { 'From the': 'brow of Hiawatha', 'Gone was': 'every trace of sorrow,', 'As the fog': 'from off the water,', 'As the mist': 'from off the meadow.', } ''' Arbitrary dictionary to be merged, intentionally containing neither key nor key-value collisions with any other global dictionary. ''' IN_THE_LODGE_OF_HIAWATHA = { 'I am': 'going, O Nokomis,', 'On a': 'long and distant journey,', 'To the portals': 'of the Sunset,', 'To the regions': 'of the home-wind,', 'Of the Northwest-Wind,': 'Keewaydin.', } ''' Arbitrary dictionary to be merged, intentionally containing: * No key-value collisions with the :data:`THE_SONG_OF_HIAWATHA` dictionary. * Two key collisions but *no* key-value collisions with the :data:`.FAREWELL_O_HIAWATHA` dictionary. ''' FAREWELL_O_HIAWATHA = { 'Thus departed': 'Hiawatha,', 'Hiawatha': 'the Beloved,', 'In the': 'glory of the sunset,', 'In the purple': 'mists of evening,', 'To the regions': 'of the home-wind,', 'Of the Northwest-Wind,': 'Keewaydin.', } ''' Arbitrary dictionary to be merged, intentionally containing two key-value collisions with the :data:`.THE_SONG_OF_HIAWATHA` dictionary. ''' THE_SONG_OF_HIAWATHA_IN_THE_LODGE_OF_HIAWATHA = { 'By the shore': 'of Gitche Gumee', 'By the shining': 'Big-Sea-Water', 'At the': 'doorway of his wigwam', 'In the': 'pleasant Summer morning', 'Hiawatha': 'stood and waited.', 'I am': 'going, O Nokomis,', 'On a': 'long and distant journey,', 'To the portals': 'of the Sunset,', 'To the regions': 'of the home-wind,', 'Of the Northwest-Wind,': 'Keewaydin.', } ''' Dictionary produced by merging the :data:`THE_SONG_OF_HIAWATHA` and :data:`.IN_THE_LODGE_OF_HIAWATHA` dictionaries. ''' IN_THE_LODGE_OF_HIAWATHA_FAREWELL_O_HIAWATHA = { 'I am': 'going, O Nokomis,', 'On a': 'long and distant journey,', 'To the portals': 'of the Sunset,', 'To the regions': 'of the home-wind,', 'Of the Northwest-Wind,': 'Keewaydin.', 'Thus departed': 'Hiawatha,', 'Hiawatha': 'the Beloved,', 'In the': 'glory of the sunset,', 'In the purple': 'mists of evening,', } ''' Dictionary produced by merging the :data:`.IN_THE_LODGE_OF_HIAWATHA` and :data:`.FAREWELL_O_HIAWATHA` dictionaries. ''' FROM_THE_BROW_OF_HIAWATHA_IN_THE_LODGE_OF_HIAWATHA_FAREWELL_O_HIAWATHA = { 'From the': 'brow of Hiawatha', 'Gone was': 'every trace of sorrow,', 'As the fog': 'from off the water,', 'As the mist': 'from off the meadow.', 'I am': 'going, O Nokomis,', 'On a': 'long and distant journey,', 'To the portals': 'of the Sunset,', 'To the regions': 'of the home-wind,', 'Of the Northwest-Wind,': 'Keewaydin.', 'Thus departed': 'Hiawatha,', 'Hiawatha': 'the Beloved,', 'In the': 'glory of the sunset,', 'In the purple': 'mists of evening,', } ''' Dictionary produced by merging the :data:`.FROM_THE_BROW_OF_HIAWATHA`, :data:`.IN_THE_LODGE_OF_HIAWATHA`, and :data:`.FAREWELL_O_HIAWATHA` dictionaries. ''' beartype-0.18.5/beartype_test/a00_unit/data/pep/000077500000000000000000000000001461113517100214065ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/pep/__init__.py000066400000000000000000000000001461113517100235050ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/pep/data_pep570.py000066400000000000000000000042741461113517100240000ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`570` **data submodule.** This submodule exercises :pep:`570` support for positional-only parameters implemented in the :func:`beartype.beartype` decorator by declaring callables accepting one or more positional-only parameters. For safety, these callables are intentionally isolated from the main test suite. Caveats ---------- **This submodule requires the active Python interpreter to target at least Python 3.8.0.** If this is *not* the case, importing this submodule raises an :exc:`SyntaxError` exception. ''' # ....................{ IMPORTS }.................... from typing import Union # ....................{ CALLABLES }.................... def pep570_posonly( now_take_away_that_flesh: Union[bytearray, str], take_away_the_teeth: Union[bool, str] = ('and the tongue'), /, ) -> Union[list, str]: ''' Arbitrary :pep:`570`-compliant callable passed a mandatory and optional positional-only parameter, all annotated with PEP-compliant type hints. ''' return now_take_away_that_flesh + '\n' + take_away_the_teeth def pep570_posonly_flex_varpos_kwonly( all_of_your_nightmares: Union[bytearray, str], for_a_time_obscured: Union[bool, str] = ( 'As by a shining brainless beacon'), /, or_a_blinding_eclipse: Union[bytes, str] = ( 'Or a blinding eclipse of the many terrible shapes of this world,'), *you_are_calm_and_joyful: Union[float, str], your_special_plan: Union[int, str], ) -> Union[list, str]: ''' Arbitrary :pep:`570`-compliant callable passed a mandatory positional-only parameter, optional positional-only parameter, flexible parameter, variadic positional parameter, and keyword-only parameter, all annotated with PEP-compliant type hints. ''' return ( all_of_your_nightmares + '\n' + for_a_time_obscured + '\n' + or_a_blinding_eclipse + '\n' + '\n'.join(you_are_calm_and_joyful) + '\n' + your_special_plan ) beartype-0.18.5/beartype_test/a00_unit/data/pep/pep563/000077500000000000000000000000001461113517100224305ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/pep/pep563/__init__.py000066400000000000000000000000001461113517100245270ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/pep/pep563/data_pep563_club.py000066400000000000000000000161071461113517100260270ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`563` **cultural data submodule.** This submodule exercises edge-case :pep:`563` support implemented in the :func:`beartype.beartype` decorator against a `recently submitted issue `__. For reproducibility, this edge case is intentionally isolated from the comparable :mod:`beartype_test.a00_unit.data.pep.pep563.data_pep563_poem` submodule. .. _issue #49: https://github.com/beartype/beartype/issues/49 ''' # ....................{ IMPORTS }.................... from __future__ import annotations from beartype import beartype from beartype.typing import ( NoReturn, Union, ) # ....................{ CONSTANTS }.................... COLORS = 'red, gold, and green' ''' Arbitrary string constant returned by the :meth:`Chameleon.like_my_dreams` class method. ''' CLING = 'our love is strong' ''' Arbitrary string constant returned by the :meth:`Karma.when_we_cling` class method. ''' DREAMS = 'Loving would be easy' ''' Arbitrary string constant returned by the :meth:`Karma.if_your_colors` class method. ''' Karma = 'you come and go' ''' Arbitrary string constant whose attribute name intentionally conflicts with that of a subsequently declared class. ''' # ....................{ EXCEPTIONS }.................... class HeWouldLingerLong(Exception): ''' Arbitrary exception subclass. ''' pass # ....................{ CLASSES }.................... @beartype class Chameleon(object): ''' Arbitrary class declaring arbitrary methods. Attributes ---------- colors : str Arbitrary string. ''' # ..................{ INITIALIZER }.................. def __init__(self, colors: str) -> None: ''' Arbitrary object initializer. ''' self.colors = colors # ..................{ METHODS }.................. # Intentionally decorate this class method directly by @beartype to validate # that @beartype resolves directly self-referential type hints (i.e., type # hints that are directly self-references to the declaring class). # @beartype @classmethod def like_my_dreams(cls) -> Chameleon: ''' Arbitrary class method decorated by the :mod:`beartype.beartype` decorator creating and returning an arbitrary instance of this class and thus annotated as returning the same class, exercising a pernicious edge case unique to :pep:`563`-specific forward references. Superficially, the PEP-compliant type hint annotating this return appears to recursively (and thus erroneously) refer to the class currently being declared. Had :pep:`563` *not* been conditionally enabled above via the ``from __future__ import annotations`` statement, this recursive reference would have induced a low-level parse-time exception from the active Python interpreter. In actuality, this recursive reference is silently elided away at runtime by the active Python interpreter. Under :pep:`563`-specific postponement (i.e., type hint unparsing), this interpreter internally stringifies this type hint into a relative forward reference to this class, thus obviating erroneous recursion at method declaration time. Ergo, this method's signature is actually the following: def like_my_dreams(cls) -> 'Chameleon': ''' return Chameleon(COLORS) # Intentionally avoid decorating this static method directly by @beartype to # validate that @beartype resolves indirectly self-referential type hints # (i.e., parent type hints subscripted by one or more child type hints that # are self-references to the declaring class). # # Note that indirectly self-referential type hints *CANNOT* be properly # resolved for methods directly decorated by @beartype. Due to # decoration-time constraints, this class itself *MUST* be decorated. @staticmethod def when_we_cling() -> Union[Chameleon, complex]: ''' Arbitrary static method decorated by the :mod:`beartype.beartype` decorator creating and returning an arbitrary instance of this class and thus annotated as returning a union containing the same class and one or more arbitrary child type hints, exercising a pernicious edge case unique to :pep:`563`-specific self-referential types. Note that this and the comparable :meth:`like_my_dreams` class method exercise different edge cases. That method exercises an edge case concerning forward references, as a method annotated as returning the type to which this method is bound under :pep:`563` is syntactically indistinguishable from a standard forward reference without :pep:`563`. This method, in the other hand, exercises an edge case concerning self-referential types, as a method annotated as returning an arbitrary type hint subscripted by the type to which this method is bound under :pep:`563` is syntactically *distinguishable* from a standard forward reference without :pep:`563`. Specifically, this method exercises a `recently submitted issue `__. .. _issue #152: https://github.com/beartype/beartype/issues/152 ''' return Chameleon(CLING) class Karma(object): ''' Arbitrary class whose name intentionally conflicts with that of a previously declared global of this submodule, declaring arbitrary methods. Attributes ---------- dreams : str Arbitrary string. ''' # ..................{ INITIALIZER }.................. def __init__(self, dreams: str): ''' Arbitrary object initializer. ''' self.dreams = dreams # ..................{ METHODS ~ class }.................. @classmethod @beartype def if_your_colors(cls) -> Karma: ''' Arbitrary class method decorated by the :mod:`beartype.beartype` decorator creating and returning an arbitrary instance of this class and thus annotated as returning the same class, exercising a pernicious edge case unique to :pep:`563`-specific forward references. See Also -------- :meth:`.Chameleon.like_my_dreams` ''' return Karma(DREAMS) # ....................{ FUNCTIONS }.................... @beartype def to_love_and_wonder() -> NoReturn: ''' Arbitrary function unconditionally raising a unique function-specific exception annotated as such, exercising an edge case in the :func:`beartype.beartype` decorator. Raises ------ HeWouldLingerLong Unconditionally. ''' raise HeWouldLingerLong('In lonesome vales, making the wild his home,') beartype-0.18.5/beartype_test/a00_unit/data/pep/pep563/data_pep563_poem.py000066400000000000000000000531311461113517100260400ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`563` **poetic data submodule.** This submodule exercises :pep:`563` support implemented in the :func:`beartype.beartype` decorator by enabling this support with a leading ``from __future__ import annotations`` statement and then declaring a callable decorated by that decorator. External unit tests are expected to import this submodule and then call that callable. ''' # ....................{ IMPORTS }.................... from __future__ import annotations from beartype import beartype from beartype.typing import ( List, Union, ) from beartype._cave._cavefast import IntType from beartype_test.a00_unit.data.data_type import decorator from collections.abc import Callable # ....................{ CONSTANTS }.................... _MINECRAFT_END_TXT_STANZAS = ( 'I see the player you mean.', '{player_name}?', 'Yes. Take care. It has reached a higher level now. It can read our thoughts.', "That doesn't matter. It thinks we are part of the game.", 'I like this player. It played well. It did not give up.', 'It is reading our thoughts as though they were words on a screen.', 'That is how it chooses to imagine many things, when it is deep in the dream of a game.', 'Words make a wonderful interface. Very flexible. And less terrifying than staring at the reality behind the screen.', 'They used to hear voices. Before players could read. Back in the days when those who did not play called the players witches, and warlocks. And players dreamed they flew through the air, on sticks powered by demons.', 'What did this player dream?', 'This player dreamed of sunlight and trees. Of fire and water. It dreamed it created. And it dreamed it destroyed. It dreamed it hunted, and was hunted. It dreamed of shelter.', 'Hah, the original interface. A million years old, and it still works. But what true structure did this player create, in the reality behind the screen?', 'It worked, with a million others, to sculpt a true world in a fold of the [scrambled], and created a [scrambled] for [scrambled], in the [scrambled].', 'It cannot read that thought.', 'No. It has not yet achieved the highest level. That, it must achieve in the long dream of life, not the short dream of a game.', 'Does it know that we love it? That the universe is kind?', 'Sometimes, through the noise of its thoughts, it hears the universe, yes.', 'But there are times it is sad, in the long dream. It creates worlds that have no summer, and it shivers under a black sun, and it takes its sad creation for reality.', 'To cure it of sorrow would destroy it. The sorrow is part of its own private task. We cannot interfere.', 'Sometimes when they are deep in dreams, I want to tell them, they are building true worlds in reality. Sometimes I want to tell them of their importance to the universe. Sometimes, when they have not made a true connection in a while, I want to help them to speak the word they fear.', 'It reads our thoughts.', 'Sometimes I do not care. Sometimes I wish to tell them, this world you take for truth is merely [scrambled] and [scrambled], I wish to tell them that they are [scrambled] in the [scrambled]. They see so little of reality, in their long dream.', 'And yet they play the game.', 'But it would be so easy to tell them...', 'Too strong for this dream. To tell them how to live is to prevent them living.', 'I will not tell the player how to live.', 'The player is growing restless.', 'I will tell the player a story.', 'But not the truth.', 'No. A story that contains the truth safely, in a cage of words. Not the naked truth that can burn over any distance.', 'Give it a body, again.', 'Yes. Player...', 'Use its name.', '{player_name}. Player of games.', 'Good.', 'Take a breath, now. Take another. Feel air in your lungs. Let your limbs return. Yes, move your fingers. Have a body again, under gravity, in air. Respawn in the long dream. There you are. Your body touching the universe again at every point, as though you were separate things. As though we were separate things.', 'Who are we? Once we were called the spirit of the mountain. Father sun, mother moon. Ancestral spirits, animal spirits. Jinn. Ghosts. The green man. Then gods, demons. Angels. Poltergeists. Aliens, extraterrestrials. Leptons, quarks. The words change. We do not change.', "We are the universe. We are everything you think isn't you. You are looking at us now, through your skin and your eyes. And why does the universe touch your skin, and throw light on you? To see you, player. To know you. And to be known. I shall tell you a story.", 'Once upon a time, there was a player.', 'The player was you, {player_name}.', 'Sometimes it thought itself human, on the thin crust of a spinning globe of molten rock. The ball of molten rock circled a ball of blazing gas that was three hundred and thirty thousand times more massive than it. They were so far apart that light took eight minutes to cross the gap. The light was information from a star, and it could burn your skin from a hundred and fifty million kilometres away.', 'Sometimes the player dreamed it was a miner, on the surface of a world that was flat, and infinite. The sun was a square of white. The days were short; there was much to do; and death was a temporary inconvenience.', 'Sometimes the player dreamed it was lost in a story.', 'Sometimes the player dreamed it was other things, in other places. Sometimes these dreams were disturbing. Sometimes very beautiful indeed. Sometimes the player woke from one dream into another, then woke from that into a third.', 'Sometimes the player dreamed it watched words on a screen.', "Let's go back.", 'The atoms of the player were scattered in the grass, in the rivers, in the air, in the ground. A woman gathered the atoms; she drank and ate and inhaled; and the woman assembled the player, in her body.', "And the player awoke, from the warm, dark world of its mother's body, into the long dream.", 'And the player was a new story, never told before, written in letters of DNA. And the player was a new program, never run before, generated by a sourcecode a billion years old. And the player was a new human, never alive before, made from nothing but milk and love.', 'You are the player. The story. The program. The human. Made from nothing but milk and love.', "Let's go further back.", "The seven billion billion billion atoms of the player's body were created, long before this game, in the heart of a star. So the player, too, is information from a star. And the player moves through a story, which is a forest of information planted by a man called Julian, on a flat, infinite world created by a man called Markus, that exists inside a small, private world created by the player, who inhabits a universe created by...", 'Shush. Sometimes the player created a small, private world that was soft and warm and simple. Sometimes hard, and cold, and complicated. Sometimes it built a model of the universe in its head; flecks of energy, moving through vast empty spaces. Sometimes it called those flecks "electrons" and "protons".', 'Sometimes it called them "planets" and "stars".', 'Sometimes it believed it was in a universe that was made of energy that was made of offs and ons; zeros and ones; lines of code. Sometimes it believed it was playing a game. Sometimes it believed it was reading words on a screen.', 'You are the player, reading words...', 'Shush... Sometimes the player read lines of code on a screen. Decoded them into words; decoded words into meaning; decoded meaning into feelings, emotions, theories, ideas, and the player started to breathe faster and deeper and realised it was alive, it was alive, those thousand deaths had not been real, the player was alive', 'You. You. You are alive.', 'and sometimes the player believed the universe had spoken to it through the sunlight that came through the shuffling leaves of the summer trees', "and sometimes the player believed the universe had spoken to it through the light that fell from the crisp night sky of winter, where a fleck of light in the corner of the player's eye might be a star a million times as massive as the sun, boiling its planets to plasma in order to be visible for a moment to the player, walking home at the far side of the universe, suddenly smelling food, almost at the familiar door, about to dream again", 'and sometimes the player believed the universe had spoken to it through the zeros and ones, through the electricity of the world, through the scrolling words on a screen at the end of a dream', 'and the universe said I love you', 'and the universe said you have played the game well', 'and the universe said everything you need is within you', 'and the universe said you are stronger than you know', 'and the universe said you are the daylight', 'and the universe said you are the night', 'and the universe said the darkness you fight is within you', 'and the universe said the light you seek is within you', 'and the universe said you are not alone', 'and the universe said you are not separate from every other thing', 'and the universe said you are the universe tasting itself, talking to itself, reading its own code', 'and the universe said I love you because you are love.', 'And the game was over and the player woke up from the dream. And the player began a new dream. And the player dreamed again, dreamed better. And the player was the universe. And the player was love.', 'You are the player.', 'Wake up.', ) # ....................{ CALLABLES ~ module : undecorated }.................... # Callables exercising module-scoped PEP 563 edge cases. def get_minecraft_end_txt(player_name: str) -> str: ''' Callable *not* decorated by :func:`beartype.beartype`, exercising that :func:`beartype.beartype` silently accepts callables with one or more non-postponed annotations under :pep:`563` by manually resolving all postponed annotations on this callable and then manually passing this callable to :func:`beartype.beartype`. ''' return ''.join(_MINECRAFT_END_TXT_STANZAS).format(player_name=player_name) def get_minecraft_end_txt_pep604(player_name: str | int) -> str | int: ''' Callable *not* decorated by :func:`beartype.beartype`, exercising that :func:`beartype.beartype` either: * Under Python < 3.10, raises an exception when decorating a callable annotated by one or more :pep:`604`-compliant new unions postponed by :pep:`563`. * Under Python >= 3.10, decorates this callable as expected *without* raising an exception. ''' # Defer to the existing getter defined above, decorated by @beartype. return beartype(get_minecraft_end_txt)(player_name) # ....................{ CALLABLES ~ module : decorated }.................... # @beartype-decorated callables exercising module-scoped PEP 563 edge cases. @beartype def get_minecraft_end_txt_stanza( player_name: str, stanza_index: IntType) -> str: ''' Callable decorated by :func:`beartype.beartype`. ''' return _MINECRAFT_END_TXT_STANZAS[stanza_index].format( player_name=player_name) # ....................{ CALLABLES ~ closure }.................... # Callables exercising closure-scoped edge cases under PEP 563. @beartype def get_minecraft_end_txt_closure(player_name: str) -> Callable: ''' Callable decorated by :func:`beartype.beartype`, internally declaring and returning a closure also decorated by :func:`beartype.beartype` and annotated by PEP-compliant type hints accessible only as local variables. ''' # PEP-compliant type hints accessible only as local variables to the # following closure, exercising a significant edge case in PEP 563 support. StringLike = Union[str, int, bytes] ListOfStrings = List[str] # Intentionally delimited by one layer of decoration to exercise edges. @decorator @beartype @decorator def get_minecraft_end_txt_substr(substr: StringLike) -> ListOfStrings: ''' Closure decorated by both :func:`beartype.beartype` and one or more decorators that are *not* :func:`beartype.beartype`, annotated by PEP-compliant type hints accessible only as local variables. ''' return [ stanza.format(player_name=player_name) for stanza in _MINECRAFT_END_TXT_STANZAS if str(substr) in stanza ] # print(f'mc.__qualname__: {get_minecraft_end_txt_substr.__qualname__}') # Return this closure. return get_minecraft_end_txt_substr @beartype def get_minecraft_end_txt_closure_factory(player_name: str) -> Callable: ''' Callable decorated by :func:`beartype.beartype`, internally declaring and returning a closure also decorated by :func:`beartype.beartype` and annotated by PEP-compliant type hints accessible only as local variables, internally declaring and returning *another* nested closure also decorated by :func:`beartype.beartype` and annotated by PEP-compliant type hints accessible only as local variables in a manner exercising edge case precedence in scope aggregation. ''' # PEP-compliant type hints accessible only as local variables to the # following closure, exercising a significant edge case in PEP 563 support. IntLike = Union[float, int] ReturnType = Callable InnerReturnType = List[str] # Intentionally delimited by two layers of decoration to exercise edges. @decorator @decorator @beartype @decorator @decorator def get_minecraft_end_txt_closure_outer( stanza_len_min: IntLike) -> ReturnType: ''' Outer closure decorated by :func:`beartype.beartype` and one or more decorators that are *not* :func:`beartype.beartype`, annotated by PEP-compliant type hints accessible only as local variables, internally declaring and returning *another* nested closure also decorated by :func:`beartype.beartype` and annotated by PEP-compliant type hints accessible only as local variables in a manner exercising edge case precedence in scope aggregation. ''' # PEP-compliant type hints accessible only as local variables to the # following closure, overriding those declared above and again # exercising a significant edge case in PEP 563 support. StringLike = Union[str, bytes] ReturnType = InnerReturnType # Intentionally delimited by no layers of decoration to exercise edges. @beartype def get_minecraft_end_txt_closure_inner( stanza_len_max: IntLike, substr: StringLike, ) -> ReturnType: ''' Inner closure decorated by :func:`beartype.beartype` and one or more decorators that are *not* :func:`beartype.beartype`, annotated by PEP-compliant type hints accessible only as local variables. ''' return [ stanza.format(player_name=player_name) for stanza in _MINECRAFT_END_TXT_STANZAS if ( len(stanza) >= int(stanza_len_min) and len(stanza) <= int(stanza_len_max) and str(substr) in stanza ) ] # Return this closure. return get_minecraft_end_txt_closure_inner # print(f'mc.__qualname__: {get_minecraft_end_txt_substr.__qualname__}') # Return this closure. return get_minecraft_end_txt_closure_outer # ....................{ CLASSES }.................... # Classes exercising module-scoped edge cases under PEP 563. #FIXME: We should probably nest this class in a function to exercise everything, #but this would seem to suffice for now as an initial foray. class MinecraftEndTxtUnscrambler(object): ''' Arbitrary class declaring a method decorated by :func:`beartype.beartype` annotated by type hints accessible only as class variables. ''' # PEP-compliant type hints accessible only as class variables to the # following method, exercising a significant edge case in PEP 563 support. NoneIsh = None TextIsh = Union[str, bytes] @beartype def __init__(self, unscrambling: TextIsh) -> NoneIsh: ''' Arbitrary method decorated by :func:`beartype.beartype`, annotated by type hints accessible only as class variables. ''' _minecraft_end_txt_stanzas_unscrambled = [ minecraft_end_txt_stanza.replace('[scrambled]', unscrambling) for minecraft_end_txt_stanza in _MINECRAFT_END_TXT_STANZAS if '[scrambled]' in minecraft_end_txt_stanza ] # Type hints accessible only as local variables to the following # closure, exercising an edge case in PEP 563 support. BoolIsh = Union[bool, float, int] @beartype def get_minecraft_end_txt_unscrambled_stanza_closure( self, is_stanza_last: BoolIsh) -> self.TextIsh: ''' Arbitrary closure decorated by :func:`beartype.beartype`, annotated by type hints accessible only as both class and local variables. ''' return _minecraft_end_txt_stanzas_unscrambled[ int(bool(is_stanza_last))] # Reuse this closure as a bound method. self.get_minecraft_end_txt_unscrambled_stanza = ( get_minecraft_end_txt_unscrambled_stanza_closure) # ....................{ CALLABLES ~ limit }.................... #FIXME: Hilariously, we can't even unit test whether the #beartype._decor._pep.pep563._die_if_hint_repr_exceeds_child_limit() function #behaves as expected. Why not? Because some combination of the "typing" module #and/or PEP 563 were implemented so space-inefficiently than even attempting to #instantiate a PEP-compliant type hint that would violate the child limit #(i.e., the maximum size for fixed lists used by the @beartype decorator to #implement its breadth-first search (BFS) across child hints) induces a memory #error from the CPython parser -- complete with non-human-readable debug #"stderr" output that I highly doubt CPython is even supposed to publicly emit: # # beartype_test/unit/data/data_pep563.py:180: MemoryError # ---------------------------------------------------- Captured stderr call ----------------------------------------------------- # s_push: parser stack overflow # #"s_push: parser stack overflow"? Really? What the pablum is this nonsense? # #Naturally, this implies that end users are by definition prohibited from #violating our package-specific child limit without our ever needing to even #explicitly validate this limit. This is ridiculous, absurd, and yet another #proverbial nail in the coffin for annotation-centric PEPs. I don't know who #was tasked with implementing this API, but they clearly had little to no #coherent idea of what they were doing. # from beartype._util.cache.pool.utilcachepoollistfixed import FIXED_LIST_SIZE_MEDIUM # from typing import List, Union # # # This global is defined below for sanity. # _HINT_BIG = None # ''' # PEP-compliant type hint guaranteed to raise an exception from the private # :func:`beartype._decor._pep.pep563._die_if_hint_repr_exceeds_child_limit` # function, which imposes strict limits on the number of child hints permitted to # be transitively nested in any top-level PEP-compliant type hint. # ''' # # # def _init() -> None: # ''' # Define the :data:`_HINT_BIG` global declared above. # ''' # # # Enable this global to be defined. # global _HINT_BIG # # # This fixed length subtracted by 1 divided by 3. Just 'cause. # SIZE_LESS_BIG = (FIXED_LIST_SIZE_MEDIUM-1) / 3 # # # Assert the fixed length of the cached fixed lists constraining the number # # of child hints permitted to be transitively nested in any top-level # # PEP-compliant type hint is evenly divisible by 3 when subtracted by 1, # # thus producing whole integers when subject to the above operation. # # # # Oddly, this condition applies to a surprising number of powers of two: # # >>> (1024 - 1) % 3 # # 341 # # >>> (256 - 1) % 3 # # 85 # assert SIZE_LESS_BIG.is_integer(), ( # '{} not integer.'.format(SIZE_LESS_BIG)) # # # Constrain this length to an integer as expected by the range() builtin. # SIZE_LESS_BIG = int(SIZE_LESS_BIG) # # # Python expression used to dynamically define this global below. # _HINT_BIG_EXPR = '{}{}{}'.format( # # Substring prefixing this hint. # ''.join('Union[int, List[' for _ in range(SIZE_LESS_BIG)), # # Substring subscripting the last "List" child hint of this hint. # 'str', # # Substring suffixing this hint. # ''.join(']]' for _ in range(SIZE_LESS_BIG)), # ) # # # Dynamically define this global, as "FIXED_LIST_SIZE_MEDIUM" is typically too large to # # allow this global to be statically defined. # _HINT_BIG = eval(_HINT_BIG_EXPR, globals()) # # # # Define the "_HINT_BIG" global declared above. # _init() # # # # Callable annotated by this global *AFTER* defining this global above. # # # # Note that this callable is intentionally *NOT* decorated by @beartype here, # # as doing so would immediately raise an exception that we would rather # # explicitly test for elsewhere. # def player_was_love(player_was_the_universe: _HINT_BIG) -> _HINT_BIG: # return player_was_the_universe beartype-0.18.5/beartype_test/a00_unit/data/pep/pep563/data_pep563_resolve.py000066400000000000000000000116541461113517100265630ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`563` **resolver** data submodule. This submodule defines callables annotated by :pep:`563`-postponed type hints under the ``from __future__ import annotations`` pragma, intended to be externally imported and called from unit tests elsewhere in this test suite. Caveats ------- **Callables and classes defined below are intentionally not decorated by the** :func:`beartype.beartype` **decorator.** Why? Because that decorator internally calls the :func:`beartype.peps.resolve_pep563` resolver. However, the whole point of this submodule is to explicitly call and thus exercise that resolver! ''' # ....................{ IMPORTS }.................... from __future__ import annotations from beartype.door import die_if_unbearable from beartype.typing import Generic from beartype._data.hint.datahinttyping import T # ....................{ CLASSES }.................... class ToAvariceOrPride(Generic[T]): ''' Arbitrary generic. ''' pass class FrequentWith(object): ''' Arbitrary class defining various problematic methods. ''' # ....................{ CLASS METHODS }.................... @classmethod def until_the_doves( cls, and_squirrels_would_partake: ExpandAbove) -> ExpandAbove: ''' Arbitrary **annotated class method** (i.e., class method annotated by one or more type hints), exercising an edge case in the :func:`beartype.peps.resolve_pep563` resolver. ''' # Subscripted generic type alias, resolved to this global attribute that # has yet to be defined by the resolve_pep563() function called by the # caller. ExpandAbove_resolved = ( FrequentWith.until_the_doves.__func__.__annotations__[ 'and_squirrels_would_partake']) # If this parameter violates this type, raise an exception. die_if_unbearable(and_squirrels_would_partake, ExpandAbove_resolved) # Return this parameter as is. return and_squirrels_would_partake # ....................{ METHODS }.................... def crystal_column(self, and_clear_shrines: OfPearl) -> OfPearl: ''' Arbitrary method both accepting and returning a value annotated as a **missing forward reference** (i.e., :pep:`563`-postponed type hint referring to a global attribute that is guaranteed to *never* be defined by this submodule), exercising an edge case in the :func:`beartype.peps.resolve_pep563` resolver. Raises ------ BeartypeCallHintForwardRefException Unconditionally. ''' # Missing forward reference, defined merely as a placeholder forward # reference proxy after the caller passes this method to the # resolve_pep563() function. OfPearl_resolved = FrequentWith.crystal_column.__annotations__[ 'and_clear_shrines'] # Raise an exception. Since this forward reference is guaranteed to be # missing, this call is guaranteed to fail. die_if_unbearable(and_clear_shrines, OfPearl_resolved) # Return this parameter as is. return and_clear_shrines # ....................{ FUNCTIONS }.................... def their_starry_domes(of_diamond_and_of_gold: ExpandAbove) -> ExpandAbove: ''' Arbitrary function both accepting and returning a value annotated as a **subscripted generic type alias forward reference** (i.e., :pep:`563`-postponed type hint referring to a global attribute that has yet to be defined whose value is a subscripted generic that *has* been defined), exercising an edge case in the :func:`beartype.peps.resolve_pep563` resolver. Raises ------ BeartypeDecorHintForwardRefException If this function has yet to be passed to the :func:`beartype.peps.resolve_pep563` resolver. ''' # Subscripted generic type alias, resolved to this global attribute that has # yet to be defined by the resolve_pep563() function called by the caller. ExpandAbove_resolved = their_starry_domes.__annotations__[ 'of_diamond_and_of_gold'] # If this parameter violates this subscripted generic, raise an exception. die_if_unbearable(of_diamond_and_of_gold, ExpandAbove_resolved) # Return this parameter as is. return of_diamond_and_of_gold # ....................{ HINTS }.................... ExpandAbove = ToAvariceOrPride[str] ''' Arbitrary **subscripted generic type alias forward reference** (i.e., :pep:`563`-postponed type hint referring to a global attribute that has yet to be defined whose value is a subscripted generic that *has* been defined), ''' beartype-0.18.5/beartype_test/a00_unit/data/pep/pep563/pep484/000077500000000000000000000000001461113517100234545ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/pep/pep563/pep484/__init__.py000066400000000000000000000000001461113517100255530ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/pep/pep563/pep484/data_pep563_pep484.py000066400000000000000000000043571461113517100271560ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`563` + :pep:`484` **integration data submodule.** This submodule exercises edge cases when combining :pep:`563` via the standard ``from __future__ import annotations`` pragma with :pep:`484`-compliant type hints known to interact problematically with :pep:`563`. ''' # ....................{ IMPORTS }.................... from __future__ import annotations from beartype import beartype from beartype.typing import NamedTuple # ....................{ CLASSES }.................... @beartype class LeadOnlyTo(NamedTuple): ''' :pep:`484`-compliant named tuple subclass defining one or more class variables annotated by type hints exercising edge cases in :pep:`563` integration implemented by the :func:`beartype.beartype` decorator. ''' # ....................{ FIELDS }.................... a_black_and_watery_depth: int ''' Arbitrary named tuple field annotated by a type hint that is a **builtin type** (i.e., C-based type globally accessible to *all* lexical contexts without requiring explicit importation). This field exercises a well-known edge case in :pep:`563` integration. For unknown reasons, the :class:`.NamedTuple` superclass dynamically generates a problematic unique ``__new__()`` dunder method for each subclass of this superclass. This method suffers various deficiencies, including: * This method erroneously claims it resides in a non-existent fake module with the fully-qualified name ``"named_{subclass_name}"`` (e.g., ``"named_LeadOnlyTo"`` in this case). * This method inexplicably encapsulates *all* stringified type hints annotating subclass fields with :class:`typing.ForwardRef` instances. Since :pep:`563` unconditionally stringifies *all* type hints in applicable modules, this implies that this method unconditionally encapsulates *all* type hints annotating subclass fields with :class:`typing.ForwardRef` instances in modules enabling :pep:`563`. ''' beartype-0.18.5/beartype_test/a00_unit/data/pep/pep563/pep604/000077500000000000000000000000001461113517100234465ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/pep/pep563/pep604/__init__.py000066400000000000000000000000001461113517100255450ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/pep/pep563/pep604/data_pep563_pep604.py000066400000000000000000000043571461113517100271420ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`563` + :pep:`604` **integration data submodule.** This submodule exercises edge cases when combining :pep:`563` via the standard ``from __future__ import annotations`` pragma with :pep:`604`-compliant unions known to interact problematically with :pep:`563`. ''' # ....................{ IMPORTS }.................... from __future__ import annotations from beartype import beartype from dataclasses import dataclass # ....................{ CLASSES }.................... @beartype @dataclass() class SpiritMoreVast(object): ''' Arbitrary :pep:`557`-compliant dataclass subclass that exists solely to be referred to below in a :pep:`604`-compliant union. ''' than_thine: str = 'Spirit more vast than thine, frame more attuned' ''' Arbitrary field. ''' @beartype @dataclass() class FrameMoreAttuned(object): ''' Arbitrary :pep:`557`-compliant dataclass subclass that exists solely to be referred to below in a :pep:`604`-compliant union. ''' to_beauty: str = 'To beauty, wasting these surpassing powers' ''' Arbitrary field. ''' # from beartype import BeartypeConf # @beartype(conf=BeartypeConf(is_debug=True)) @beartype @dataclass() class WithVoiceFarSweeter(object): ''' :pep:`557`-compliant dataclass subclass defining one or more class variables annotated by type hints exercising edge cases in :pep:`563` integration implemented by the :func:`beartype.beartype` decorator. ''' # ....................{ FIELDS }.................... than_thy_dying_notes: SpiritMoreVastOrFrameMoreAttuned | None # = None ''' Arbitrary field annotated by an undefined :pep:`604`-compliant union, exercising a well-known edge case in :pep:`563` integration. ''' # ....................{ HINTS }.................... SpiritMoreVastOrFrameMoreAttuned = SpiritMoreVast | FrameMoreAttuned ''' Arbitrary :pep:`604`-compliant union referenced by the dataclass defined above. ''' beartype-0.18.5/beartype_test/a00_unit/data/pep/pep695/000077500000000000000000000000001461113517100224365ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/pep/pep695/__init__.py000066400000000000000000000000001461113517100245350ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/pep/pep695/data_pep695_util.py000066400000000000000000000142551461113517100260750ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide :pep:`695` **utility data submodule.** This submodule exercises :pep:`695` support for statement-level type aliases implemented in the :func:`beartype.beartype` decorator by declaring unit tests exercising these aliases. For safety, these tests are intentionally isolated from the main test suite. Notably, this low-level submodule implements the higher-level ``test_decor_pep695()`` unit test in the main test suite. Caveats ------- **This submodule requires the active Python interpreter to target at least Python 3.12.0.** If this is *not* the case, importing this submodule raises an :exc:`SyntaxError` exception. ''' # ....................{ TESTS ~ iterator }.................... def unit_test_iter_hint_pep695_forwardrefs() -> None: ''' Test the private :mod:`beartype._util.hint.pep.proposal.utilpep695.iter_hint_pep695_forwardrefs` iterator. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar import BeartypeDecorHintPep695Exception from beartype._check.forward.reference.fwdrefmeta import ( BeartypeForwardRefMeta) from beartype._util.hint.pep.proposal.utilpep695 import ( iter_hint_pep695_forwardrefs) from pytest import raises # ....................{ LOCALS }.................... # Type alias containing *NO* unquoted forward references. type of_intermitted_song = str | int # Type alias containing one unquoted forward reference. type sudden_she_rose = ItsBurstingBurthen | bool # Type alias containing two or more unquoted forward reference. type as_if_her_heart = ImpatientlyEndured | complex | AtTheSoundHeTurned # ....................{ ASSERTS ~ null }.................... # Assert that this iterator yields nothing when passed a type alias # containing *NO* unquoted forward references. their_own_life = list(iter_hint_pep695_forwardrefs(of_intermitted_song)) assert not their_own_life # ....................{ ASSERTS ~ single }.................... # Assert that this iterator yields a single forward reference proxy # referring to the single unquoted forward reference embedded in a # passed type alias containing only that reference. and_saw_by = iter_hint_pep695_forwardrefs(sudden_she_rose) the_warm_light_of = next(and_saw_by) assert isinstance(the_warm_light_of, BeartypeForwardRefMeta) assert the_warm_light_of.__name__ == 'ItsBurstingBurthen' # Assert this iterator raises the expected exception when attempting to # erroneously iterate past an unquoted forward reference referring to an # undefined attribute. with raises(BeartypeDecorHintPep695Exception): next(and_saw_by) class ItsBurstingBurthen(object): ''' Class to which this unquoted forward reference refers, intentionally declared while iterating this iterator. ''' pass # Assert that this iterator is now exhausted. with raises(StopIteration): next(and_saw_by) # ....................{ ASSERTS ~ multiple }.................... # Assert that this iterator first yields a forward reference proxy referring # to the first unquoted forward reference embedded in a passed type alias # containing only that reference. her_glowing_limbs = iter_hint_pep695_forwardrefs(as_if_her_heart) beneath_the_sinuous_veil = next(her_glowing_limbs) assert isinstance(beneath_the_sinuous_veil, BeartypeForwardRefMeta) assert beneath_the_sinuous_veil.__name__ == 'ImpatientlyEndured' class ImpatientlyEndured(object): ''' Class to which this unquoted forward reference refers, intentionally declared while iterating this iterator. ''' pass # Assert that this iterator lastly yields a forward reference proxy # referring to the last unquoted forward reference in this type alias. of_woven_wind = next(her_glowing_limbs) assert isinstance(of_woven_wind, BeartypeForwardRefMeta) assert of_woven_wind.__name__ == 'AtTheSoundHeTurned' class AtTheSoundHeTurned(object): ''' Class to which this unquoted forward reference refers, intentionally declared while iterating this iterator. ''' pass # Assert this iterator raises the expected exception when attempting to # erroneously iterate past an unquoted forward reference referring to an # undefined attribute. with raises(StopIteration): next(her_glowing_limbs) # ....................{ TESTS ~ reducer }.................... def unit_test_reduce_hint_pep695() -> None: ''' Test the private :mod:`beartype._util.hint.pep.proposal.utilpep695.reduce_hint_pep695` reducer. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.roar import BeartypeDecorHintPep695Exception from beartype._util.hint.pep.proposal.utilpep695 import reduce_hint_pep695 from pytest import raises # ....................{ LOCALS }.................... # Type alias containing *NO* unquoted forward references. type her_dark_locks = str | int # Type alias containing one unquoted forward reference. type floating_in = TheBreathOfNight | bool # ....................{ PASS }.................... # Assert that this reducer returns the type hint underlying the passed type # alias containing *NO* unquoted forward references. assert reduce_hint_pep695( hint=her_dark_locks, exception_prefix='') == str | int # ....................{ FAIL }.................... # Assert that this reducer raises the expected exception when passed a type # alias containing one unquoted forward reference. with raises(BeartypeDecorHintPep695Exception): reduce_hint_pep695(hint=floating_in, exception_prefix='') beartype-0.18.5/beartype_test/a00_unit/data/util/000077500000000000000000000000001461113517100215775ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/util/__init__.py000066400000000000000000000000001461113517100236760ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/util/func/000077500000000000000000000000001461113517100225325ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/util/func/__init__.py000066400000000000000000000000001461113517100246310ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/util/func/data_utilfunccode.py000066400000000000000000000040431461113517100265620ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype generic callable code data submodule.** This submodule predefines low-level class constants exercising known edge cases on behalf of the higher-level :mod:`beartype_test.a00_unit.a20_util.func.test_utilfunccode` submodule. Unit tests defined in that submodule are sufficiently fragile that *no* other submodule should import from this submodule. ''' # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: For completeness, unit tests test the *EXACT* contents of this file. # Changes to this file must thus be synchronized with those tests. # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ IMPORTS }.................... from beartype._util.func.utilfuncmake import make_func # ....................{ CALLABLES ~ dynamic }.................... of_vapours = make_func( func_name='vaulted_with_all_thy_congregated_might', func_code=''' def vaulted_with_all_thy_congregated_might(): return 'Of vapours, from whose solid atmosphere' ''', func_doc=''' Arbitrary callable dynamically declared in-memory. ''') # ....................{ CALLABLES ~ physical }.................... def will_be_the_dome(): ''' Arbitrary non-lambda function physically declared by this submodule. ''' return 'of a vast sepulchre' # ....................{ CALLABLES ~ physical : lambda }.................... thou_dirge = lambda: 'Of the dying year, to which this closing night' ''' Arbitrary lambda function physically declared by this submodule. ''' yellow = lambda: 'and black,', lambda: 'and pale,', lambda: 'and hectic red,' ''' 3-tuple of three arbitrary lambda functions physically declared by this submodule, intentionally declared on the same line so as to induce edge cases in lambda function detection code. ''' beartype-0.18.5/beartype_test/a00_unit/data/util/mod/000077500000000000000000000000001461113517100223565ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/util/mod/__init__.py000066400000000000000000000000001461113517100244550ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a00_unit/data/util/mod/data_utilmodule_bad.py000066400000000000000000000011671461113517100267170ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **unimportable data submodule.** This submodule exercises dynamic importability by providing an unimportable submodule defining an arbitrary attribute. External unit tests are expected to dynamically import this attribute from this submodule. ''' # ....................{ EXCEPTIONS }.................... raise ValueError( 'Can you imagine a fulfilled society? ' 'Whoa, what would everyone do?' ) beartype-0.18.5/beartype_test/a00_unit/data/util/mod/data_utilmodule_good.py000066400000000000000000000021361461113517100271160ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **importable data submodule.** This submodule exercises dynamic importability by providing an importable submodule defining an arbitrary attribute. External unit tests are expected to dynamically import this attribute from this submodule. ''' # ....................{ ATTRIBUTES }.................... attrgood = ( "I started to see human beings as little lonesome, water based, pink " "meat, life forms pushing air through themselves and making noises that " "the other little pieces of meat seemed to understand. I was thinking to " "myself, \"There's five billion people here but we've never been more " "isolated.\" The only result of the aggressive individualism we pursue is " "that you lose sight of your compassion and we go to bed at night " "thinking \"Is this all there is?\" because we don't feel fulfilled." ) ''' Arbitrary module-scope attribute. ''' beartype-0.18.5/beartype_test/a00_unit/data/util/mod/data_utilmodule_line.py000066400000000000000000000030131461113517100271100ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype module getter data submodule.** This submodule predefines module-scoped objects of various types with well-known line numbers guaranteed to remain constant, exercising issues with respect to line numbers in higher-level test submodules. ''' # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: For completeness, unit tests test the *EXACT* contents of this file. # Changes to this file must thus be synchronized with those tests. # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ CALLABLES ~ non-lambda }.................... def like_snakes_that_watch_their_prey(): ''' Arbitrary non-lambda function physically declared by this submodule. ''' return 'from their far fountains,' # ....................{ CALLABLES ~ lambda }.................... ozymandias = lambda: 'I met a traveller from an antique land,' ''' Arbitrary lambda function declared on-disk. ''' which_yet_survive = eval("lambda: 'stamped on these lifeless things'") ''' Arbitrary lambda function declared in-memory. ''' # ....................{ CLASSES }.................... class SlowRollingOn(object): ''' Arbitrary class physically declared by this submodule. ''' THERE = 'many a precipice' beartype-0.18.5/beartype_test/a90_func/000077500000000000000000000000001461113517100176765ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/README.rst000066400000000000000000000007351461113517100213720ustar00rootroot00000000000000.. # ------------------( SYNOPSIS )------------------ ======================== Project Functional Tests ======================== This subpackage provides *all* pytest_-based **functional tests** (i.e., callables exercising the functional behaviour of this project *without* regard to this project's public API) for this project. .. # ------------------( LINKS )------------------ .. _pytest: https://docs.pytest.org beartype-0.18.5/beartype_test/a90_func/__init__.py000066400000000000000000000000001461113517100217750ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/data/000077500000000000000000000000001461113517100206075ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/data/__init__.py000066400000000000000000000000001461113517100227060ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/data/lib/000077500000000000000000000000001461113517100213555ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/data/lib/__init__.py000066400000000000000000000000001461113517100234540ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/data/lib/nuitka/000077500000000000000000000000001461113517100226505ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/data/lib/nuitka/beartype_nuitka.py000077500000000000000000000024701461113517100264160ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Nuitka-specific functional test script** (i.e., script intended to be compiled by the third-party :mod:`nuitka` compiler). This script exercises the expected relationship between the :mod:`beartype.beartype` decorator and the :mod:`nuitka` compiler by ensuring :mod:`nuitka` successfully compiles scripts leveraging that decorator. ''' # ....................{ IMPORTS }.................... from beartype import beartype from beartype.door import TypeHint from beartype.typing import Tuple, Union # ....................{ FUNCTIONS }.................... @beartype def make_type_hints() -> Tuple[TypeHint, ...]: ''' Non-empty tuple containing one or more :class:`beartype.door.TypeHint` instances, exercising that :mod:`nuitka` supports those instances. ''' hint = Union[int, float] type_hints = TypeHint(hint) return tuple(t for t in type_hints) # ....................{ MAIN }.................... # Print the representations of these "TypeHint" instances for verifiability. for type_hint in make_type_hints(): print(type_hint) beartype-0.18.5/beartype_test/a90_func/data/lib/sphinx/000077500000000000000000000000001461113517100226665ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/data/lib/sphinx/Makefile000066400000000000000000000011721461113517100243270ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) beartype-0.18.5/beartype_test/a90_func/data/lib/sphinx/_build/000077500000000000000000000000001461113517100241245ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/data/lib/sphinx/_build/.gitkeep000066400000000000000000000000001461113517100255430ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/data/lib/sphinx/_static/000077500000000000000000000000001461113517100243145ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/data/lib/sphinx/_static/.gitkeep000066400000000000000000000000001461113517100257330ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/data/lib/sphinx/_templates/000077500000000000000000000000001461113517100250235ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/data/lib/sphinx/_templates/.gitkeep000066400000000000000000000000001461113517100264420ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/data/lib/sphinx/beartype_sphinx.py000077500000000000000000000041641461113517100264540ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Sphinx-specific functional test module** (i.e., module intended to be imported *only* by Sphinx's bundled :mod:`sphinx.ext.autodoc` extension from a ``".. automodule:: beartype_sphin"`` statement in the top-level ``index.rst`` file governing this test). This module exercises the expected relationship between the :mod:`beartype.beartype` decorator and the :mod:`sphinx.ext.autodoc` extension by ensuring our decorator reduces to the **identity decorator** (i.e., decorator preserving the decorated callable as is) when imported by that extension. Why? Because of mocking. When :mod:`beartype.beartype`-decorated callables are annotated with one more classes mocked by ``autodoc_mock_imports``, our decorator frequently raises exceptions at decoration time. Why? Because mocking subverts our assumptions and expectations about classes used as annotations. ''' # ....................{ IMPORTS }.................... from beartype import beartype #FIXME: Uncomment to debug that this module is actually being imported. # print('Some phantom, some faint image; till the breast') # ....................{ VALIDATION }.................... def till_the_breast() -> str: ''' Arbitrary callable *not* decorated by the :func:`beartype.beartype` decorator intentionally annotated by one or more arbitrary unignorable type hints to prevent that decorator from silently reducing to a noop. ''' return 'Some phantom, some faint image;' # That callable decorated by @beartype. till_the_breast_beartyped = beartype(till_the_breast) # If beartype did *NOT* correctly detect itself to be running during Sphinx # autodocumentation by preserving that callable as is, raise an exception. if till_the_breast_beartyped is not till_the_breast: raise ValueError( '@beartype failed to reduce to the identity decorator during ' 'automatic Sphinx document generation by "sphinx.ext.autodoc".' ) beartype-0.18.5/beartype_test/a90_func/data/lib/sphinx/conf.py000066400000000000000000000107021461113517100241650ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- 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. # # Note that this is mandatory. If absent, the "autodoc" extension enabled below # fails with the following build-time error: # autodoc: failed to import module 'beartype_sphinx'; the following exception was raised: # No module named 'beartype_sphinx' import os import sys sys.path.insert(0, os.path.abspath('.')) # -- Project information ----------------------------------------------------- project = 'beartype_sphinx' copyright = '2021, @leycec' author = '@leycec' # The full version, including alpha/beta/rc tags release = '0.0.1' # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ # ..................{ BUILTIN }.................. # Builtin extensions unconditionally available under *ALL* reasonably # modern versions of Sphinx uniquely prefixed by "sphinx.ext.". # Builtin extension autogenerating reStructuredText documentation from # class, callable, and variable docstrings embedded in Python modules, # documenting this project's public (and optionally also private) API. 'sphinx.ext.autodoc', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # 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 = ['_build', 'Thumbs.db', '.DS_Store'] # -- 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 = 'alabaster' # 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'] # ....................{ TESTS }.................... #FIXME: Ugh! This decoration raises non-human-readable exceptions. The culprit #appears to be PEP 563. For unknown reasons, Sphinx appears to be forcing PEP #563 for this file in a subversive way. Since this file does *NOT* contain an #actual "from futures import __annotations__" line, @beartype fails to detect #that Sphinx has activated PEP 563. The result is pure chaos. We'll probably #have to do something insane like either: #* Detect when @beartype is running under Sphinx. We already do that, don't we? # Sadly, that detection appears to be failing when run from a "conf.py" file. # If that detection actually worked, then this decoration would just silently # reduce to a noop -- but it's not, so it's not. *shrug* #* Detect if the name of the calling script is "conf.py". Ugh! This is terrible. # #Clearly, the right thing to do is just reduce @beartype to a noop when run from #this file -- because the alternative is madness. Ergo, we need to improve our #Sphinx detection to also detect this file. Shouldn't be terribly arduous... or #it might be, because Sphinx appears to be exec()-ing this file. (Madness!) #Still, there should be some deterministic remnant of this perfidious behaviour #on the call stack, we should think. *sigh* # from beartype import beartype # # @beartype # def sphinx_conf_beartyped_func() -> str: # ''' # Arbitrary callable annotated by one or more arbitrary type hints. # # This callable exercises an edge case in the :mod:`beartype` codebase in # which decorating callables defined in Sphinx-specific ``conf.py`` files # previously caused :mod:`beartype` to raise non-human-readable exceptions. # ''' # # return 'Bother-free is the way to be.' # # # Call this function to ensure that it is, indeed, callable. # sphinx_conf_beartyped_func() beartype-0.18.5/beartype_test/a90_func/data/lib/sphinx/index.rst000066400000000000000000000007761461113517100245410ustar00rootroot00000000000000.. beartype_sphinx documentation master file, created by sphinx-quickstart on Wed Nov 3 00:24:42 2021. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to beartype_sphinx's documentation! ================================================ .. toctree:: :maxdepth: 2 :caption: Contents: .. automodule:: beartype_sphinx :members: Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` beartype-0.18.5/beartype_test/a90_func/doc/000077500000000000000000000000001461113517100204435ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/doc/__init__.py000066400000000000000000000000001461113517100225420ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/doc/test_docreadme.py000066400000000000000000000104251461113517100240010ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Project ``README.rst`` functional tests.** This submodule functionally tests the syntactic validity of this project's top-level ``README.rst`` file. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype_test._util.mark.pytskip import skip_unless_package # ....................{ TESTS }.................... #FIXME: Consider submitting as a StackOverflow post. Dis iz l33t, yo! # If the third-party "docutils" package satisfying this minimum version is # unavailable, skip this test. Note that: # # * "docutils" is the reference standard for parsing reStructuredText (reST). # Unsurprisingly, even Sphinx parses reST with "docutils". # * This test makes assumptions about the "docutils" public API satisfied # *ONLY* by this minimum version. @skip_unless_package(package_name='docutils', minimum_version='0.15') def test_doc_readme(monkeypatch) -> None: ''' Functional test testing the syntactic validity of this project's top-level ``README.rst`` file by monkeypatching the public :mod:`docutils` singleton responsible for emitting warnings and errors to instead convert these warnings and errors into a test failure. Parameters ---------- monkeypatch : MonkeyPatch Builtin fixture object permitting object attributes to be safely modified for the duration of this test. ''' # Defer test-specific imports. from docutils.core import publish_parts from docutils.utils import Reporter from beartype_test._util.path.pytpathmain import get_main_readme_file # Decoded plaintext contents of this project's readme file as a string. # # Note this encoding *MUST* explicitly be passed here. Although macOS and # Linux both default to this encoding, Windows defaults to the single-byte # encoding "cp1252" for backward compatibility. Failing to pass this # encoding here results in a non-human-readable test failure under Windows: # UnicodeDecodeError: 'charmap' codec can't decode byte 0x9d in # position 1495: character maps to README_CONTENTS = get_main_readme_file().read_text(encoding='utf-8') # List of all warning and error messages emitted by "docutils" during # parsing of this project's top-level "README.rst" file. system_messages = [] # Original non-monkey-patched method of the public :mod:`docutils` # singleton emitting warnings and errors *BEFORE* patching this method. system_message_unpatched = Reporter.system_message def system_message_patched(reporter, level, message, *args, **kwargs): ''' Method of the public :mod:`docutils` singleton emitting warnings and errors redefined as a closure collecting these warnings and errors into the local list defined above. ''' # Call this non-monkey-patched method with all passed parameters as is. message_result = system_message_unpatched( reporter, level, message, *args, **kwargs) # If this message is either a warning *OR* error, append this message # to the above list. if level >= reporter.WARNING_LEVEL: system_messages.append(message) # Else, this message is neither a warning *NOR* error. In this case, # silently ignore this message. # Return value returned by the above call as is. return message_result # Temporarily install this monkey-patch for the duration of this test. monkeypatch.setattr( Reporter, name='system_message', value=system_message_patched, ) # Attempt to render this "README.rst" file as reST, implicitly invoking this # monkey-patch. publish_parts(source=README_CONTENTS, writer_name='html4css1') # Assert "docutils" to have emitted *NO* warnings or errors. assert not system_messages beartype-0.18.5/beartype_test/a90_func/package/000077500000000000000000000000001461113517100212715ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/package/__init__.py000066400000000000000000000000001461113517100233700ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/package/test_package_import.py000066400000000000000000000127401461113517100256730ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Project-wide functional importation tests.** This submodule functionally tests the this project's behaviour with respect to imports of both internal subpackages and submodules (unique to this project) *and* external third-party packages and modules. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ....................{ TESTS }.................... def test_package_import_isolation() -> None: ''' Test that importing the top-level lightweight :mod:`beartype` package does *not* accidentally import from one or more heavyweight (sub)packages. This test ensures that the fix cost of the first importation of the :mod:`beartype` package itself remains low -- if not ideally negligible. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype._util.py.utilpyinterpreter import ( get_interpreter_command_words) from beartype_test._util.command.pytcmdrun import ( run_command_forward_stderr_return_stdout) from re import ( compile as re_compile, search as re_search, ) # ....................{ LOCAL }.................... #FIXME: *FRAGILE.* Manually hardcoding module names here invites #desynchronization, particularly with optional third-party dependencies. #Instead, the names of optional third-party packages should be dynamically #constructed from the contents of the "beartype.meta" submodule. # Tuple of uncompiled regular expressions, each matching the # fully-qualified name of *ANY* heavyweight (sub)module or package whose # importation violates our self-imposed constraint of fast importation of # our core @beartype.beartype decorator. # # Note that: # * The third-party "typing_extensions" module has been profiled across # all supported CPython versions to import either faster or only slightly # slower than the standard "typing" module. In either case, both modules # implement sufficiently rapidly as to be ignorable with respect to # importation costs here. See also @posita's stunning profiling work at: # https://github.com/beartype/beartype/pull/103#discussion_r815027198 _HEAVY_MODULE_NAME_RAW_REGEXES = ( r'beartype\.cave', r'beartype\.door', r'beartype\.vale', r'numpy', ) # Uncompiled regular expressions synthesized from this tuple. _HEAVY_MODULE_NAME_RAW_REGEX = '|'.join(_HEAVY_MODULE_NAME_RAW_REGEXES) # Compiled regular expression synthesized from this tuple. _HEAVY_MODULE_NAME_REGEX = re_compile( fr'^({_HEAVY_MODULE_NAME_RAW_REGEX})\b') # print(f'_HEAVY_MODULE_NAME_REGEX: {_HEAVY_MODULE_NAME_REGEX}') # Python code printing all imports (i.e., the contents of the standard # "sys.modules" list) *AFTER* importing the @beartype decorator. _CODE_PRINT_IMPORTS_AFTER_IMPORTING_BEARTYPE = ''' # Import the core @beartype decorator and requisite "sys" machinery. from beartype import beartype from sys import modules # Print a newline-delimited list of the fully-qualified names of all modules # transitively imported by the prior "import" statements. print('\\n'.join(module_name for module_name in modules.keys())) ''' # Tuple of all arguments to be passed to the active Python interpreter rerun # as an external command. _PYTHON_ARGS = get_interpreter_command_words() + ( '-c', _CODE_PRINT_IMPORTS_AFTER_IMPORTING_BEARTYPE, ) # ....................{ PASS }.................... # Run this code isolated to a Python subprocess, raising an exception on # subprocess failure while both forwarding all standard error output by this # subprocess to the standard error file handle of the active Python process # *AND* capturing and returning all subprocess stdout. module_names_str = run_command_forward_stderr_return_stdout( command_words=_PYTHON_ARGS) #FIXME: Actually parse "module_names" for bad entries here, please. # print(module_names) # List of the fully-qualified names of all modules transitively imported by # importing the @beartype decorator in an isolated Python interpreter. module_names = module_names_str.splitlines() # For each such name, assert this name is *NOT* that of a heavyweight # (sub)module or package enumerated above. # # Note that this iteration could, of course, also be more efficiently # implementable as a single composite regex match on the newline-delimited # "module_names_str" string. However, doing so would entirely defeat the # purpose of this iteration: to offer unambiguous and human-readable error # messages in the common event of an importation violation. for module_name in module_names: assert re_search(_HEAVY_MODULE_NAME_REGEX, module_name) is None, ( f'@beartype.beartype improperly imports heavyweight module ' f'"{module_name}" from global scope.' ) beartype-0.18.5/beartype_test/a90_func/pep/000077500000000000000000000000001461113517100204625ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/pep/__init__.py000066400000000000000000000000001461113517100225610ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/pep/test_pep561_static.py000066400000000000000000000275211461113517100244710ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Project-wide functional static type-checker tests.** This submodule functionally tests the this project's compliance with third-party static type-checkers and hence :pep:`561`. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype_test._util.mark.pytskip import ( skip_if_ci, skip_if_pypy, skip_unless_package, skip_unless_pathable, ) # ....................{ TESTS ~ mypy }.................... #FIXME: Consider submitting as a StackOverflow post. Dis iz l33t, yo! #FIXME: Sadly, diz iz no longer l33t. Mypy broke its runtime API. This test now #spuriously fails under CI with a non-human-readable exception message #resembling: # TypeError: 'mypy' is not a package # #The solution? Refactor this test ala the existing test_pep561_pyright() test, #which thankfully already does everything we want here. Rejoice! # If the third-party "mypy" package is unavailable, skip this test. Note that: # * "mypy" is the reference standard for static type-checking in Python. # * Unbelievably, "mypy" violates PEP 8 versioning standards by failing to # define the "mypy.__init__.__version__" attribute, which means that passing # the optional "minimum_version" parameter to the skip_unless_package() # decorator fails on failing to find that attribute. While we *COULD* instead # explicitly test the "mypy.version.__version__" attribute, doing so would # require defining a new and *MUCH* less trivial # @skip_unless_module_attribute decorator. For sanity, we instead currently # accept the importability of the "mypy" package as sufficient, which it # absolutely isn't, but what you gonna do, right? # # Skip this "mypy"-specific functional test unless all of the following apply: # * The "mypy" package is importable under the active Python interpreter. # * The active Python interpreter is *NOT* PyPy. mypy is currently incompatible # with PyPy for inscrutable reasons that should presumably be fixed at some # future point. See also: # https://mypy.readthedocs.io/en/stable/faq.html#does-it-run-on-pypy @skip_unless_package('mypy') @skip_if_pypy() def test_pep561_mypy() -> None: ''' Functional test testing this project's compliance with :pep:`561` by externally running :mod:`mypy` (i.e., the most popular third-party static type checker as of this test) against this project's top-level package. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype._util.py.utilpyinterpreter import ( get_interpreter_command_words) from beartype_test._util.command.pytcmdrun import ( run_command_return_stdout_stderr) from beartype_test._util.path.pytpathmain import ( get_main_mypy_config_file, get_main_package_dir, ) # ....................{ COMMAND }.................... # Tuple of all shell words with which to run the external "mypy" command. MYPY_ARGS = get_interpreter_command_words() + ( # Fully-qualified name of the "mypy" package to be run. '-m', 'mypy', # Absolute dirname of this project's top-level mypy configuration. Since # our "tox" configuration isolates testing to a temporary directory, # mypy is unable to find its configuration without assistance. '--config-file', str(get_main_mypy_config_file()), # Absolute dirname of this project's top-level package. str(get_main_package_dir()), ) # Run this command, raising an exception on subprocess failure while # forwarding all standard output and error output by this subprocess to the # standard output and error file handles of the active Python process. # # Note that we intentionally do *NOT* assert that call to have exited with # a successful exit code. Although mypy does exit with success on local # developer machines, it inexplicably does *NOT* under remote GitHub # Actions-based continuous integration despite "mypy_stderr" being empty. # Ergo, we conveniently ignore the former in favour of the latter. mypy_stdout, mypy_stderr = run_command_return_stdout_stderr( command_words=MYPY_ARGS) # ....................{ ASSERT }.................... # If "mypy" emitted *NO* warnings or errors to either standard # output or error. # # Note that "mypy" predominantly emits both warnings and errors to "stdout" # rather than "stderr", despite this contravening sane POSIX semantics. # They did this because some guy complained about not being able to # trivially grep "mypy" output, regardless of the fact that redirecting # stderr to stdout is a trivial shell fragment (e.g., "2>&1"), but let's # break everything just because some guy can't shell. See also: # https://github.com/python/mypy/issues/1051 # # Assert "mypy" to have emitted *NO* warnings or errors to "stdout". # Unfortunately, doing so is complicated by the failure of "mypy" to # conform to sane POSIX semantics. Specifically: # * If "mypy" succeeds, "mypy" emits to "stdout" a single line resembling: # Success: no issues found in 83 source files # * If "mypy" fails, "mypy" emits to "stdout" *ANY* other line(s). # # Ergo, asserting this string to start with "Success:" suffices. Note this # assertion depends on "mypy" internals and is thus fragile, but that we # have *NO* sane alternative. Specifically, if either... if ( # Mypy emitted one or more characters to standard error *OR*... mypy_stderr or # Mypy emitted standard output that does *NOT* contain this substring... 'Success: no issues found' not in mypy_stdout ): # Print this string to standard output for debuggability, which pytest # then captures and reprints on this subsequent assertion failure. print(mypy_stdout) # Force an unconditional assertion failure. assert False # Else, "mypy" emitted *NO* warnings or errors to either standard output or # error. In this case, encourage this test to succeed by reducing to a noop. # ....................{ TESTS ~ pyright }.................... # If the external third-party "pyright" command is *NOT* pathable (i.e., an # executable command residing in the ${PATH} of the local filesystem), skip this # test. Note that: # * "pyright" is the most popular static type-checker for Python, mostly due to # "pylance" (i.e., the most popular Python language plugin for VSCode, itself # the most popular integrated development environment (IDE)) both bundling # *AND* enabling "pyright" by default. # * "pyright" is implemented in pure-TypeScript (i.e., JavaScript augmented with # type hints transpiling down to pure-JavaScript at compilation time). # * There exists a largely unrelated "pyright" Python package shim unofficially # published at: # https://github.com/RobertCraigie/pyright-python # Sadly, that package does fundamentally unsafe things like: # * Violating privacy encapsulation of "pytest", repeatedly. # * Performing online "npm"-based auto-installation of the "pyright" # JavaScript package if currently not installed. Currently, there exists # *NO* means of disabling that dubious behavior. # Ergo, we resoundingly ignore that high-level package in favour of the # low-level "pyright" command. Such is quality assurance. It always hurts. # # Skip this "pyright"-specific functional test unless all of the following # apply: # * The "pyright" command is in the current "${PATH}". # * Tests are *NOT* running remotely under GitHub Actions-based continuous # integration (CI). Since the only sane means of installing "pyright" under # GitHub Actions is via the third-party "jakebailey/pyright-action" action # (which implicitly exercises this package against "pyright"), explicitly # exercising this package against "pyright" yet again would only needlessly # complicate CI workflows and consume excess CI minutes for *NO* gain. @skip_unless_pathable('pyright') @skip_if_ci() def test_pep561_pyright(monkeypatch) -> None: ''' Functional test testing this project's compliance with :pep:`561` by externally running :mod:`pyright` (i.e., the most popular third-party static type checker as of this test) against this project's top-level package. See Also ---------- :mod:`pytest_pyright` Third-party :mod:`pytest` plugin automating this integration. Since this integration is trivial *and* since :mod:`beartype` assiduously avoids *all* mandatory dependencies, we perform this integration manually. Moreover, this plugin: * Internally violates privacy encapsulation in :mod:`pytest` by widely importing private :mod:`pytest` attributes. * Explicitly admonishes downstream dependencies *not* to depend upon this plugin: This project was created for internal use within another project of mine, support will be minimal. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype.meta import PACKAGE_NAME from beartype._util.py.utilpyversion import get_python_version_major_minor from beartype_test._util.command.pytcmdrun import run_command_forward_output from beartype_test._util.path.pytpathmain import get_main_dir # ....................{ COMMAND }.................... # Tuple of all shell words with which to run the external "pyright" command. PYRIGHT_ARGS = ( # Basename of the external "pyright" command to be run. 'pyright', #FIXME: Note that we *COULD* additionally pass the "--verifytypes" #option, which exposes further "pyright" complaints. Let's avoid doing #so until someone explicitly requests we do so, please. This has dragged #on long enough, people! # Major and minor version of the active Python interpreter, ignoring the # patch version of this interpreter. '--pythonversion', get_python_version_major_minor(), # Relative basename of this project's top-level package. Ideally, the # absolute dirname of this package would instead be passed as: # str(get_main_package_dir()) # # Doing so succeeds when manually running tests via our top-level # "pytest" script but fails when automatically running tests via the # "tox" command, presumably due to "pyright" failing to recognize that # that dirname encapsulates a Python package. *sigh* PACKAGE_NAME, ) # Temporarily change to the root directory for this project *BEFORE* running # the "pyright" command. Unlike the "mypy" command, "pyright" fails to # accept an option or argument specifying the target directory containing # the specified package. If this directory is *NOT* changed to here, # "pyright" fails with a fatal error under "tox" resembling: # File or directory # "/home/leycec/py/beartype/.tox/py311-coverage/tmp/beartype" does not # exist. monkeypatch.chdir(str(get_main_dir())) # Run this command, raising an exception on subprocess failure while # forwarding all standard output and error output by this subprocess to the # standard output and error file handles of the active Python process. run_command_forward_output(command_words=PYRIGHT_ARGS) beartype-0.18.5/beartype_test/a90_func/z90_lib/000077500000000000000000000000001461113517100211465ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/z90_lib/__init__.py000066400000000000000000000000001461113517100232450ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/z90_lib/a00_sphinx/000077500000000000000000000000001461113517100231175ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/z90_lib/a00_sphinx/__init__.py000066400000000000000000000000001461113517100252160ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/z90_lib/a00_sphinx/conftest.py000066400000000000000000000017471461113517100253270ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' :mod:`pytest` **Sphinx test plugin** (i.e., early-time configuration guaranteed to be run by :mod:`pytest` *after* passed command-line arguments are parsed). :mod:`pytest` implicitly imports *all* functionality defined by this module into *all* submodules of this subpackage. ''' # ....................{ IMPORTS }.................... # Attempt to... try: # Import the Sphinx-specific make_app() fixture required to portably test # Sphinx documentation builds. # # Note that the Sphinx-specific test_params() fixture is imported *ONLY* to # expose that fixture to make_app(), which requires that fixture. from sphinx.testing.fixtures import ( make_app, test_params, ) # If doing so fails, silently ignore this failure. except: pass beartype-0.18.5/beartype_test/a90_func/z90_lib/a00_sphinx/test_sphinx.py000066400000000000000000000230731461113517100260460ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **Sphinx** integration tests. This submodule functionally tests the :func:`beartype.beartype` decorator with respect to the third-party Sphinx documentation build toolchain. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype_test._util.mark.pytskip import ( skip, skip_unless_package, ) # ....................{ TESTS }.................... @skip_unless_package('sphinx') def test_sphinx_docs_other(tmp_path) -> None: ''' Functional test validating that the :func:`beartype.beartype` decorator conditionally reduces to a noop when the active Python interpreter is building documentation for the third-party :mod:`sphinx` package. To do so, this test externally runs the ``sphinx-build`` command against a minimal-length Sphinx document tree exercising all known edge cases. Parameters ---------- tmp_path : pathlib.Path Abstract path encapsulating a temporary directory unique to this unit test, created in the base temporary directory. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. # from beartype._util.api.utilapisphinx import ( # _SPHINX_AUTODOC_SUBPACKAGE_NAME) from beartype._util.py.utilpyinterpreter import ( get_interpreter_command_words) from beartype_test._util.command.pytcmdrun import run_command_forward_output from beartype_test._util.path.pytpathtest import ( get_test_func_data_lib_sphinx_dir) # ..................{ SPHINX-BUILD }.................. # Tuple of all shell words with which to run the "sphinx-build" command. SPHINX_ARGS = get_interpreter_command_words() + ( # Fully-qualified name of the Sphinx package to be run. Interestingly, # running this package as a script is semantically equivalent to running # the "sphinx-build" command (with the added bonus of running under the # same version of Python as the active Python interpreter). '-m', 'sphinx', # Avoid caching data into a "{OUTPUTDIR}/.doctrees/" subdirectory. # Although typically advisable, "{OUTPUTDIR}" is an ignorable temporary # test-specific directory deleted immediately after completion of this # test. Caching data would only needlessly consume time and space. '-E', # Enable the HTML mode, rendering HTML-specific documentation files. # Although technically arbitrary, this is the typical default mode. '-b', 'html', # Suffix raised exceptions with tracebacks. This is *CRITICAL.* By # default, Sphinx insanely emits *ONLY* useless exception messages for # fatal "autodoc" errors resembling: # WARNING: autodoc: failed to import module 'beartype_sphinx'; the # following exception was raised: # cannot import name 'BeartypeConf' from partially initialized # module 'beartype' (most likely due to a circular import) '-T', # Treat non-fatal warnings as fatal errors. This is *CRITICAL.* By # default, Sphinx insanely emits non-fatal warnings for fatal "autodoc" # errors resembling: # WARNING: autodoc: failed to import module 'beartype_sphinx'; the # following exception was raised: # No module named 'beartype_sphinx' '-W', # Absolute or relative dirname of a test-specific subdirectory # containing a sample Sphinx structure exercising edge cases in the # @beartype decorator. str(get_test_func_data_lib_sphinx_dir()), # Absolute or relative dirname of a test-specific temporary directory to # which Sphinx will emit ignorable rendered documentation files. str(tmp_path), ) # Run this command, raising an exception on subprocess failure while # forwarding all standard output and error output by this subprocess to the # standard output and error file handles of the active Python process. run_command_forward_output(command_words=SPHINX_ARGS) #FIXME: For unknown reasons, this currently fails despite working for literally #years. The culprit is almost certainly the "sphinx.testing.fixtures" API, which #has yet to be properly documented. That said, we've personally validated that #this API does still define the expected "make_app" fixture. So, it's unclear #exactly why our GitHub Actions-based continuous integration (CI) workflow is #complaining about that fixture being undefined: # ___________________ ERROR at setup of test_sphinx_docs_these ___________________ # file /home/runner/work/beartype/beartype/beartype_test/a90_func/z90_lib/a00_sphinx/test_sphinx.py, line 105 # @skip_unless_package('sphinx') # def test_sphinx_docs_these(make_app, tmp_path) -> None: # E fixture 'make_app' not found # > available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory # > use 'pytest --fixtures [testpath]' for help on them. # # /home/runner/work/beartype/beartype/beartype_test/a90_func/z90_lib/a00_sphinx/test_sphinx.py:105 #FIXME: For the benefit of the community, externally document how to do this #for others at this open issue: # https://github.com/sphinx-doc/sphinx/issues/7008 #Note the trivial "conftest" submodule in this directory. Since this is all #surprisingly easy, a simple comment describing this should easily suffice. @skip('Currently broken due to Sphinx breaking backward compatibility.') @skip_unless_package('sphinx') def test_sphinx_docs_these(make_app, tmp_path) -> None: ''' Functional test validating that the third-party :mod:`sphinx` package successfully builds ReadTheDocs (RTD)-hosted documentation for this project residing in the top-level ``doc/`` subdirectory. To do so, this test *effectively* externally runs the ``sphinx-build`` command against our ``doc/src/` Sphinx document tree. Parameters ---------- make_app : sphinx.testing.util.SphinxTestApp Factory fixture creating and returning a :mod:`pytest`-friendly Sphinx object encapsulating the process of running the ``sphinx-build`` command with the passed parameters. tmp_path : pathlib.Path Abstract path encapsulating a temporary directory unique to this unit test, created in the base temporary directory. ''' # ..................{ IMPORTS }.................. # Defer test-specific imports. from beartype_test._util.command.pytcmdrun import run_command_forward_output from beartype_test._util.path.pytpathmain import get_main_sphinx_source_dir from beartype._util.py.utilpyinterpreter import ( get_interpreter_command_words) # ..................{ SPHINX-BUILD }.................. # Tuple of all shell words with which to run the "sphinx-build" command. SPHINX_ARGS = get_interpreter_command_words() + ( # Fully-qualified name of the Sphinx package to be run. Interestingly, # running this package as a script is semantically equivalent to running # the "sphinx-build" command (with the added bonus of running under the # same version of Python as the active Python interpreter). '-m', 'sphinx', # Avoid caching data into a "{OUTPUTDIR}/.doctrees/" subdirectory. # Although typically advisable, "{OUTPUTDIR}" is an ignorable temporary # test-specific directory deleted immediately after completion of this # test. Caching data would only needlessly consume time and space. '-E', # Enable the HTML mode, rendering HTML-specific documentation files. # Although technically arbitrary, this is the typical default mode. '-b', 'html', #FIXME: Reenable once working. Sadly, Sphinx is currently emitting #warnings we have *NO* idea how to reasonably resolve. *sigh* # # Treat non-fatal warnings as fatal errors. This is *CRITICAL.* By # # default, Sphinx insanely emits non-fatal warnings for fatal "autodoc" # # errors resembling: # # WARNING: autodoc: failed to import module 'beartype_sphinx'; the # # following exception was raised: # # No module named 'beartype_sphinx' # '-W', # Absolute or relative dirname of a test-specific subdirectory # containing a sample Sphinx structure exercising edge cases in the # @beartype decorator. str(get_main_sphinx_source_dir()), # Absolute or relative dirname of a test-specific temporary directory to # which Sphinx will emit ignorable rendered documentation files. str(tmp_path), ) # Run this command, raising an exception on subprocess failure while # forwarding all standard output and error output by this subprocess to the # standard output and error file handles of the active Python process. run_command_forward_output(command_words=SPHINX_ARGS) beartype-0.18.5/beartype_test/a90_func/z90_lib/a90_nuitka/000077500000000000000000000000001461113517100231125ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/z90_lib/a90_nuitka/__init__.py000066400000000000000000000000001461113517100252110ustar00rootroot00000000000000beartype-0.18.5/beartype_test/a90_func/z90_lib/a90_nuitka/test_nuitka.py000066400000000000000000000126331461113517100260230ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Project-wide nuitka integration tests.** This submodule functionally tests that the third-party ``nuitka`` compiler successfully compiles this pure-Python project. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype_test._util.mark.pytskip import ( skip_unless_os_linux, skip_unless_package, skip_unless_pathable, ) # ....................{ TESTS }.................... # Skip this "nuitka"-specific functional test unless all of the following apply: # * The "nuitka" package is importable under the active Python interpreter. # * The third-party GCC compiler is in the current ${PATH}. # * The current platform is *NOT* a Linux distribution. Although "nuitka" # technically *CAN* be used under non-Linux platforms, doing so is typically # non-trivial and likely to unexpectedly explode with catastrophic errors. @skip_unless_os_linux() @skip_unless_package('nuitka') @skip_unless_pathable('gcc') def test_nuitka(capsys, tmp_path) -> None: ''' Functional test testing that the third-party ``nuitka`` compiler successfully compiles a minimal-length example (MLE) extensively leveraging this project. Parameters ---------- capsys :mod:`pytest`-specific systems capability fixture. tmp_path : pathlib.Path Abstract path encapsulating a temporary directory unique to this unit test, created in the base temporary directory. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype._util.py.utilpyinterpreter import ( get_interpreter_command_words) from beartype_test._util.command.pytcmdrun import ( run_command_forward_output, run_command_forward_stderr_return_stdout, ) from beartype_test._util.os.pytosshell import shell_quote from beartype_test._util.path.pytpathtest import ( get_test_func_data_lib_nuitka_file) # ....................{ COMMAND }.................... #FIXME: "nuitka" occasionally attempts to download external third-party #tooling. By default, it interactively prompts the user before doing so. #This is obviously bad for automated testing. Thankfully, "nuitka" avoids #doing so when it detects that stdin has been redirected to "/dev/null". #Ergo, improve the run_command_forward_output() function called below to #*ALWAYS* redirect stdin to "/dev/null". We *ALWAYS* want that everywhere. # Tuple of all shell words with which to run the external "nuitka" command. NUITKA_ARGS = get_interpreter_command_words() + ( # Fully-qualified name of the "nuitka" package to be run. '-m', 'nuitka', # Do *NOT* attempt to cache the compilation of intermediate C artifacts # with the third-party "ccache" command, which may not even necessarily # be installed into the current ${PATH}. '--disable-ccache', #FIXME: Currently disabled as enabling even just this incurs *FAR* too #much time and space. Sadness ensues. # # Instruct "nuitka" to additionally compile the entirety of the # # "beartype" package. By default, "nuitka" only compiles the script # # (specified below). # '--follow-import-to=beartype', # Absolute or relative dirname of a test-specific temporary directory to # which "nuitka" will generate ignorable compilation artifacts. f'--output-dir={shell_quote(str(tmp_path))}', # Absolute filename of a minimal-length example (MLE) leveraging this # project to be compiled by "nuitka". str(get_test_func_data_lib_nuitka_file()), ) # With pytest's default capturing of standard output and error temporarily # disabled... # # Technically, this is optional. Pragmatically, "nuitka" is sufficiently # slow that failing to do this renders this test silent for several tens of # seconds to minutes, which is significantly worse than printing progress. with capsys.disabled(): # Run the "nuitka" command in the current ${PATH} with these options and # arguments, raising an exception on subprocess failure while forwarding # all standard output and error output by this subprocess to the # standard output and error file handles of the active Python process. run_command_forward_output(command_words=NUITKA_ARGS) # ....................{ ASSERT }.................... # Absolute or relative filename of the executable binary generated by # "nuitka" after running the above command. COMPILED_FILENAME = str(tmp_path / 'beartype_nuitka.bin') # Standard output emitted by running this executable binary. COMPILED_STDOUT = run_command_forward_stderr_return_stdout( COMPILED_FILENAME) # Assert that this is the expected output. assert COMPILED_STDOUT == ( '''ClassTypeHint() ClassTypeHint()''') beartype-0.18.5/beartype_test/a90_func/z90_lib/test_equinox.py000066400000000000000000000062371461113517100242570ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **Equinox integration tests.** This submodule functionally tests the :mod:`beartype` package against the third-party Equinox package. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype_test._util.mark.pytskip import skip_unless_package # ....................{ TESTS }.................... @skip_unless_package('equinox') def test_equinox() -> None: ''' Functional test validating that the :mod:`beartype` package successfully type-checks subclasses of superclasses defined by the third-party Equinox package annotated by type hints by the third-party :mod:`jaxtyping` package. See Also -------- https://github.com/patrick-kidger/equinox/issues/584 Upstream Equinox issue strongly inspiring this functional test. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype import beartype from beartype.roar import BeartypeCallHintParamViolation from equinox import Module from jax import numpy as jnp from jaxtyping import ( Array, Float, ) from pytest import raises # ....................{ CLASSES }.................... @beartype class EquinoxModule(Module): ''' Arbitrary subclass of the :class:`equinox.Module` superclass decorated by the :func:`beartype.beartype` decorator. ''' # ....................{ CLASS VARS }.................... float_array: Float[Array, ''] ''' Arbitrary class variable annotated by a :mod:`jaxtyping` type hint. ''' # ....................{ METHODS }.................... def munge_array(self, python_bool: bool) -> Float[Array, '']: ''' Arbitrary method accepting a trivial object to be type-checked. ''' # Arbitrary one-liner is arbitrary. return self.float_array + 1 # ....................{ LOCALS }.................... # JAX-based NumPy array containing arbitrary data. jax_array = jnp.array(1.) # Arbitrary instance of the above Equinox subclass. equinox_module = EquinoxModule(jax_array) # ....................{ FAIL }.................... # Assert that this @beartype-decorated method of this Equinox instance # raises the expected type-checking violation exception when passed an # invalid parameter violating the type hint annotating that parameter. with raises(BeartypeCallHintParamViolation): equinox_module.munge_array('A string is not a boolean.') beartype-0.18.5/beartype_test/a90_func/z90_lib/test_torch.py000066400000000000000000000041451461113517100237020ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' Project-wide **PyTorch integration tests.** This submodule functionally tests the :mod:`beartype` package against the third-party PyTorch package. ''' # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To raise human-readable test errors, avoid importing from # package-specific submodules at module scope. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! from beartype_test._util.mark.pytskip import skip_unless_package # ....................{ TESTS }.................... @skip_unless_package('torch') def test_torch() -> None: ''' Functional test validating that the :mod:`beartype` package raises *no* unexpected exceptions when imported by the third-party PyTorch package. To do so, this test externally imports the :mod:`torch` package against a minimal-length Python snippet exercising all known edge cases. ''' # ....................{ IMPORTS }.................... # Defer test-specific imports. from beartype._util.py.utilpyinterpreter import ( get_interpreter_command_words) from beartype_test._util.command.pytcmdrun import run_command_forward_output # ....................{ LOCALS }.................... # Tuple of all arguments to be passed to the active Python interpreter rerun # as an external command. _PYTHON_ARGS = get_interpreter_command_words() + ('-c', 'import torch',) # ....................{ PASS }.................... # Run this command, raising an exception on subprocess failure while # forwarding all standard output and error output by this subprocess to the # standard output and error file handles of the active Python process. run_command_forward_output(command_words=_PYTHON_ARGS) beartype-0.18.5/beartype_test/conftest.py000066400000000000000000000174321461113517100205000ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' :mod:`pytest` **global test configuration** (i.e., early-time configuration guaranteed to be run by :mod:`pytest` *after* passed command-line arguments are parsed). :mod:`pytest` implicitly imports *all* functionality defined by this module into *all* submodules of this subpackage. See Also ---------- https://github.com/pytest-dev/pytest-asyncio/blob/master/pytest_asyncio/plugin.py :mod:`pytest` plugin strongly inspiring this implementation. Despite its popularity,, pytest-asyncio is mostly unmaintained, poorly commented and documented, overly obfuscatory, has an extreme number of unresolved issues and unmerged pull requests, and just generally exhibits code smells. ''' # ....................{ TODO }.................... #FIXME: Consider refactoring the pytest_pyfunc_call() hook defined below into: #* A pull request against pytest itself. Pytest absolutely requires support for # asynchronous test functions. This is 2021, people. #* A new competing "pytest-async" plugin. This is substantially easier but less # ideal, as pytest *REALLY* both wants and needs this functionality. # ....................{ IMPORTS }.................... from asyncio import ( get_event_loop_policy, new_event_loop, set_event_loop, ) from functools import wraps from inspect import iscoroutinefunction from pytest import hookimpl from warnings import ( catch_warnings, simplefilter, ) # ....................{ HOOKS ~ configure }.................... @hookimpl(hookwrapper=True, tryfirst=True) def pytest_pyfunc_call(pyfuncitem: 'Function') -> None: ''' Hook wrapper called immediately *before* calling the passed test function. Specifically, this hook wrapper: * If this test function is synchronous (i.e., declared with ``def``), preserves this test function as is. * If this test function is asynchronous (i.e., declared with ``async def``), wraps this test function in a synchronous wrapper function synchronously running this test function under an event loop uniquely isolated to this test function. For safety, each asynchronous test function is run under a new event loop. This wrapper wraps all non-wrapper ``pytest_pyfunc_call()`` hooks and is hopefully called *before* all wrapper ``pytest_pyfunc_call()`` hooks. See also `the official pytest hook wrapper documentation `__. Parameters ---------- pyfuncitem : Function :mod:`pytest` object encapsulating the test function to be run. .. _hook wrapper: https://docs.pytest.org/en/6.2.x/writing_plugins.html#hookwrapper-executing-around-other-hooks ''' # Test function to be called by this hook. test_func = pyfuncitem.obj # If this test function is an asynchronous coroutine function (i.e., # callable declared with "async def" containing *NO* "yield" # expressions)... # # Note that we intentionally prefer calling this well-tested tester of the # well-tested "inspect" module rather than our comparable # beartype._util.func.utilfunctest.is_func_coro() tester, which # is hopefully but *NOT* necessarily known to be working here. if iscoroutinefunction(test_func): @wraps(test_func) def test_func_synchronous(*args, **kwargs): ''' Closure synchronously calling the current asynchronous test coroutine function under a new event loop uniquely isolated to this coroutine. ''' # With a warning context manager... with catch_warnings(): # Ignore *ALL* deprecating warnings emitted by the # get_event_loop() function called below. For unknown reasons, # CPython 3.11 devs thought that emitting a "There is no current # event loop" warning (erroneously classified as a # "deprecation") was a wonderful idea. "asyncio" is arduous # enough to portably support as it is. Work with me here, guys! simplefilter('ignore', DeprecationWarning) # Current event loop for the current threading context if any # *OR* create a new event loop otherwise. Note that the # higher-level asyncio.get_event_loop() getter is intentionally # *NOT* called here, as Python 3.10 broke backward compatibility # by refactoring that getter to be an alias for the wildly # different asyncio.get_running_loop() getter, which *MUST* be # called only from within either an asynchronous callable or # running event loop. In either case, asyncio.get_running_loop() # and thus asyncio.get_event_loop() is useless in this context. # Instead, we call the lower-level # get_event_loop_policy().get_event_loop() getter -- which # asyncio.get_event_loop() used to wrap. *facepalm* # # This getter should ideally return "None" rather than creating # a new event loop without our permission if no loop has been # set. This getter instead does the latter, implying that this # closure will typically instantiate two event loops per # asynchronous coroutine test function: # * The first useless event loop implicitly created by this # get_event_loop() call. # * The second useful event loop explicitly created by the # subsequent new_event_loop() call. # # Since there exists *NO* other means of querying the current # event loop, we reluctantly bite the bullet and pay the piper. event_loop_old = get_event_loop_policy().get_event_loop() # Close this loop, regardless of whether the prior # get_event_loop() call just implicitly created this loop, # because the "asyncio" API offers *NO* means of differentiating # these two common edge cases. *double facepalm* event_loop_old.close() # New event loop isolated to this coroutine. # # Note that this event loop has yet to be set as the current event # loop for the current threading context. Explicit is better than # implicit. event_loop = new_event_loop() # Set this as the current event loop for this threading context. set_event_loop(event_loop) # Coroutine object produced by this asynchronous coroutine test # function. Technically, coroutine functions are *NOT* actually # coroutines; they're just syntactic sugar implemented as standard # synchronous functions dynamically creating and returning # asynchronous coroutine objects on each call. test_func_coroutine = test_func(*args, **kwargs) # Synchronously run a new asynchronous task implicitly scheduled to # run this coroutine, ignoring the value returned by this coroutine # (if any) while reraising any exception raised by this coroutine # up the call stack to pytest. event_loop.run_until_complete(test_func_coroutine) # Close this event loop. event_loop.close() # Replace this asynchronous coroutine test function with this # synchronous closure wrapping this test function. pyfuncitem.obj = test_func_synchronous # Perform this test by calling this test function. yield beartype-0.18.5/bin/000077500000000000000000000000001461113517100141705ustar00rootroot00000000000000beartype-0.18.5/bin/profile.bash000077500000000000000000000433571461113517100165060ustar00rootroot00000000000000#!/usr/bin/env bash # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2022 Beartype authors. # See "LICENSE" for further details. # # --------------------( SYNOPSIS )-------------------- # Bash shell script profiling this project against comparable competitors under # a battery of simple (albeit instructive and hopefully unbiased) tests. # # --------------------( DEPENDENCIES )-------------------- # This script only requires "beartype" itself as a mandatory dependencies. # # This script optionally requires these Python 3.x packages: # # * "pydantic". # * "typeguard". # * "typical". # # These packages are trivially installable via this CLI one-liner: # $ sudo -H pip3 install beartype pydantic typeguard typical # ....................{ TODO }.................... #FIXME: Add support for profiling "typical". Doing so will, of course, require #us to add a "typical" ebuild to "raiagent". Fortuitously, Portage already #provides all mandatory dependencies required by "typical". See also upstream: # https://github.com/seandstewart/typical/blob/main/pyproject.toml #FIXME: Add support for profiling "enforce" *AFTER* "enforce" finally supports #Python >= 3.7, which it currently does *NOT*: # https://github.com/RussBaz/enforce/issues/71 #FIXME: Add support for profiling "pytypes" *AFTER* "pytypes" finally supports #Python >= 3.7, which it currently does *NOT*: # https://github.com/Stewori/pytypes/issues/40 #FIXME: If pydantic is optimized with Cython, print a non-fatal warning, as the #resulting timings are likely to be unfairly biased towards pydantic. We'd #rather *NOT* go down the Cython route ourselves, as doing so would require #duplication across a non-Cython and Cython codebase internal to beartype, #which would be absolutely crazy. Instead, a fair test would be to time both #pydantic and beartype under PyPy. So, we should suggest that on detecting #pydantic Cythonization. *shrug* #FIXME: The stdlib "timeit" module should be conditionally replaced with the #superior third-party drop-in replacement "pyperf" module, if the latter is #conditionally available under the active Python interpreter. Indeed, #attempting to run "timeit" under PyPy3 emits eggregious warnings. #FIXME: Consider supplanting with airspeed-velocity (asv), a Python-specific #space and time profiler oriented towards web-hosted tracking of lifetime #performance over all Git commits -- which is pretty awesome, basically: # https://asv.readthedocs.io # ....................{ PREAMBLE }.................... # Enable strictness for sanity. set -e # ....................{ CONSTANTS }.................... # Human-readable version of this profiling suite. VERSION='0.0.3' # Array of all arguments with which to invoke new Python interpreter processes. # PYTHON_ARGS=( python3 ) # PYTHON_ARGS=( python3.6 ) PYTHON_ARGS=( python3.9 ) # PYTHON_ARGS=( pypy3.7 ) # ....................{ GREETING }.................... # Print a greeting preamble. echo "beartype profiler [version]: ${VERSION}" echo # ....................{ FUNCTIONS ~ testers }.................... # is_package(module_name: str) -> bool # # Report success only if a package or module with the passed fully-qualified # name is importable and thus installed under the active Python interpreter. # This tester is strongly inspired by this StackOverflow post: # https://askubuntu.com/a/588392/415719 function is_package() { # Validate and localize all passed arguments. (( $# == 1 )) || { echo 'Expected exactly one argument.' 1>&2 return 1 } local package_name="${1}" # Report success only if this package or module exists. "${PYTHON_ARGS[@]}" -c "import ${package_name}" } # ....................{ CONSTANTS ~ packages }.................... # Package-specific booleans defined *AFTER* defining the is_package() tester. # Non-empty *ONLY* if "typeguard" is installed. IS_PACKAGE_TYPEGUARD="$(is_package 'typeguard' && echo 1)" # ....................{ FUNCTIONS ~ profilers }.................... # profile_func( # label: str, # code_setup: str, # code_func: str, # code_call: str, # num_best: int = 3, # num_loop: int = 100, # num_loop_calls: int = 100, # ) -> None # # Profile the passed snippet of Python code defining a function to be # iteratively decorated by each runtime type checker recognized by this script # (i.e., "beartype", "pytypes", "typeguard") and then repeatedly called by # the passed snippet of arbitrary Python code after first running the passed # snippet of arbitrary Python code exactly once. # # Arguments # ---------- # label : str # Human-readable phrase describing this snippet (e.g., "List[object]"). # code_setup : str # Python code snippet to be run exactly once *BEFORE* repeatedly running the # Python code snippet to be profiled. # code_func : str # Python code snippet defining the undecorated function to be type-checked. # This snippet *MUST* be prefixed by "def ". # code_call : str # Python code snippet calling this function. # num_loop_calls : int = 100 # Number of times to repeatedly call this function. Defaults to 100. # num_loop : int = 100 # Number of times to rerun the complete Python code snippet to be profiled # (i.e., concatenation of the snippets defining and calling this function). # Defaults to 100. # num_best : int = 3 # Number of times to reperform this entire profiling and then take the best # (i.e., minimum) timing of as the "final" profiling timing. Defaults to 3. function profile_callable() { # Validate and localize all passed arguments. (( $# >= 4 )) || { echo 'Expected at least four arguments.' 1>&2 return 1 } local \ label="${1}" \ code_setup="${2}" \ code_func="${3}" \ code_call="${4}" \ num_loop_calls="${5:-100}" \ num_loop="${6:-100}" \ num_best="${7:-3}" \ code_call_repeat \ CODE_SETUP_BEARTYPE \ CODE_SETUP_TYPEGUARD \ CODE_DECOR_BEARTYPE \ CODE_DECOR_TYPEGUARD # Print the passed label as a banner. print_banner "${label}" # Print metadata describing the current profiling regime. echo 'profiling regime:' echo " number of meta-loops: ${num_best}" echo " number of loops: ${num_loop}" echo " number of calls each loop: ${num_loop_calls}" # Python code snippet repeatedly performing the passed function call. code_call_repeat=" for _ in range(${num_loop_calls}): ${code_call}" #FIXME: Conditionally print these strings *ONLY* if the caller explicitly #requests verbosity (e.g., by passing an option "-v" or "--verbose"), #presumably by leveraging the getopt() Bash builtin. # # Print the function to be called. # echo -e "function to be decorated with type-checking:\n${code_func}\n" # # # Print the function calls to be performed. # echo -e "function calls to be type-checked:${code_call_repeat}\n" # Python code snippet importing the "beartype" decorator. CODE_SETUP_BEARTYPE='from beartype import beartype ' # Python code snippet importing the "typeguard" decorator. CODE_SETUP_TYPEGUARD='from typeguard import typechecked ' # Python code snippet decorating the passed function with "beartype". CODE_DECOR_BEARTYPE='@beartype ' # Python code snippet decorating the passed function with "typeguard". CODE_DECOR_TYPEGUARD='@typechecked ' # Profile this undecorated definition of this function as a baseline. profile_snippet 'decoration [none ]: ' \ "${code_setup}" \ "${code_func}" \ "${num_loop}" "${num_best}" # Profile the "beartype"-decorated definition of this function. profile_snippet 'decoration [beartype ]: ' \ "${CODE_SETUP_BEARTYPE}${code_setup}" \ "${CODE_DECOR_BEARTYPE}${code_func}" \ "${num_loop}" "${num_best}" # If "typeguard" is installed... if [[ -n "${IS_PACKAGE_TYPEGUARD}" ]]; then # Profile the "typeguard"-decorated definition of this function. profile_snippet 'decoration [typeguard]: ' \ "${CODE_SETUP_TYPEGUARD}${code_setup}" \ "${CODE_DECOR_TYPEGUARD}${code_func}" \ "${num_loop}" "${num_best}" fi # Profile this undecorated definition and repeated calling of this function # as a baseline. profile_snippet 'decoration + calls [none ]: ' \ "${code_setup}" \ "${code_func}${code_call_repeat}" \ "${num_loop}" "${num_best}" # Profile the "beartype"-decorated definition and repeated calling of this # function. profile_snippet 'decoration + calls [beartype ]: ' \ "${CODE_SETUP_BEARTYPE}${code_setup}" \ "${CODE_DECOR_BEARTYPE}${code_func}${code_call_repeat}" \ "${num_loop}" "${num_best}" # If "typeguard" is installed... if [[ -n "${IS_PACKAGE_TYPEGUARD}" ]]; then # Profile the "beartype"-decorated definition and repeated calling of # this function. profile_snippet 'decoration + calls [typeguard]: ' \ "${CODE_SETUP_TYPEGUARD}${code_setup}" \ "${CODE_DECOR_TYPEGUARD}${code_func}${code_call_repeat}" \ "${num_loop}" "${num_best}" fi } # profile_snippet( # label: str, # code_setup: str, # code_profile: str, # num_loop: int = 100, # num_best: int = 3, # ) -> None # # Profile the passed snippet of arbitrary Python code to be timed after first # running the passed snippet of arbitrary Python code exactly once. # # Arguments # ---------- # label : str # Human-readable phrase describing this code (e.g., "decoration [none]: "). # code_setup : str # Python code snippet to be run exactly once *BEFORE* repeatedly running the # Python code snippet to be profiled. # code_profile : str # Python code snippet to be profiled. # num_loop : int = 100 # Number of times to rerun the complete Python code snippet to be profiled # (i.e., concatenation of the snippets defining and calling this function). # Defaults to 100. # num_best : int # Number of times to reperform this entire profiling and then take the best # (i.e., minimum) timing of as the "final" profiling timing. Defaults to 3. function profile_snippet() { # Validate and localize all passed arguments. (( $# >= 3 )) || { echo 'Expected at least three arguments.' 1>&2 return 1 } local \ label="${1}" \ code_setup="${2}" \ code_profile="${3}" \ num_loop="${4:-100}" \ num_best="${5:-3}" # Print the passed label *BEFORE* profiling, which (thankfully) implicitly # prints succinct timings after completion. echo -n "${label}" # Profile these snippets. command "${PYTHON_ARGS[@]}" -m timeit \ -n "${num_loop}" \ -r "${num_best}" \ -s "${code_setup}" \ "${code_profile}" } # ....................{ FUNCTIONS ~ printers }.................... # print_banner(label: str) -> None # # Print the passed terse human-readable string containing *NO* newlines as a # banner message, both centered to the current terminal width and padded # (i.e., preceded and followed) by "=" characters. # # See also this StackExchange answer strongly inspiring this implementation: # https://unix.stackexchange.com/a/267730/117478 function print_banner() { # Validate and localize all passed arguments. (( $# == 1 )) || { echo 'Expected one argument.' 1>&2 return 1 } local label="${1}" label_len terminal_len padding_len padding # If either: # # * Stdout (i.e., standard output) is *NOT* attached to an interactive # terminal *OR*... # * The "tput" command is *NOT* in the current ${PATH}... # # Then print this label as is and immediately return. { [[ -t 1 ]] && is_command 'tput'; } || { echo "${label}" return 0 } # Else, stdout is attached to an interactive terminal *AND* the "tput" # command is in the current ${PATH}. # Number of characters in this label. label_len="${#label}" # Number of characters comprising each line of this terminal. # terminal_len="$(tput cols)" terminal_len=80 # Number of characters comprising both the prefixing and suffixing padding. padding_len="$(((terminal_len - label_len - 2)/2))" # "=" character repeated 500 times, to be truncated below. padding="$(printf '%0.1s' ={1..500})" # Magically print this label as a banner. printf '\n%*.*s %s %*.*s\n'\ 0 \ "${padding_len}" \ "${padding}" \ "${label}" \ 0 \ "${padding_len}" \ "${padding}" } # ....................{ FUNCTIONS ~ testers }.................... # is_command(command_basename: str) -> bool # # Report success only if a command with the passed basename is available in the # current "${PATH}". # # See also this StackExchange answer strongly inspiring this implementation: # https://stackoverflow.com/a/46013739/2809027 function is_command() { # Validate and localize all passed arguments. (( $# == 1 )) || { echo 'Expected one argument.' 1>&2 return 1 } local command_basename="${1}" command -v "${command_basename}" >/dev/null } # ....................{ VERSIONS }.................... # Print the current basename and version of Python 3.x. echo "python [basename]: ${PYTHON_ARGS[*]}" echo -n 'python [version]: ' command "${PYTHON_ARGS[@]}" --version # Print the current version of beartype *BEFORE* profiling. command "${PYTHON_ARGS[@]}" -c ' import beartype print("beartype [version]: " + beartype.__version__)' # If pydantic is installed, print the current version of pydantic as well. if is_package 'pydantic'; then command "${PYTHON_ARGS[@]}" -c ' import pydantic print("pydantic [version]: " + pydantic.version.VERSION)' fi #FIXME: Also call "is_package typeguard" above. # If typeguard is installed, print the current version of typeguard as well. # Note that the "typeguard" package fails to explicitly publish its version, so # we fallback to the setuptools-based Hard Way. *sigh* if [[ -n "${IS_PACKAGE_TYPEGUARD}" ]]; then command "${PYTHON_ARGS[@]}" -c ' import pkg_resources print("typeguard [version]: " + pkg_resources.require("typeguard")[0].version)' fi #FIXME: Uncomment after implementing "typical" support. # If typical is installed, print the current version of typical as well. # if is_package 'typical'; then # command "${PYTHON_ARGS[@]}" -c ' # import pkg_resources # print("typeguard [version]: " + pkg_resources.require("typeguard")[0].version)' # fi # ....................{ PROFILE ~ scalar }.................... profile_callable 'str' '' \ 'def monkey_people(tree_land: str) -> str: return tree_land' \ 'monkey_people("Then they began their flight; and the flight of the Monkey-People through tree-land is one of the things nobody can describe.")' # ....................{ PROFILE ~ union }.................... profile_callable 'Union[int, str]' \ 'from typing import Union' \ 'def panther_canter( quick_foot: Union[int, str]) -> Union[int, str]: return quick_foot' \ 'panther_canter("We dare not wait for thee. Follow, Baloo. We must go on the quick-foot -- Kaa and I.")' # ....................{ PROFILE ~ container : list }.................... # To ensure fairness in comparing beartype's non-naive random sampling of # container items against runtime type-checkers naive brute-forcing of *ALL* # container items, set the "num_loop_calls" argument to be the expected number # of calls needed to recursively check all items of a container containing # only non-container items (as formalized by our front-facing "README.fst"). To # do so, interactively run the following from within a Python REPL: # # >>> import math # >>> get_num_loop = lambda n: round(math.log(n)*n+1/2+0.5772156649*n+1/n) # # Pass this lambda the total number of container items. The result is # # the "num_loop_calls" argument to be passed. # >>> get_num_loop(100) # 519 # # When profiling naive runtime type-checkers under large containers, reduce # both the number of iterations and iterations of iterations (i.e., "best of") # to avoid infinitely halting the active process. #FIXME: Temporarily commented out, as the following tests are more specific and #thus more instructive. # NUM_LIST_ITEMS=1000 # profile_callable "List[object] of ${NUM_LIST_ITEMS} items" " # from typing import List # THOUSANDS_OF_TIRED_VOICES = list(range(${NUM_LIST_ITEMS}))" \ # 'def parade_song(camp_animals: List[object]) -> List[object]: # return camp_animals' \ # 'parade_song(THOUSANDS_OF_TIRED_VOICES)' 7485 1 1 NUM_LIST_ITEMS=1000 profile_callable "List[int] of ${NUM_LIST_ITEMS} items" " # from typing import List List = list TEN_FOOT_TEAMS_OF_THE_FORTY_POUNDER_TRAIN = list(range(${NUM_LIST_ITEMS}))" \ 'def gun_teams(elephants: List[int]) -> List[int]: return elephants' \ 'gun_teams(TEN_FOOT_TEAMS_OF_THE_FORTY_POUNDER_TRAIN)' 7485 1 1 NUM_SEQUENCE_ITEMS_EACH=10 profile_callable "List[Sequence[MutableSequence[int]]] of ${NUM_SEQUENCE_ITEMS_EACH} items each" " # from typing import List, MutableSequence, Sequence from collections.abc import MutableSequence, Sequence List = list WAY_OF_THE_WAR_HORSE = list( tuple( list(range(${NUM_SEQUENCE_ITEMS_EACH})) for _ in range(${NUM_SEQUENCE_ITEMS_EACH}) ) for _ in range(${NUM_SEQUENCE_ITEMS_EACH}) )" \ 'def lancers_hussars_and_dragoons( cavalry_horses: List[Sequence[MutableSequence[int]]]) -> ( List[Sequence[MutableSequence[int]]]): return cavalry_horses' \ 'lancers_hussars_and_dragoons(WAY_OF_THE_WAR_HORSE)' 7485 1 1 beartype-0.18.5/bin/profile_decoration.py000077500000000000000000000011541461113517100204150ustar00rootroot00000000000000#!/usr/bin/env python3 # Torture test designed to find hotspots in @beartype at decoration time. import yappi from beartype import beartype def run_beartype_decorator(): for _ in range(10000): @beartype def ugh(text: list[str]) -> int: # def ugh(text: str) -> int: return len(text) yappi.set_clock_type("cpu") # Use set_clock_type("wall") for wall time yappi.start() try: run_beartype_decorator() finally: # yappi.get_func_stats().print_all() func_stats = yappi.get_func_stats() func_stats.save('yappi.prof', 'PSTAT') yappi.stop() yappi.clear_stats() beartype-0.18.5/conftest.py000066400000000000000000000165571461113517100156350ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Root test configuration** (i.e., early-time configuration guaranteed to be run by :mod:`pytest` *before* passed command-line arguments are parsed) for this test suite. Caveats ---------- For safety, this configuration should contain *only* early-time hooks absolutely required by :mod:`pytest` design to be defined in this configuration. Hooks for which this is the case (e.g., :func:`pytest_addoption`) are explicitly annotated as such in official :mod:`pytest` documentation with a note resembling: Note This function should be implemented only in plugins or ``conftest.py`` files situated at the tests root directory due to how pytest discovers plugins during startup. This file is the aforementioned ``conftest.py`` file "...situated at the tests root directory." ''' # ....................{ IMPORTS }.................... from collections.abc import Callable from pytest import Function from typing import Optional # ....................{ HOOKS ~ ini }.................... def pytest_configure(config) -> None: ''' Hook programmatically configuring the top-level ``"pytest.ini"`` file. ''' # Programmatically add our custom "run_in_subprocess" mark, enabling tests # to notify the pytest_pyfunc_call() hook that they require isolation to a # Python subprocess of the current Python process. config.addinivalue_line( 'markers', f'{_MARK_NAME_SUBPROCESS}: mark test to run in an isolated subprocess', ) # ....................{ HOOKS ~ test : run }.................... def pytest_pyfunc_call(pyfuncitem: Function) -> Optional[bool]: ''' Hook intercepting the call to run the passed :mod:`pytest` test function. Specifically, this test: * If this test has been decorated by our custom ``@pytest.mark.run_in_subprocess`` marker, runs this test in a Python subprocess of the current Python process isolated to this test. * Else, runs this test in the current Python process by deferring to the standard :mod:`pytest` logic for running this test. Parameters ---------- pyfuncitem: Function :mod:`pytest`-specific object encapsulating the current test function being run. Returns ---------- Optional[bool] Either: * If this hook ran this test, :data:`True`. * If this hook did *not* run this test, :data:`None`. See Also ---------- https://github.com/ansible/pytest-mp/issues/15#issuecomment-1342682418 GitHub comment by @pelson (Phil Elson) strongly inspiring this hook. ''' # If this test has been decorated by our custom # @pytest.mark.run_in_subprocess marker... if _MARK_NAME_SUBPROCESS in pyfuncitem.keywords: # Defer hook-specific imports. from functools import partial from multiprocessing import Process from pytest import fail # Test function to be run. test_func = pyfuncitem.function # Partial object encapsulating the _run_test_in_subprocess() helper # bound to the current "pyfuncitem" object encapsulating this test. # # Note that this logic is robust under POSIX-compliant platforms (e.g., # Linux, macOS) but *EXTREMELY* fragile under Windows, where the # "multiprocessing.Process" class sadly leverages the mostly unusable # "pickle" module rather than the mostly usable third-party "dill" # module. Notably, we intentionally: # * Leverage a "partial" object (which "pickle" silently supports) # rather than a closure (which "pickle" rejects with # non-human-readable exceptions). # * Pass the actual low-level test function being tested (which "pickle" # silently supports) rather than the high-level "pyfuncitem" object # encapsulating that function (which "pickle" rejects with # non-human-readable exceptions). test_func_in_subprocess = partial(_test_func_in_subprocess, test_func) # Python subprocess tasked with running this test. test_subprocess = Process(target=test_func_in_subprocess) # Begin running this test in this subprocess. test_subprocess.start() # Block this parent Python process until this test completes. test_subprocess.join() # If this subprocess reports non-zero exit status, this test failed. In # this case... if test_subprocess.exitcode != 0: # Human-readable exception message to be raised. exception_message = ( f'Test "{pyfuncitem.name}" failed in isolated subprocess with:') # Raise a pytest-compliant exception. raise fail(exception_message, pytrace=False) # Else, this subprocess reports zero exit status. In this case, this # test succeeded. # Notify pytest that this hook successfully ran this test. return True # Notify pytest that this hook avoided attempting to run this test, in which # case pytest will continue to look for a suitable runner for this test. return None # ....................{ PRIVATE ~ globals }.................... _MARK_NAME_SUBPROCESS = 'run_in_subprocess' ''' **Subprocess mark** (i.e., name of our custom :mod:`pytest` mark, enabling tests to notify the :func:`.pytest_pyfunc_call` hook that they require isolation to a Python subprocess of the current Python process). ''' # ....................{ PRIVATE ~ classes }.................... class _UnbufferedOutputStream(object): ''' **Unbuffered standard output stream** (i.e., proxy object encapsulating a buffered standard output stream by forcefully flushing that stream on all writes to that stream). See Also ---------- https://github.com/ansible/pytest-mp/issues/15#issuecomment-1342682418 GitHub comment by @pelson (Phil Elson) strongly inspiring this class. ''' def __init__(self, stream) -> None: self.stream = stream def write(self, data) -> None: self.stream.write(data) self.stream.flush() def writelines(self, datas) -> None: self.stream.writelines(datas) self.stream.flush() def __getattr__(self, attr: str) -> object: return getattr(self.stream, attr) # ....................{ PRIVATE ~ functions }.................... def _test_func_in_subprocess(test_func: Callable) -> object: ''' Run the passed :mod:`pytest` test function isolated to a Python subprocess of the current Python process. Parameters ---------- test_func : Callable Test function to be run. Returns ---------- object Arbitrary object returned by this test if any *or* :data:`None`. ''' # Defer subpracess-specific imports. import sys # Monkey-patch the unbuffered standard error and output streams of this # subprocess with buffered equivalents, ensuring that pytest will reliably # capture *all* standard error and output emitted by running this test. sys.stderr = _UnbufferedOutputStream(sys.stderr) sys.stdout = _UnbufferedOutputStream(sys.stdout) # Run this test and return the result of doing so. return test_func() beartype-0.18.5/doc/000077500000000000000000000000001461113517100141655ustar00rootroot00000000000000beartype-0.18.5/doc/404.rst000066400000000000000000000003431461113517100152260ustar00rootroot00000000000000======== Beartype ======== Oops! The page you are looking for was not found. Maybe you'll find what you're looking for by searching the documentation or returning to the `home page `_. .. _rtd: https://beartype.rtfd.org beartype-0.18.5/doc/Makefile000066400000000000000000000011711461113517100156250ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = src BUILDDIR = trg # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) beartype-0.18.5/doc/RELEASE.rst000066400000000000000000000535341461113517100160110ustar00rootroot00000000000000.. # ------------------( SYNOPSIS )------------------ ========= Releasing ========= Stable releases are manually published with a rigorous procedure. The tl;dr is: #. A stable commit is **tagged** with an annotated Git tag of the proper format expected by the automation triggered by... #. That tag is **pushed** to GitHub_, which triggers the automated publication of both source tarballs and binary wheels of this stable commit in various popular formats to both GitHub_ itself and `PyPI`_ using the GitHub_ Actions CI/CD workflow configured by the ``.github/workflows/pythonrelease.yml`` file. (\ *Phew!*\ ) While technically optional, this procedure reduces the likelihood of installation and usage woes by downstream consumers (\ *e.g.,* end users, package maintainers) and is thus effectively mandatory. .. # ------------------( TABLE OF CONTENTS )------------------ .. # Blank line. By default, Docutils appears to only separate the subsequent .. # table of contents heading from the prior paragraph by less than a single .. # blank line, hampering this table's readability and aesthetic comeliness. | .. # Table of contents, excluding the above document heading. While the .. # official reStructuredText documentation suggests that a language-specific .. # heading will automatically prepend this table, this does *NOT* appear to .. # be the case. Instead, this heading must be explicitly declared. .. contents:: **Contents** :local: .. # ------------------( DESCRIPTION )------------------ Procedure ========= Beartype is releasable to all supported platforms as follows: #. (\ *Optional*\ ) **Validate reStructuredText (reST) rendering.** The human-readable description for this release derives directly from `the top-level README.rst file `__ for this project. Sadly, PyPI's reST renderer supports only a proper subset of the syntax supported by the reST standard – itself only a proper subset of the syntax supported by Sphinx. If `this file `__ contains syntax unsupported by PyPI's reST renderer, PyPI erroneously preserves this file as plaintext rather than rendering this file as HTML. To avoid this: #. Install the ``collective.checkdocs`` Python package. .. code-block:: shell-session $ sudo pip3 install collective.checkdocs #. Validate the PyPI-specific compatibility of `this file `__. .. code-block:: shell-session $ python3 setup.py checkdocs #. After submitting this release to PyPI below, manually browse to `the PyPI-hosted page `__ for this project and verify by cursory inspection that this project's description is rendered as HTML. #. (\ *Optional*\ ) **Install** wheel_, a third-party pure-Python package permitting this release to be packaged into a cross-platform pre-compiled binary distribution supported by both PyPI_ and ``pip``. This optional dependency augments setuptools with the ``bdist_wheel`` subcommand invoked below when locally testing the generation of binary wheels. .. code-block:: shell-session $ sudo pip3 install wheel #. (\ *Optional*\ ) **Test packaging both a source tarball and binary wheel.** .. code-block:: shell-session $ python3 setup.py sdist bdist_wheel #. (\ *Optional*\ ) **List the contents of this source tarball,** where ``${version}`` is the purely numeric version of this release (e.g., ``0.4.1``). Verify by inspection that no unwanted paths were packaged. .. code-block:: shell-session $ tar -tvzf dist/beartype-${version}.tar.gz | less #. (\ *Optional*\ ) **Test the local installation of this release.** If installation of this release differs from that of prior releases, testing *before* publishing this release to PyPI_ and elsewhere is advisable. #. **Test this source tarball locally.** #. **Create a new empty (venv)** (i.e., virtual environment). .. code-block:: shell-session $ python3 -m venv --clear /tmp/beartype-sdist #. **Install this source tarball into this venv.**\ [#venv]_ .. code-block:: shell-session $ /tmp/beartype-sdist/bin/pip3 install wheel $ /tmp/beartype-sdist/bin/pip3 install dist/beartype-${version}.tar.gz #. **Test this release from this venv.** .. code-block:: shell-session $ cd /tmp && /tmp/beartype-sdist/bin/beartype try #. **Remove this venv and return to the prior directory.** .. code-block:: shell-session $ rm -rf /tmp/beartype-sdist && cd - #. **Test this binary wheel locally.** #. **Create a new empty venv.** .. code-block:: shell-session $ python3 -m venv --clear /tmp/beartype-wheel #. **Install this binary wheel into this venv.**\ [#venv]_ .. code-block:: shell-session $ /tmp/beartype-wheel/bin/pip3 install \ dist/beartype-${version}-py3-none-any.whl #. **Test this release from this venv.** .. code-block:: shell-session $ cd /tmp && /tmp/beartype-wheel/bin/beartype try #. **Remove this venv and sample simulation and return to the prior directory.** .. code-block:: shell-session $ rm -rf /tmp/beartype-wheel /tmp/sample_sim && cd - #. (\ *Optional*\ ) **Bump release metadata.** Assuming the prior release followed these instructions, release metadata has already been bumped in preparation for the next (i.e., this) release. If another bump is required (e.g., to upgrade this release from a patch to a minor or even major update), this bump should be performed *before* tagging this release. For details, see the eponymous *"Bump release metadata."* instructions below. #. (\ *Optional*\ ) **List all existing tags.** For reference, listing all previously created tags *before* creating new tags is often advisable. .. code-block:: shell-session $ git tag #. **Create an announcement commit,** ideally as an **empty commit** (i.e., commit containing only a message rather than both changes *and* a message). Empty announcements reduce the likelihood of introducing last-minute instability into an otherwise stable release. Of course, this assumes that the prior non-empty commit passed all continuous integration (CI) hosts. .. code-block:: shell-session $ git commit --allow-empty This commit should have a message whose: * First line is of the format ``"beartype {version} released."``, where ``{version}`` is the current value of the ``beartype.__version__`` global. * Next line lists all GitHub Sponsors at the Papa Bear tier and higher. **THIS IS CRITICALLY VITAL. DO _NOT_ NEGLECT THIS WONDERFUL USERBASE.** As of now, relevant Sponsors include... *nobody*, sadly! We've failed money. * Remaining lines are a changelog synopsizing the significant changes implemented by this release -- ideally in GitHub-flavoured Markdown (GHFM) format, as depicted below. Note that this format requires enabling the ``[commit] cleanup = scissors`` setting in the ``~/.gitconfig`` file, as ``git`` otherwise treats lines prefixed by "#" characters (e.g., Markdown headers) in commit messages as ignorable comments to be removed. For example: .. code-block:: markdown **Beartype 0.0.1** released. This major release brings titillating support for **[this][this]**, **[that][that]**, and **PEP numbers compliance**. This major release resolves **some issues** and merges **some pull requests.** But first, a quiet word from our wondrous sponsors. They are monocled QA wizards who serve justice while crushing bugs for humanity. High fives, please! ## Beartype Sponsors * [**ZeroGuard:** The Modern Threat Hunting Platform](https://zeroguard.com). *All the signals, All the time.* Thunderous applause echoes through the cavernous confines of the Bear Den. :clap: :polar_bear: :clap: And now... the moment we've waited for. A heinous display of plaintext that assaults all five senses simultaneously. ## Compatibility Improved * **Python >= 3.9.0.** This release officially supports the first stable release of the Python 3.9.x series (i.e., Python 3.9.0). ## Compatibility Broken * **None.** This release preserves backward compatibility with the prior stable release. ## Packaging Improved * **macOS Homebrew tap,** just 'cause. ## Dependencies Bumped * **`setuptools` >= 38.2.0,** just 'cause. ## Features Added * **Type library,** just 'cause. ## Features Improved * **`@beartype` performance,** just 'cause. ## Features Optimized * **`@beartype` performance,** just 'cause. ## Features Deprecated * **`@beartype.moar` submodule,** to be removed in `beartype` 0.1.0. ## Features Removed * **None.** ## Issues Resolved * **#3,** just 'cause. * **pypa/pip#6163,** just 'cause. ## Tests Improved * **GitLab CI + `tox`,** just 'cause. ## Documentation Revised * **Installation instructions,** just 'cause. ## API Changed * Renamed: * `beartype.roar` subpackage to `beartype.hoar`. * Added: * `beartype.soar` submodule. * Improved: * `beartype.lore` subpackage. * Removed: * `beartype._boar` submodule. [this]: https://this.com [that]: https://that.com #. **Tag this commit.** An annotated tag\ [#tags]_ should be created whose: * Name is ``v{version}``, where ``{version}`` is the current value of the ``beartype.__version__`` global. * Message is the same commit message created above. .. code-block:: shell-session $ git tag -a v{version} #. **Bump release metadata.** In preparation for developing the next release, the ``beartype.meta.VERSION`` global should be incremented according to the `best practices `__ detailed below. #. **Create another announcement commit.** This commit should have a message whose first line is of the format ``"beartype {version} started."``, where ``{version}`` is the new value of the ``beartype.__version__`` global. Since no changelog for this release yet exists, a single-line message suffices for this commit. For example:: **Beartype 0.4.1** started. #. **Push these commits and tags.** After doing so, GitHub will automatically publish source tarballs and binary wheels in various popular formats (e.g., ``.zip``, ``.tar.bz2``) containing the contents of this repository at this tagged commit to this project's `GitHub releases page `__ and `PyPI releases portal `__. No further work is required to distribute this release to *any* service – excluding third-party package managers (e.g., Anaconda_) and platforms (e.g., Linux distributions), which typically require manual intervention. **This release has now been officially distributed to GitHub and PyPI.** .. code-block:: shell-session $ git push && git push --tags #. **Reinstall this package.** Doing so updates the setuptools-specific version associated with its internal installation of this package, ensuring that subsequent attempts to install downstream packages requiring this version (e.g., BETSE_, BETSEE_) will behave as expected. .. code-block:: shell-session $ pip3 install -e . #. (\ *Optional*\ ) **Test the remote installation of this release.** #. **Test this release on** `Test PyPI`_. Note that, as this server is a moving target, the `official instructions `__ *always* supersede those listed for convenience below. #. **Create a** `Test PyPI user`_. #. **Create a** ``~/.pypirc`` **dotfile,** ideally by following the `official instructions `__ for doing so. #. **Register this project with** `Test PyPI`_. .. code-block:: shell-session $ python3 setup.py register -r testpypi #. **Browse to this project on** `Test PyPI`_. Verify by inspection all identifying metadata at the following URL: https://testpypi.python.org/pypi/beartype #. **Upload this source tarball and binary wheel to** `Test PyPI`_. .. code-block:: shell-session $ twine upload -r testpypi dist/beartype-${version}* #. **Create a new empty venv.** .. code-block:: shell-session $ python3 -m venv --clear /tmp/beartype-pypi #. **Install this release into this venv.**\ [#venv]_ .. code-block:: shell-session $ /tmp/beartype-pypi/bin/pip3 install \ install -i https://testpypi.python.org/pypi beartype #. **Test this release from this venv.** .. code-block:: shell-session $ cd /tmp && /tmp/beartype-pypi/bin/beartype try #. **Remove this venv and sample simulation and return to the prior directory.** .. code-block:: shell-session $ rm -rf /tmp/beartype-pypi /tmp/sample_sim && cd - #. (\ *Obsolete*\ ) **Manually publish this release to** `PyPI`_. .. note:: The following instructions have been obsoleted by the GitHub_ Actions CI/CD workflow configured by the ``.github/workflows/pythonrelease.yml`` file, which now automates publication of both source tarballs and binary wheels of this this stable release in various popular formats to both GitHub_ itself and `PyPI`_ when pushing the tag for this release above. #. **Create a** `PyPI user`_. #. **Validate the primary e-mail address associated with this account,** which `PyPI`_ requires as a hard prerequisite to performing the first upload (and hence creation) for this project. #. **Create a** ``~/.pypirc`` **dotfile,** ideally by following the `official instructions `__ for doing so. #. **Package both a source tarball and binary wheel.** .. code-block:: shell-session $ python3 setup.py sdist bdist_wheel #. **Upload this source tarball and binary wheel to** `PyPI`_. If this is the first such upload for this project, a `PyPI`_-hosted project page will be implicitly created by this upload. `PyPI` neither requires, recommends, nor supports end user intervention in this process. .. code-block:: shell-session $ twine upload dist/beartype-${version}* #. (\ *Optional*\ ) **Browse to this project on** `PyPI`_. Verify by inspection all identifying metadata at the following URL: https://pypi.python.org/pypi/beartype #. (\ *Optional*\ ) **Test the installation of this release from** `PyPI`_. #. **Create a new empty venv.** .. code-block:: shell-session $ python3 -m venv --clear /tmp/beartype-pypi #. **Install this release into this venv.**\ [#venv]_ .. code-block:: shell-session $ /tmp/beartype-pypi/bin/pip3 install beartype #. **Test this release from this venv.** .. code-block:: shell-session $ cd /tmp && /tmp/beartype-pypi/bin/beartype try #. **Remove this venv and sample simulation and return to the prior directory.** .. code-block:: shell-session $ rm -rf /tmp/beartype-pypi /tmp/sample_sim && cd - #. (\ *Optional*\ ) **Update third-party packages.** As of this writing, these include (in no particular order): * Our official `Anaconda package`_, automatically produced for all supported platforms from the `conda recipe`_ hosted at the `conda-forge feedstock`_ maintained by the maintainer of beartype. Updating this package thus reduces to updating this recipe. To do so, avoid directly pushing to any branch (including ``master``) of the `feedstock repository`_, as doing so conflicts with `conda-forge`_ automation; instead (in order): #. Remotely create a `GitHub`_ account. #. Remotely login to this account. #. Remotely fork our `feedstock repository`_. #. Locally clone this forked feedstock repository. #. Locally create a new branch of this repository specific to this update. .. code-block:: shell-session $ git checkout -b beartype-${version} #. Locally update this recipe from this branch (typically, by editing the ``recipe/meta.yaml`` file). When doing so, note that: * The sha256 hash of the updated tarball *must* be manually embedded in this recipe. To obtain this hash remotely (in order): * Browse to `the PyPI-hosted page `__ for this project. * Click the *Download Files* link. * Click the *SHA256* link to the right of the updated tarball. * Paste the resulting string as the value of the ``sha256`` Jinja2 templated variable in this recipe. #. Locally stage and commit these changes. .. code-block:: shell-session $ git commit --all #. Locally push these changes to the upstream fork. .. code-block:: shell-session $ git push --set-upstream origin beartype-v${version} #. Remotely open a pull request (PR) from the upstream fork against the `original repository `__. See also the `conda-forge FAQ`_ entry `"Using a fork vs a branch when updating a recipe." `__ * Our official `Gentoo Linux ebuild`_, currently hosted at the `raiagent overlay`_ maintained by the maintainer of beartype. Thus begins the dawn of a new scientific epoch. .. [#tags] Do *not* create a lightweight tag, which omits critical metadata (e.g., author identity, descriptive message). *Always* create an annotated tag containing this metadata by explicitly passing the ``-a`` option to the ``git tag`` subcommand. .. [#venv] Installing this release into a venv requires installing *all* mandatory dependencies of this release into this venv from either binary wheels or source tarballs. In either case, expect installation to consume non-trivial space and time. The cheese shop was not instantiated in a day. Version Nomenclature ==================== This application should be **versioned** (i.e., assigned a new version) according to the `Semantic Versioning`_ schema. Each version *must* consist of three ``.``-delimited integers ``{major}.{minor}.{patch}``, where: * ``{major}`` is the **major version,** incremented only when either: * **Breaking backward compatibility with existing simulation configurations.** The public API of this application is its configuration file format rather than the public subset of its codebase (e.g., public submodules or classes). No codebase change can be considered to break backward compatibility unless also changing the simulation configuration file format in a manner rendering existing files in the prior format unusable. Note that doing so is unequivocally bad and hence *much* discouraged. * **Implementing headline-worthy functionality** (e.g., a GUI). Technically, this condition breaks the `Semantic Versioning`_ schema, which stipulates that *only* changes breaking backward compatibility warrant major bumps. But this is the real world. In the real world, significant improvements are rewarded with significant version changes. In either case, the minor and patch versions both reset to 0. * ``{minor}`` is the **minor version,** incremented only when implementing customary functionality in a manner preserving backward compatibility. In this case, only the patch version resets to 0. * ``{patch}`` is the **patch version,** incremented only when correcting outstanding issues in a manner preserving backward compatibility. When in doubt, bump only the minor version and reset only the patch version. .. # ------------------( LINKS ~ beartype )------------------ .. _readme: https://github.com/beartype/beartype/blob/main/README.rst .. _tarballs: https://github.com/beartype/beartype/releases .. _PyPI beartype: https://pypi.python.org/pypi/beartype .. # ------------------( LINKS ~ beartype : gentoo )------------------ .. _Gentoo Linux ebuild: https://github.com/leycec/raiagent/tree/master/dev-python/beartype .. _raiagent overlay: https://github.com/leycec/raiagent .. # ------------------( LINKS ~ beartype : conda )------------------ .. _Anaconda package: https://anaconda.org/conda-forge/beartype .. _conda recipe: https://github.com/leycec/beartype-feedstock/blob/master/recipe/meta.yaml .. _conda-forge feedstock: .. _feedstock repository: https://github.com/leycec/beartype-feedstock .. # ------------------( LINKS ~ py )------------------ .. _Semantic Versioning: http://semver.org .. _twine: https://pypi.python.org/pypi/twine .. _wheel: https://wheel.readthedocs.io .. # ------------------( LINKS ~ py : conda )------------------ .. _conda-forge: https://conda-forge.org .. _conda-forge FAQ: https://conda-forge.org/docs/conda-forge_gotchas.html .. _conda-forge update recipe: https://conda-forge.org/docs/conda-forge_gotchas.html#using-a-fork-vs-a-branch-when-updating-a-recipe .. # ------------------( LINKS ~ py : package )------------------ .. _BETSE: https://github.com/betsee/betse .. _BETSEE: https://github.com/betsee/betsee .. # ------------------( LINKS ~ py : pypi )------------------ .. _Test PyPI: https://testpypi.python.org/pypi .. _Test PyPI instructions: https://wiki.python.org/moin/TestPyPI .. _Test PyPI user: https://testpypi.python.org/pypi?%3Aaction=register_form .. _PyPI: https://pypi.python.org/pypi .. _PyPI user: https://pypi.python.org/pypi?%3Aaction=register_form .. # ------------------( LINKS ~ service )------------------ .. _GitHub: https://github.com beartype-0.18.5/doc/make.bat000066400000000000000000000014321461113517100155720ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=src set BUILDDIR=trg 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% %O% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd beartype-0.18.5/doc/src/000077500000000000000000000000001461113517100147545ustar00rootroot00000000000000beartype-0.18.5/doc/src/_links.rst000066400000000000000000001032141461113517100167660ustar00rootroot00000000000000.. # ------------------( LICENSE )------------------ .. # Copyright (c) 2014-2024 Beartype authors. .. # See "LICENSE" for further details. .. # .. # ------------------( SYNOPSIS )------------------ .. # URI repository (i.e., hidden reStructuredText (reST) document centralizing .. # common URI links in reST format, automatically exposed to all other reST .. # documents in this project via the "rst_epilog" setting in "conf.py"). .. # ------------------( IMAGES ~ badge )------------------ .. |bear-ified| image:: https://raw.githubusercontent.com/beartype/beartype-assets/main/badge/bear-ified.svg :align: top :target: https://beartype.readthedocs.io :alt: bear-ified .. # ------------------( IMAGES ~ downstream )------------------ .. # Insert links to GitHub Sponsors funding at the icon level here, please! .. # ------------------( LINKS ~ beartype : funding )------------------ .. _BETSE: https://github.com/betsee/betse .. _BETSEE: https://github.com/betsee/betsee .. _GitHub Sponsors: https://github.com/sponsors/leycec .. _beartype sponsorship: https://github.com/sponsors/leycec .. _Paul Allen: https://en.wikipedia.org/wiki/Paul_Allen .. _Paul Allen Discovery Center: http://www.alleninstitute.org/what-we-do/frontiers-group/discovery-centers/allen-discovery-center-tufts-university .. _Paul Allen Discovery Center award: https://www.alleninstitute.org/what-we-do/frontiers-group/news-press/press-resources/press-releases/paul-g-allen-frontiers-group-announces-allen-discovery-center-tufts-university .. _Paul G. Allen Frontiers Group: https://www.alleninstitute.org/what-we-do/frontiers-group .. _Tufts University: https://www.tufts.edu .. _beadspace9: https://beadspace9.ca .. # ------------------( LINKS ~ beartype : github )------------------ .. _beartype: https://github.com/beartype/beartype .. _beartype 1.0.0: https://github.com/beartype/beartype/issues/7 .. _beartype codebase: https://github.com/beartype/beartype/tree/main/beartype .. _beartype issues: https://github.com/beartype/beartype/issues .. _beartype mascot: https://github.com/beartype/beartype-assets/tree/main/banner .. _beartype mascot artist: https://github.com/felix-hilden .. _beartype organization: https://github.com/beartype .. _beartype pulls: https://github.com/beartype/beartype/pulls .. _beartype tests: https://github.com/beartype/beartype/actions?workflow=tests .. # ------------------( LINKS ~ beartype : github : related )------------------ .. _pytest-beartype: https://github.com/beartype/pytest-beartype .. # ------------------( LINKS ~ beartype : github : time )------------------ .. _beartype profiler: https://github.com/beartype/beartype/blob/main/bin/profile.bash .. _beartype profiler Cisco: https://github.com/beartype/beartype/issues/58#issuecomment-940100279 .. # ------------------( LINKS ~ beartype : github : user )------------------ .. _patrick-kidger: https://github.com/patrick-kidger .. _harens: https://github.com/harens .. _@leycec: https://github.com/leycec .. _leycec: https://github.com/leycec .. # ------------------( LINKS ~ beartype : local )------------------ .. _beartype license: https://github.com/beartype/beartype/blob/main/LICENSE .. # ------------------( LINKS ~ beartype : local : module )------------------ .. _beartype errormain: https://github.com/beartype/beartype/blob/main/beartype/_decor/_code/_pep/_error/errormain.py .. _beartype pephint: https://github.com/beartype/beartype/blob/main/beartype/_decor/_code/_pep/_pephint.py .. _beartype test data pep: https://github.com/beartype/beartype/blob/main/beartype_test/unit/data/hint/pep/proposal/ .. _beartype test data pep 484: https://github.com/beartype/beartype/blob/main/beartype_test/unit/data/hint/pep/proposal/data_hintpep484.py .. _@callable_cached: https://github.com/beartype/beartype/blob/main/beartype/_util/cache/utilcachecall.py .. _beartype util data pep: https://github.com/beartype/beartype/blob/main/beartype/_util/hint/data/pep/proposal/ .. _beartype util data pep parent: https://github.com/beartype/beartype/blob/main/beartype/_util/hint/data/pep/utilhintdatapep.py .. _beartype util pep: https://github.com/beartype/beartype/blob/main/beartype/_util/hint/pep/proposal .. # ------------------( LINKS ~ beartype : package )------------------ .. _beartype Anaconda: https://anaconda.org/conda-forge/beartype .. _beartype Arch: https://aur.archlinux.org/packages/python-beartype .. _beartype Gentoo: https://packages.gentoo.org/packages/dev-python/beartype .. _beartype Homebrew: https://github.com/beartype/homebrew-beartype .. _beartype MacPorts: https://ports.macports.org/port/py-beartype .. _beartype PyPI: https://pypi.org/project/beartype .. _pytest-beartype PyPI: https://pypi.org/project/pytest-beartype .. # ------------------( LINKS ~ beartype : package : meta )------------------ .. _Libraries.io: https://libraries.io .. _beartype dependents: https://libraries.io/pypi/beartype/dependents .. # ------------------( LINKS ~ github )------------------ .. _GitHub Actions: https://github.com/features/actions .. _GitHub account signin: https://github.com/login .. _GitHub account signup: https://github.com/join .. _gitter: https://gitter.im .. # ------------------( LINKS ~ idea )------------------ .. _Denial-of-Service: https://en.wikipedia.org/wiki/Denial-of-service_attack .. _DRY: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself .. _IDE: https://en.wikipedia.org/wiki/Integrated_development_environment .. _JIT: https://en.wikipedia.org/wiki/Just-in-time_compilation .. _SQA: https://en.wikipedia.org/wiki/Software_quality_assurance .. _amortized analysis: https://en.wikipedia.org/wiki/Amortized_analysis .. _computer vision: https://en.wikipedia.org/wiki/Computer_vision .. _continuous integration: https://en.wikipedia.org/wiki/Continuous_integration .. _duck typing: https://en.wikipedia.org/wiki/Duck_typing .. _dynamic typing: https://en.wikipedia.org/wiki/Type_system .. _dynamically-typed: https://en.wikipedia.org/wiki/Type_system .. _endorheic basins: https://en.wikipedia.org/wiki/Endorheic_basin .. _full-stack: https://en.wikipedia.org/wiki/Solution_stack .. _generative pre-trained transformer: https://en.wikipedia.org/wiki/Generative_pre-trained_transformer .. _gratis versus libre: https://en.wikipedia.org/wiki/Gratis_versus_libre .. _large language model: https://en.wikipedia.org/wiki/Large_language_model .. _machine learning: https://en.wikipedia.org/wiki/Machine_learning .. _memory safety: https://en.wikipedia.org/wiki/Memory_safety .. _multiple dispatch: https://en.wikipedia.org/wiki/Multiple_dispatch .. _near-real-time: https://en.wikipedia.org/wiki/Real-time_computing#Near_real-time .. _random walk: https://en.wikipedia.org/wiki/Random_walk .. _real-time: https://en.wikipedia.org/wiki/Real-time_computing .. _set theory: https://en.wikipedia.org/wiki/Set_theory .. _shield wall: https://en.wikipedia.org/wiki/Shield_wall .. _static typing: https://en.wikipedia.org/wiki/Type_system .. _statically-typed: https://en.wikipedia.org/wiki/Type_system .. _topological sort: https://en.wikipedia.org/wiki/Topological_sorting .. _type inference: https://en.wikipedia.org/wiki/Type_inference .. _zero-cost abstraction: https://boats.gitlab.io/blog/post/zero-cost-abstractions .. # ------------------( LINKS ~ kipling )------------------ .. _The Jungle Book: https://www.gutenberg.org/files/236/236-h/236-h.htm .. _Shere Khan: https://en.wikipedia.org/wiki/Shere_Khan .. # ------------------( LINKS ~ math )------------------ .. _Euler–Mascheroni constant: https://en.wikipedia.org/wiki/Euler%E2%80%93Mascheroni_constant .. _coupon collector's problem: https://en.wikipedia.org/wiki/Coupon_collector%27s_problem .. _Big O: https://en.wikipedia.org/wiki/Big_O_notation .. # ------------------( LINKS ~ math : set )------------------ .. _conjunction: https://en.wikipedia.org/wiki/Logical_conjunction .. _disjunction: https://en.wikipedia.org/wiki/Logical_disjunction .. _intersection: https://en.wikipedia.org/wiki/Intersection_(set_theory) .. _relative set complement: https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement .. # ------------------( LINKS ~ math : type )------------------ .. _covariance: https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science) .. # ------------------( LINKS ~ meme )------------------ .. _RNGesus: https://knowyourmeme.com/memes/rngesus .. _Up: https://www.youtube.com/watch?v=F2bk_9T482g .. _goes up to eleven: https://www.youtube.com/watch?v=uMSV4OteqBE .. _greased lightning: https://www.youtube.com/watch?v=H-kL8A4RNQ8 .. _led zeppelin: https://rateyourmusic.com/release/album/led-zeppelin/led-zeppelin .. _ludicrous speed: https://www.youtube.com/watch?v=6tTvklMXeFE .. _the gripping hand: http://catb.org/jargon/html/O/on-the-gripping-hand.html .. # ------------------( LINKS ~ os : linux )------------------ .. _Gentoo Linux: https://www.gentoo.org .. # ------------------( LINKS ~ os : linux : arch )------------------ .. _Arch Linux: https://archlinux.org .. _AUR: https://aur.archlinux.org/packages/python-beartype .. # ------------------( LINKS ~ os : macos )------------------ .. _macOS: https://en.wikipedia.org/wiki/MacOS .. _HomeBrew: https://brew.sh .. _MacPorts: https://www.macports.org .. # ------------------( LINKS ~ other )------------------ .. _heliotrope: https://en.wikipedia.org/wiki/Heliotropium .. # ------------------( LINKS ~ py )------------------ .. _Python: https://www.python.org .. _Python status: https://devguide.python.org/#status-of-python-branches .. _pip: https://pip.pypa.io .. # ------------------( LINKS ~ py : cli )------------------ .. _-O: https://docs.python.org/3/using/cmdline.html#cmdoption-o .. _PYTHONOPTIMIZE: https://docs.python.org/3/using/cmdline.html#envvar-PYTHONOPTIMIZE .. # ------------------( LINKS ~ py : interpreter )------------------ .. _Brython: https://brython.info .. _CPython: https://github.com/python/cpython .. _Cython: https://cython.org .. _Nuitka: https://nuitka.net .. _Numba: https://numba.pydata.org .. _PyPy: https://www.pypy.org .. _Pyodide: https://pyodide.org .. # ------------------( LINKS ~ py : interpreter : cpython )------------------ .. _CPython bug tracker: https://github.com/python/cpython/issues .. # ------------------( LINKS ~ py : lang )------------------ .. _generic alias parameters: https://docs.python.org/3/library/stdtypes.html#genericalias.__parameters__ .. _isinstancecheck: https://docs.python.org/3/reference/datamodel.html#customizing-instance-and-subclass-checks .. _mro: https://docs.python.org/3/library/stdtypes.html#class.__mro__ .. _object: https://docs.python.org/3/reference/datamodel.html#basic-customization .. _operator precedence: https://docs.python.org/3/reference/expressions.html#operator-precedence .. # ------------------( LINKS ~ py : misc )------------------ .. _Guido van Rossum: https://en.wikipedia.org/wiki/Guido_van_Rossum .. _RealPython: https://realpython.com/python-type-checking .. # ------------------( LINKS ~ py : package )------------------ .. _Django: https://www.djangoproject.com .. _Hypothesis: https://hypothesis.readthedocs.io .. _JAX: https://jax.readthedocs.io .. _NetworkX: https://networkx.org .. _PyTorch: https://pytorch.org .. _SymPy: https://www.sympy.org .. _TensorFlow: https://www.tensorflow.org .. _equinox: https://github.com/patrick-kidger/equinox .. _nptyping: https://github.com/ramonhagenaars/nptyping .. _numerary: https://github.com/posita/numerary .. _pyenv: https://operatingops.org/2020/10/24/tox-testing-multiple-python-versions-with-pyenv .. _typing_extensions: https://pypi.org/project/typing-extensions .. # ------------------( LINKS ~ py : package : boto3 )------------------ .. _Boto3: https://aws.amazon.com/sdk-for-python .. _bearboto3: https://github.com/beartype/bearboto3 .. _mypy-boto3: https://mypy-boto3.readthedocs.io .. # ------------------( LINKS ~ py : package : jax )------------------ .. _jax.numpy: https://jax.readthedocs.io/en/latest/notebooks/thinking_in_jax.html .. # ------------------( LINKS ~ py : package : numpy )------------------ .. _NumPy: https://numpy.org .. _numpy.dtype: https://numpy.org/doc/stable/reference/arrays.dtypes.html .. _numpy.dtype.type: https://numpy.org/doc/stable/reference/arrays.dtypes.html .. _numpy.empty_like: https://numpy.org/doc/stable/reference/generated/numpy.empty_like.html .. _numpy.floating: https://numpy.org/doc/stable/reference/arrays.scalars.html?highlight=numpy%20generic#numpy.floating .. _numpy.generic: https://numpy.org/doc/stable/reference/arrays.scalars.html?highlight=numpy%20generic#numpy.generic .. _numpy.integer: https://numpy.org/doc/stable/reference/arrays.scalars.html?highlight=numpy%20generic#numpy.integer .. _numpy.typing: https://numpy.org/devdocs/reference/typing.html .. _numpy.typing.NDArray: https://numpy.org/devdocs/reference/typing.html#ndarray .. # ------------------( LINKS ~ py : package : pandas )------------------ .. # Note that "pandas" is officially lowercase. .. _pandas: https://pandas.pydata.org .. _pandas.DataFrame: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html .. # ------------------( LINKS ~ py : package : panderas )------------------ .. # Note that "pandera" is officially lowercase. .. _pandera: https://pandera.readthedocs.io .. _pandera.check_types: https://pandera.readthedocs.io/en/stable/reference/generated/pandera.decorators.check_types.html .. _pandera.typing: https://pandera.readthedocs.io/en/stable/reference/generated/pandera.typing.html .. _pandera.typing.DataFrame: https://pandera.readthedocs.io/en/stable/dataframe_models.html .. # ------------------( LINKS ~ py : package : sphinx )------------------ .. _Sphinx: https://www.sphinx-doc.org .. _sphinx.ext.autodoc: https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html .. # ------------------( LINKS ~ py : package : test )------------------ .. _Codecov: https://about.codecov.io .. _pytest: https://docs.pytest.org .. _tox: https://tox.readthedocs.io .. # ------------------( LINKS ~ py : pep )------------------ .. _PEPs: https://peps.python.org .. _PEP 20: https://peps.python.org/pep-0020 .. _PEP 362: https://peps.python.org/pep-0362 .. _PEP 483: https://peps.python.org/pep-0483 .. _PEP 526: https://peps.python.org/pep-0526 .. _PEP 544: https://peps.python.org/pep-0544 .. _PEP 557: https://peps.python.org/pep-0557 .. _PEP 561: https://peps.python.org/pep-0561 .. _PEP 563: https://peps.python.org/pep-0563 .. _PEP 570: https://peps.python.org/pep-0570 .. _PEP 572: https://peps.python.org/pep-0572 .. _PEP 585: https://peps.python.org/pep-0585 .. _PEP 586: https://peps.python.org/pep-0586 .. _PEP 589: https://peps.python.org/pep-0589 .. _PEP 591: https://peps.python.org/pep-0591 .. _PEP 593: https://peps.python.org/pep-0593 .. _PEP 604: https://peps.python.org/pep-0604 .. _PEP 612: https://peps.python.org/pep-0612 .. _PEP 613: https://peps.python.org/pep-0613 .. _PEP 646: https://peps.python.org/pep-0646 .. _PEP 647: https://peps.python.org/pep-0647 .. _PEP 673: https://peps.python.org/pep-0673 .. _PEP 675: https://peps.python.org/pep-0675 .. _PEP 681: https://peps.python.org/pep-0681 .. _PEP 3102: https://peps.python.org/pep-3102 .. _PEP 3141: https://peps.python.org/pep-3141 .. # ------------------( LINKS ~ py : pep : 3119 )------------------ .. _PEP 3119: https://peps.python.org/pep-3119 .. _virtual base classes: https://peps.python.org/pep-3119/#id33 .. # ------------------( LINKS ~ py : pep : 484 )------------------ .. _PEP 484: https://peps.python.org/pep-0484 .. _implicit numeric tower: https://peps.python.org/pep-0484/#the-numeric-tower .. _relative forward references: https://peps.python.org/pep-0484/#forward-references .. _type aliases: https://peps.python.org/pep-0484/#type-aliases .. # ------------------( LINKS ~ py : pep : 560 )------------------ .. _PEP 560: https://peps.python.org/pep-0560 .. _mro_entries: https://peps.python.org/pep-0560/#id20 .. # ------------------( LINKS ~ py : service )------------------ .. _Anaconda: https://docs.conda.io/en/latest/miniconda.html .. # ------------------( LINKS ~ py : service : pypi )------------------ .. _PyPI: https://pypi.org .. _PyPI cheese shop: https://pypi.org .. _cheese shop sketch: https://en.wikipedia.org/wiki/Cheese_Shop_sketch .. # ------------------( LINKS ~ py : stdlib )------------------ .. _linecache: https://docs.python.org/3/library/linecache.html .. _weakref: https://docs.python.org/3/library/weakref.html .. # ------------------( LINKS ~ py : stdlib : abc )------------------ .. _abc: https://docs.python.org/3/library/abc.html .. _abc.ABCMeta: https://docs.python.org/3/library/abc.html#abc.ABCMeta .. # ------------------( LINKS ~ py : stdlib : builtins )------------------ .. _builtins: https://docs.python.org/3/library/stdtypes.html .. _None: https://docs.python.org/3/library/constants.html#None .. _NotImplemented: https://docs.python.org/3/library/constants.html#NotImplemented .. _dict: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict .. _dir: https://docs.python.org/3/library/functions.html#dir .. _frozenset: https://docs.python.org/3/library/stdtypes.html#set-types-set-frozenset .. _isinstance: https://docs.python.org/3/library/functions.html#isinstance .. _issubclass: https://docs.python.org/3/library/functions.html#issubclass .. _list: https://docs.python.org/3/library/stdtypes.html#lists .. _memoryview: https://docs.python.org/3/library/stdtypes.html#memory-views .. _range: https://docs.python.org/3/library/stdtypes.html#typesseq-range .. _set: https://docs.python.org/3/library/stdtypes.html#set-types-set-frozenset .. _tuple: https://docs.python.org/3/library/stdtypes.html#tuples .. _type: https://docs.python.org/3/library/stdtypes.html#bltin-type-objects .. # ------------------( LINKS ~ py : stdlib : collections }------------------ .. _collections: https://docs.python.org/3/library/collections.html .. _collections.ChainMap: https://docs.python.org/3/library/collections.html#collections.ChainMap .. _collections.Counter: https://docs.python.org/3/library/collections.html#collections.Counter .. _collections.OrderedDict: https://docs.python.org/3/library/collections.html#collections.OrderedDict .. _collections.defaultdict: https://docs.python.org/3/library/collections.html#collections.defaultdict .. _collections.deque: https://docs.python.org/3/library/collections.html#collections.deque .. # ------------------( LINKS ~ py : stdlib : collections.abc }--------------- .. _collections.abc: https://docs.python.org/3/library/collections.abc.html .. _collections.abc.AsyncGenerator: https://docs.python.org/3/library/collections.abc.html#collections.abc.AsyncGenerator .. _collections.abc.AsyncIterable: https://docs.python.org/3/library/collections.abc.html#collections.abc.AsyncIterable .. _collections.abc.AsyncIterator: https://docs.python.org/3/library/collections.abc.html#collections.abc.AsyncIterator .. _collections.abc.Awaitable: https://docs.python.org/3/library/collections.abc.html#collections.abc.Awaitable .. _collections.abc.ByteString: https://docs.python.org/3/library/collections.abc.html#collections.abc.ByteString .. _collections.abc.Callable: https://docs.python.org/3/library/collections.abc.html#collections.abc.Callable .. _collections.abc.Collection: https://docs.python.org/3/library/collections.abc.html#collections.abc.Collection .. _collections.abc.Container: https://docs.python.org/3/library/collections.abc.html#collections.abc.Container .. _collections.abc.Coroutine: https://docs.python.org/3/library/collections.abc.html#collections.abc.Coroutine .. _collections.abc.Generator: https://docs.python.org/3/library/collections.abc.html#collections.abc.Generator .. _collections.abc.ItemsView: https://docs.python.org/3/library/collections.abc.html#collections.abc.ItemsView .. _collections.abc.Iterable: https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterable .. _collections.abc.Iterator: https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterator .. _collections.abc.KeysView: https://docs.python.org/3/library/collections.abc.html#collections.abc.KeysView .. _collections.abc.Mapping: https://docs.python.org/3/library/collections.abc.html#collections.abc.Mapping .. _collections.abc.MappingView: https://docs.python.org/3/library/collections.abc.html#collections.abc.MappingView .. _collections.abc.MutableMapping: https://docs.python.org/3/library/collections.abc.html#collections.abc.MutableMapping .. _collections.abc.MutableSequence: https://docs.python.org/3/library/collections.abc.html#collections.abc.MutableSequence .. _collections.abc.MutableSet: https://docs.python.org/3/library/collections.abc.html#collections.abc.MutableSet .. _collections.abc.Reversible: https://docs.python.org/3/library/collections.abc.html#collections.abc.Reversible .. _collections.abc.Sequence: https://docs.python.org/3/library/collections.abc.html#collections.abc.Sequence .. _collections.abc.Set: https://docs.python.org/3/library/collections.abc.html#collections.abc.Set .. _collections.abc.ValuesView: https://docs.python.org/3/library/collections.abc.html#collections.abc.ValuesView .. # ------------------( LINKS ~ py : stdlib : contextlib )------------------ .. _contextlib: https://docs.python.org/3/library/contextlib.html .. _contextlib.AbstractAsyncContextManager: https://docs.python.org/3/library/contextlib.html#contextlib.AbstractAsyncContextManager .. _contextlib.AbstractContextManager: https://docs.python.org/3/library/contextlib.html#contextlib.AbstractContextManager .. # ------------------( LINKS ~ py : stdlib : abc )------------------ .. _dataclasses: https://docs.python.org/3/library/dataclasses.html .. _dataclasses.InitVar: https://docs.python.org/3/library/dataclasses.html#init-only-variables .. _dataclasses.dataclass: https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass .. # ------------------( LINKS ~ py : stdlib : enum )------------------ .. _enum.Enum: https://docs.python.org/3/library/enum.html#enum.Enum .. # ------------------( LINKS ~ py : stdlib : io )------------------ .. _io: https://docs.python.org/3/library/io.html .. # ------------------( LINKS ~ py : stdlib : os )------------------ .. _os: https://docs.python.org/3/library/os.html .. _os.walk: https://docs.python.org/3/library/os.html#os.walk .. # ------------------( LINKS ~ py : stdlib : random )------------------ .. _random: https://docs.python.org/3/library/random.html .. _random.getrandbits: https://docs.python.org/3/library/random.html#random.getrandbits .. _random twister: https://stackoverflow.com/a/11704178/2809027 .. # ------------------( LINKS ~ py : stdlib : re )------------------ .. _re: https://docs.python.org/3/library/re.html .. _re.Match: https://docs.python.org/3/library/re.html#match-objects .. _re.Pattern: https://docs.python.org/3/library/re.html#regular-expression-objects .. # ------------------( LINKS ~ py : stdlib : typing : attr )------------------ .. _typing: https://docs.python.org/3/library/typing.html .. _typing.AbstractSet: https://docs.python.org/3/library/typing.html#typing.AbstractSet .. _typing.Annotated: https://docs.python.org/3/library/typing.html#typing.Annotated .. _typing.Any: https://docs.python.org/3/library/typing.html#typing.Any .. _typing.AnyStr: https://docs.python.org/3/library/typing.html#typing.AnyStr .. _typing.AsyncContextManager: https://docs.python.org/3/library/typing.html#typing.AsyncContextManager .. _typing.AsyncGenerator: https://docs.python.org/3/library/typing.html#typing.AsyncGenerator .. _typing.AsyncIterable: https://docs.python.org/3/library/typing.html#typing.AsyncIterable .. _typing.AsyncIterator: https://docs.python.org/3/library/typing.html#typing.AsyncIterator .. _typing.Awaitable: https://docs.python.org/3/library/typing.html#typing.Awaitable .. _typing.BinaryIO: https://docs.python.org/3/library/typing.html#typing.BinaryIO .. _typing.ByteString: https://docs.python.org/3/library/typing.html#typing.ByteString .. _typing.Callable: https://docs.python.org/3/library/typing.html#typing.Callable .. _typing.ChainMap: https://docs.python.org/3/library/typing.html#typing.ChainMap .. _typing.ClassVar: https://docs.python.org/3/library/typing.html#typing.ClassVar .. _typing.Collection: https://docs.python.org/3/library/typing.html#typing.Collection .. _typing.Concatenate: https://docs.python.org/3/library/typing.html#typing.Concatenate .. _typing.Container: https://docs.python.org/3/library/typing.html#typing.Container .. _typing.ContextManager: https://docs.python.org/3/library/typing.html#typing.ContextManager .. _typing.Coroutine: https://docs.python.org/3/library/typing.html#typing.Coroutine .. _typing.Counter: https://docs.python.org/3/library/typing.html#typing.Counter .. _typing.DefaultDict: https://docs.python.org/3/library/typing.html#typing.DefaultDict .. _typing.Deque: https://docs.python.org/3/library/typing.html#typing.Deque .. _typing.Dict: https://docs.python.org/3/library/typing.html#typing.Dict .. _typing.Final: https://docs.python.org/3/library/typing.html#typing.Final .. _typing.ForwardRef: https://docs.python.org/3/library/typing.html#typing.ForwardRef .. _typing.FrozenSet: https://docs.python.org/3/library/typing.html#typing.FrozenSet .. _typing.Generator: https://docs.python.org/3/library/typing.html#typing.Generator .. _typing.Generic: https://docs.python.org/3/library/typing.html#typing.Generic .. _typing.Hashable: https://docs.python.org/3/library/typing.html#typing.Hashable .. _typing.IO: https://docs.python.org/3/library/typing.html#typing.IO .. _typing.ItemsView: https://docs.python.org/3/library/typing.html#typing.ItemsView .. _typing.Iterable: https://docs.python.org/3/library/typing.html#typing.Iterable .. _typing.Iterator: https://docs.python.org/3/library/typing.html#typing.Iterator .. _typing.KeysView: https://docs.python.org/3/library/typing.html#typing.KeysView .. _typing.List: https://docs.python.org/3/library/typing.html#typing.List .. _typing.Literal: https://docs.python.org/3/library/typing.html#typing.Literal .. _typing.Mapping: https://docs.python.org/3/library/typing.html#typing.Mapping .. _typing.MappingView: https://docs.python.org/3/library/typing.html#typing.MappinViewg .. _typing.Match: https://docs.python.org/3/library/typing.html#typing.Match .. _typing.MutableMapping: https://docs.python.org/3/library/typing.html#typing.MutableMapping .. _typing.MutableSequence: https://docs.python.org/3/library/typing.html#typing.MutableSequence .. _typing.MutableSet: https://docs.python.org/3/library/typing.html#typing.MutableSet .. _typing.NamedTuple: https://docs.python.org/3/library/typing.html#typing.NamedTuple .. _typing.NewType: https://docs.python.org/3/library/typing.html#typing.NewType .. _typing.NoReturn: https://docs.python.org/3/library/typing.html#typing.NoReturn .. _typing.Optional: https://docs.python.org/3/library/typing.html#typing.Optional .. _typing.OrderedDict: https://docs.python.org/3/library/typing.html#typing.OrderedDict .. _typing.ParamSpec: https://docs.python.org/3/library/typing.html#typing.ParamSpec .. _typing.ParamSpecArgs: https://docs.python.org/3/library/typing.html#typing.ParamSpecArgs .. _typing.ParamSpecKwargs: https://docs.python.org/3/library/typing.html#typing.ParamSpecKwargs .. _typing.Pattern: https://docs.python.org/3/library/typing.html#typing.Pattern .. _typing.Protocol: https://docs.python.org/3/library/typing.html#typing.Protocol .. _typing.Reversible: https://docs.python.org/3/library/typing.html#typing.Reversible .. _typing.Self: https://docs.python.org/3/library/typing.html#typing.Self .. _typing.Sequence: https://docs.python.org/3/library/typing.html#typing.Sequence .. _typing.Set: https://docs.python.org/3/library/typing.html#typing.Set .. _typing.Sized: https://docs.python.org/3/library/typing.html#typing.Sized .. _typing.SupportsAbs: https://docs.python.org/3/library/typing.html#typing.SupportsAbs .. _typing.SupportsBytes: https://docs.python.org/3/library/typing.html#typing.SupportsBytes .. _typing.SupportsComplex: https://docs.python.org/3/library/typing.html#typing.SupportsComplex .. _typing.SupportsFloat: https://docs.python.org/3/library/typing.html#typing.SupportsFloat .. _typing.SupportsIndex: https://docs.python.org/3/library/typing.html#typing.SupportsIndex .. _typing.SupportsInt: https://docs.python.org/3/library/typing.html#typing.SupportsInt .. _typing.SupportsRound: https://docs.python.org/3/library/typing.html#typing.SupportsRound .. _typing.Text: https://docs.python.org/3/library/typing.html#typing.Text .. _typing.TextIO: https://docs.python.org/3/library/typing.html#typing.TextIO .. _typing.Tuple: https://docs.python.org/3/library/typing.html#typing.Tuple .. _typing.Type: https://docs.python.org/3/library/typing.html#typing.Type .. _typing.TypeGuard: https://docs.python.org/3/library/typing.html#typing.TypeGuard .. _typing.TypedDict: https://docs.python.org/3/library/typing.html#typing.TypedDict .. _typing.TypeVar: https://docs.python.org/3/library/typing.html#typing.TypeVar .. _typing.Union: https://docs.python.org/3/library/typing.html#typing.Union .. _typing.ValuesView: https://docs.python.org/3/library/typing.html#typing.ValuesView .. _@typing.final: https://docs.python.org/3/library/typing.html#typing.final .. _@typing.no_type_check: https://docs.python.org/3/library/typing.html#typing.no_type_check .. _typing.TYPE_CHECKING: https://docs.python.org/3/library/typing.html#typing.TYPE_CHECKING .. # ------------------( LINKS ~ py : type : runtime )------------------ .. _enforce: https://github.com/RussBaz/enforce .. _enforce_typing: https://github.com/matchawine/python-enforce-typing .. _pydantic: https://pydantic-docs.helpmanual.io .. _pytypes: https://github.com/Stewori/pytypes .. _typeen: https://github.com/k2bd/typen .. _typical: https://github.com/seandstewart/typical .. # ------------------( LINKS ~ py : type : runtime : typeg )------------------ .. _typeguard: https://github.com/agronholm/typeguard .. _typeguard.check_type: https://typeguard.readthedocs.io/en/latest/userguide.html#checking-types-directly .. # ------------------( LINKS ~ py : type : runtime : data )------------------ .. _PyContracts: https://github.com/AlexandruBurlacu/pycontracts .. _contracts: https://pypi.org/project/contracts .. _covenant: https://github.com/kisielk/covenant .. _dpcontracts: https://pypi.org/project/dpcontracts .. _icontract: https://github.com/Parquery/icontract .. _pyadbc: https://pypi.org/project/pyadbc .. _pcd: https://pypi.org/project/pcd .. # ------------------( LINKS ~ py : type : static )------------------ .. _Pyre: https://pyre-check.org .. _pytype: https://github.com/google/pytype .. # ------------------( LINKS ~ py : type : static : pyright)------------------ .. _pyright: https://github.com/Microsoft/pyright .. _pyright plugins: https://github.com/microsoft/pyright/issues/607#issuecomment-873467941 .. _pyright PEP violation #1: https://github.com/beartype/beartype/issues/126 .. _pyright PEP violation #2: https://github.com/beartype/beartype/issues/127 .. # ------------------( LINKS ~ py : type : static : mypy )------------------ .. _mypy: http://mypy-lang.org .. _mypy install: https://mypy.readthedocs.io/en/stable/getting_started.html .. _mypy plugin: https://mypy.readthedocs.io/en/stable/extending_mypy.html .. _type narrowing: https://mypy.readthedocs.io/en/stable/type_narrowing.html .. # ------------------( LINKS ~ py : type : tensor )------------------ .. _jaxtyping: https://github.com/google/jaxtyping .. _nptyping: https://github.com/ramonhagenaars/nptyping .. # ------------------( LINKS ~ soft : ide )------------------ .. _PyCharm: https://en.wikipedia.org/wiki/PyCharm .. _Vim: https://www.vim.org .. # ------------------( LINKS ~ soft : ide : vscode )------------------ .. _Pylance: https://github.com/microsoft/pylance-release .. _VSCode: https://code.visualstudio.com .. _VSCode Mypy extension: https://marketplace.visualstudio.com/items?itemName=matangover.mypy .. # ------------------( LINKS ~ soft : lang )------------------ .. _C: https://en.wikipedia.org/wiki/C_(programming_language) .. _C++: https://en.wikipedia.org/wiki/C%2B%2B .. _Ruby: https://www.ruby-lang.org .. _Rust: https://www.rust-lang.org .. # ------------------( LINKS ~ soft : license )------------------ .. _MIT license: https://opensource.org/licenses/MIT .. # ------------------( LINKS ~ soft : web )------------------ .. _React: https://reactjs.org beartype-0.18.5/doc/src/_templates/000077500000000000000000000000001461113517100171115ustar00rootroot00000000000000beartype-0.18.5/doc/src/_templates/sidebar-nav-bs.html000066400000000000000000000057201461113517100226000ustar00rootroot00000000000000 beartype-0.18.5/doc/src/api.rst000066400000000000000000000106771461113517100162720ustar00rootroot00000000000000.. # ------------------( LICENSE )------------------ .. # Copyright (c) 2014-2024 Beartype authors. .. # See "LICENSE" for further details. .. # .. # ------------------( SYNOPSIS )------------------ .. # Child reStructuredText (reST) document detailing all public-facing APIs .. # exposed by this project. .. # ------------------( TODO )------------------ .. # FIXME: Document the new "beartype.peps" subpackage as well, please! .. # ------------------( MAIN )------------------ .. _api:api: ################################## Beartype API: It Bears Bookmarking ################################## Beartype isn't just the :func:`beartype.beartype` decorator. Beartype is a menagerie of public APIs for type-checking, introspecting, and manipulating type hints at runtime – all accessible under the ``beartype`` package installed when you installed beartype. But all beartype documentation begins with :func:`beartype.beartype`, just like all rivers run to the sea. [#endorheic_basins]_ .. [#endorheic_basins] That's a lie, actually. Numerous river tributaries just pour out into deserts. Do `endorheic basins`_ mean nothing to you, beartype? Wikipedia: *the more you click, the less you know.* .. # ------------------( TABLES OF CONTENTS )------------------ .. # Project-wide tables of contents (TOCs). See also official documentation on .. # the Sphinx-specific "toctree::" directive: .. # https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-toctree .. # Child TOC tree. .. # .. # Note that child documents *MUST* reside in the same subdirectory as this .. # parent document. Although Sphinx locally supports child documents residing .. # in a different subdirectory (e.g., "doc/srcapi/"), Sphinx remotely fails .. # when this is the case under ReadTheDocs (RTD) with fatal warnings ala: .. # WARNING: toctree contains reference to nonexisting document "api/decor" .. # .. # See also this StackOverflow post, where the only valid solution is to .. # flatten the Sphinx document structure as we have necessarily done: .. # https://stackoverflow.com/a/51283544/2809027 .. toctree:: :hidden: :caption: Bear with Us Beartype Import Hooks Beartype Decorator Beartype Validators Beartype Introspectors Beartype Errors .. #FIXME: Uncomment *AFTER* re-enabling "autoapi" support in "conf.py" and .. #resolving outstanding issues with that support. *gulp* .. # .. toctree:: .. # :caption: Beartype API reference .. # .. # API .. # .. # Would You Like to Know More? .. # ---------------------------- .. # .. # * :ref:`genindex` .. # * :ref:`modindex` .. # * :ref:`search` .. # Table of contents, excluding the above document heading. While the .. # official reStructuredText documentation suggests that a language-specific .. # heading will automatically prepend this table, this does *NOT* appear to .. # be the case. Instead, this heading must be explicitly declared. .. contents:: **Bear with Us** :local: .. # ------------------( DESCRIPTION )------------------ ***************** The Left-Paw Path ***************** See the left sidebar for links to human-readable API documentation – including: * :mod:`beartype`, documenting the core :func:`.beartype` decorator API. * :mod:`beartype.claw`, documenting the beartype import hook API. * :mod:`beartype.door`, documenting the Decidedly Object-Oriented Runtime-checker (DOOR) API. * :mod:`beartype.roar`, documenting the beartype exception and warning API. * :mod:`beartype.vale`, documenting the beartype validator API. Or see these autogenerated indices for machine-readable laundry lists. For those about to put on the 90's-era Geocities nostalgia goggles, you prefer inscrutable enumerations in lexicographic (i.e., effectively arbitrary) order of *all* public beartype: * :ref:`Attributes `. This is literally everything. By everything, we mean modules, classes, functions, and globals. If it's not here, it doesn't exist. If it actually exists, it's private and you shouldn't have gone there. But curiosity killed your codebase, didn't it? You went there. You violated privacy encapsulation and now nothing works. So this is what it's like when doves cry. * :ref:`Modules `. Look. It's just modules. Never click this. beartype-0.18.5/doc/src/api_claw.rst000066400000000000000000000715571461113517100173040ustar00rootroot00000000000000.. # ------------------( LICENSE )------------------ .. # Copyright (c) 2014-2024 Beartype authors. .. # See "LICENSE" for further details. .. # .. # ------------------( SYNOPSIS )------------------ .. # Child reStructuredText (reST) document detailing the public-facing API of .. # the "beartype.claw" subpackage, governing import hooks. .. # ------------------( METADATA )------------------ .. # Fully-qualified name of the (sub)package described by this document, .. # enabling this document to be externally referenced as :mod:`{name}`. .. py:module:: beartype.claw .. # ------------------( MAIN )------------------ .. _api_claw:api_claw: ********************* Beartype Import Hooks ********************* **Beartype import hooks** enforce type hints across your entire app in two lines of code with *no* runtime overhead. This is beartype import hooks in ten seconds. :superscript:`dyslexia notwithstanding` .. code-block:: python # Add *ONE* of the following semantically equivalent two-liners to the very # top of your "{your_package}.__init__" submodule. Start with *THE FAST WAY*. # ....................{ THE FAST WAY }.................... from beartype.claw import beartype_this_package # <-- this is boring, but... beartype_this_package() # <-- the fast way # ....................{ THE LESS FAST WAY }.................... from beartype.claw import beartype_package # <-- still boring, but... beartype_package('{your_package}') # <-- the less fast way # ....................{ THE MORE SLOW WAY }.................... from beartype.claw import beartype_packages # <-- boring intensifies beartype_packages(('{your_package}',)) # <-- the more slow way .. #FIXME: Uncomment *AFTER* we actually build out a reasonable first iteration .. #of our local import hook API. *sigh* .. # # ....................{ THE WAY OF THE BEAR NINJA }.................... .. # from beartype.claw import beartyping # <-- getting weird here .. # with beartyping(): # <-- weird context manager .. # from {your_package} import {your_thing} # <-- import some stuff .. # from {some_package} import {some_thing} # <-- import more stuff Beartype import hooks extend the surprisingly sharp claws of :mod:`beartype` to your full app stack, whether anyone else wanted you to do that or not. Claw your way to the top of the bug heap; then sit on that heap with a smug expression. Do it for the new guy sobbing quietly in his cubicle. .. # ------------------( TABLES OF CONTENTS )------------------ .. # Table of contents, excluding the above document heading. While the .. # official reStructuredText documentation suggests that a language-specific .. # heading will automatically prepend this table, this does *NOT* appear to .. # be the case. Instead, this heading must be explicitly declared. .. contents:: **Bear with Us** :local: .. # ------------------( DESCRIPTION )------------------ Import Hooks Overview ##################### Beartype import hooks implicitly perform both: * Standard **runtime type-checking** (ala the :func:`beartype.beartype` decorator). * Standard **static type-checking** (ala mypy_ and pyright_) but **at runtime** – and that ain't standard. Automate the :func:`beartype.beartype` decorator away today with magical import hooks published by the :mod:`beartype.claw` subpackage. When you install import hooks from beartype, you augment beartype from a :ref:`pure-runtime second-generation type-checker ` into a :ref:`hybrid runtime-static third-generation type-checker `. That's right. Beartype is now a tentacular cyberpunk horror like that mutant brain baby from Katsuhiro Otomo's dystopian 80's masterpiece *Akira*. You can't look away! .. image:: https://user-images.githubusercontent.com/217028/272775190-8996c4a2-b320-4ca1-ba83-5c4dd36e6165.png :width: 300 :alt: mutant brain baby :superscript:`May Neo-Tokyo have mercy on your codebase's soul.` Import Hooks Overview, Part Deux ################################ Beartype import hooks is a hobbit hole so deep we had to deescalate it with decrepit manga panels from *Akira*. Prepare to enter that hole. What Is beartype_this_package()? ******************************** Let's begin by outlining exactly **what** :func:`.beartype_this_package` does. As the simplest and most convenient of several import hooks published by the :mod:`beartype.claw` subpackage, :func:`.beartype_this_package` type-checks *all* subsequently imported submodules of ``{your_package}``. Notably, :func:`.beartype_this_package`: * Implicitly decorates *all* callables and classes across ``{your_package}`` by the :func:`beartype.beartype` decorator. Rejoice, fellow mammals! You no longer need to explicitly decorate anything by :func:`beartype.beartype` ever again. Of course, you *can* if you want to – but there's no compelling reason to do so and many compelling reasons *not* to do so. You have probably just thought of five, but there are even more. * Implicitly appends *every* :pep:`526`\ -compliant annotated variable assignment (e.g., ``muh_int: int = 'Pretty sure this isn't an integer, but not sure.'``) across ``{your_package}`` by a new statement at the same indentation level calling the :func:`beartype.door.die_if_unbearable` function passed both that variable and that type hint. Never do that manually. Now, you never do. Examples or we're lying again. :func:`.beartype_this_package` transforms your ``{your_package}.{buggy_submodule}`` from this quietly broken code that you insist you never knew about, you swear: .. code-block:: python # This is "{your_package}.{buggy_submodule}". It is bad, but you never knew. import typing as t bad_global: int = 'My eyes! The goggles do nothing.' # <-- no exception def bad_function() -> str: return b"I could've been somebody, instead of a bum byte string." bad_function() # <-- no exception class BadClass(object): def bad_method(self) -> t.NoReturn: return 'Nobody puts BadClass in the corner.' BadClass().bad_method() # <-- no exception ...into this loudly broken code that even your unionized QA team can no longer ignore: .. code-block:: python # This is "{your_package}.{buggy_submodule}" on beartype_this_package(). # Any questions? Actually, that was rhetorical. No questions, please. from beartype import beartype from beartype.door import die_if_unbearable import typing as t bad_global: int = 'My eyes! The goggles do nothing.' die_if_unbearable(bad_global, int) # <-- raises exception @beartype def bad_function() -> str: return b"I could've been somebody, instead of a bum byte string." bad_function() # <-- raises exception @beartype class BadClass(object): def bad_method(self) -> t.NoReturn: return 'Nobody puts BadClass in the corner.' BadClass().bad_method() # <-- raises exception By doing nothing, you saved five lines of extraneous boilerplate you no longer need to maintain, preserved `DRY (Don't Repeat Yourself) `__, and mended your coworker's career, who you would have blamed for all this. You had nothing to do with that code. It's a nothingburger! Beartype believes you. This is why we :func:`.beartype_this_package`. .. image:: https://user-images.githubusercontent.com/217028/272775040-9bf81c0b-3994-4420-a1d5-ac5835f0a0b2.png :alt: looks kinda bad :superscript:`This is what happens when we don't beartype_this_package().` Why Is beartype_this_package()? ******************************* Let's continue by justifying **why** you want to use :func:`.beartype_this_package`. Don't worry. The "why?" is easier than the "what?". It often is. The answer is: "Safety is my middle name." :superscript:`<-- more lies` :func:`.beartype_this_package` isolates its bug-hunting action to the current package. This is what everyone wants to try first. Type-checking only *your* first-party package under *your* control is the safest course of action, because you rigorously stress-tested your package with beartype. You did, didn't you? You're not making us look bad here? Don't make us look bad. We already have GitHub and Reddit for that. Other beartype import hooks – like :func:`.beartype_packages` or :func:`.beartyping` – can be (mis)used to dangerously type-check *other* third-party packages outside your control that have probably never been stress-tested with beartype. Those packages could raise type-checking violations at runtime that you have no control over. If they don't now, they could later. Forward compatibility is out the window. ``git blame`` has things to say about that. If :func:`.beartype_this_package` fails, there is no hope for your package. Even though it might be beartype's fault, beartype will still blame you for its mistakes. Import Hooks API ################ Beartype import hooks come in two flavours: * :ref:`Global import hooks `, whose effects encompass *all* subsequently imported packages and modules matching various patterns. * :ref:`Local import hooks `, whose effects are isolated to only specific packages and modules imported inside specific blocks of code. Any subsequently imported packages and modules remain unaffected. .. _api_claw:global: Global Import Hooks ******************* Global beartype import hooks are... well, *global*. Their claws extend to a horizontal slice of your full stack. These hooks globally type-check *all* annotated callables, classes, and variable assignments in *all* subsequently imported packages and modules matching various patterns. With great globality comes great responsibility. .. py:function:: beartype_this_package(*, conf: beartype.BeartypeConf = beartype.BeartypeConf()) -> None :arg conf: Beartype configuration. Defaults to the default configuration performing :math:`O(1)` type-checking. :type conf: beartype.BeartypeConf :raise beartype.roar.BeartypeClawHookException: If either: * This function is *not* called from a module (i.e., this function is called directly from within a read–eval–print loop (REPL)). * ``conf`` is *not* a beartype configuration. **Self-package runtime-static type-checking import hook.** This hook accepts *no* package or module names, instead type-checking *all* annotated callables, classes, and variable assignments across *all* submodules of the **current package** (i.e., the caller-defined package directly calling this function). This hook only applies to subsequent imports performed *after* this hook, as the term "import hook" implies; previously imported submodules and subpackages remain unaffected. This hook is typically called as the first statement in the ``__init__`` submodule of whichever (sub)package you would like to type-check. If you call this hook from: * Your top-level ``{your_package}.__init__`` submodule, this hook type-checks your entire package. This includes *all* submodules and subpackages across your entire package. * Some mid-level ``{your_package}.{your_subpackage}.__init__`` submodule, this hook type-checks only that subpackage. This includes *only* submodules and subsubpackages of that subpackage. All other submodules and subpackages of your package remain unaffected (i.e., will *not* be type-checked). .. code-block:: python # At the top of your "{your_package}.__init__" submodule: from beartype import BeartypeConf # <-- boilerplate from beartype.claw import beartype_this_package # <-- boilerplate: the revenge beartype_this_package(conf=BeartypeConf(is_color=False)) # <-- no color is best color This hook is effectively syntactic sugar for the following idiomatic one-liners that are so cumbersome, fragile, and unreadable that no one should even be reading this: .. code-block:: python beartype_this_package() # <-- this... beartype_package(__name__.rpartition('.')[0]) # <-- ...is equivalent to this... beartype_packages((__name__.rpartition('.')[0],)) # <-- ...is equivalent to this. When in doubt, have no doubt. Just call :func:`.beartype_this_package`. .. versionadded:: 0.15.0 .. image:: https://user-images.githubusercontent.com/217028/272775398-761b9f11-95c2-4410-ad56-fd1ebe99bf04.png :alt: fierce determined face :superscript:`beartype_this_package(): It do be like that.` .. py:function:: beartype_package( \ package_name: str, \ *, \ conf: beartype.BeartypeConf = beartype.BeartypeConf() \ ) -> None :arg package_name: Absolute name of the package or module to be type-checked. :type package_name: str :arg conf: Beartype configuration. Defaults to the default configuration performing :math:`O(1)` type-checking. :type conf: beartype.BeartypeConf :raise beartype.roar.BeartypeClawHookException: If either: * ``conf`` is *not* a beartype configuration. * ``package_name`` is either: * *Not* a string. * The empty string. * A non-empty string that is *not* a valid **package or module name** (i.e., ``"."``-delimited concatenation of valid Python identifiers). **Uni-package runtime-static type-checking import hook.** This hook accepts only a single package or single module name, type-checking *all* annotated callables, classes, and variable assignments across either: * If the passed name is that of a (sub)package, *all* submodules of that (sub)package. * If the passed name is that of a (sub)module, *only* that (sub)module. This hook should be called *before* that package or module is imported; when erroneously called *after* that package or module is imported, this hook silently reduces to a noop (i.e., does nothing regardless of how many times you squint at it suspiciously). This hook is typically called as the first statement in the ``__init__`` submodule of your top-level ``{your_package}.__init__`` submodule. .. code-block:: python # At the top of your "{your_package}.__init__" submodule: from beartype import BeartypeConf # <-- from beartype.claw import beartype_package # <-- x 2 beartype_package('your_package', conf=BeartypeConf(is_debug=True)) # ^-- they said explicit is better than implicit, # but all i got was this t-shirt and a hicky. Of course, that's fairly worthless. Just call :func:`.beartype_this_package`, right? But what if you want to type-check just *one* subpackage or submodule of your package rather than your *entire* package? In that case, :func:`.beartype_this_package` is overbearing. :superscript:`badum ching` Enter :func:`.beartype_package`, the outer limits of QA where you control the horizontal and the vertical: .. code-block:: python # Just because you can do something, means you should do something. beartype_package('good_package.m.A.A.d_submodule') # <-- fine-grained precision strike :func:`.beartype_package` shows it true worth, however, in type-checking *other* people's code. Because the :mod:`beartype.claw` API is a permissive Sarlacc pit, :func:`.beartype_package` happily accepts the absolute name of *any* package or module – whether they wanted you to do that or not: .. code-block:: python # Whenever you want to break something over your knee, never leave your # favorite IDE [read: Vim] without beartype_package(). beartype_package('somebody_elses_package') # <-- blow it up like you just don't care This hook is effectively syntactic sugar for passing the :func:`.beartype_packages` function a 1-tuple containing only this package or module name. .. code-block:: python beartype_package('your_package') # <-- this... beartype_packages(('your_package',)) # <-- ...is equivalent to this. Pretend you didn't see that. Just call :func:`.beartype_package`. .. versionadded:: 0.15.0 .. image:: https://user-images.githubusercontent.com/217028/272775461-e5f62d59-9fe9-49e8-9904-47a1326d8695.png :alt: wizened psychic baby lady :superscript:`Truer words were never spoken, wizened psychic baby lady.` .. py:function:: beartype_packages( \ package_names: collections.abc.Iterable[str], \ *, \ conf: beartype.BeartypeConf = beartype.BeartypeConf() \ ) -> None :arg package_name: Iterable of the absolute names of one or more packages or modules to be type-checked. :type package_name: collections.abc.Iterable[str] :arg conf: Beartype configuration. Defaults to the default configuration performing :math:`O(1)` type-checking. :type conf: beartype.BeartypeConf :raise beartype.roar.BeartypeClawHookException: If either: * ``conf`` is *not* a beartype configuration. * ``package_names`` is either: * *Not* an iterable. * The empty iterable. * A non-empty iterable containing at least one item that is either: * *Not* a string. * The empty string. * A non-empty string that is *not* a valid **package or module name** (i.e., ``"."``-delimited concatenation of valid Python identifiers). **Multi-package runtime-static type-checking import hook.** This hook accepts one or more package and module names in any arbitrary order (i.e., order is insignificant), type-checking *all* annotated callables, classes, and variable assignments across: * For each passed name that is a (sub)package, *all* submodules of that (sub)package. * For each passed name that is a (sub)module, *only* that (sub)module. This hook should be called *before* those packages and modules are imported; when erroneously called *after* those packages and modules are imported, this hook silently reduces to a noop. Squinting still does nothing. This hook is typically called as the first statement in the ``__init__`` submodule of your top-level ``{your_package}.__init__`` submodule. .. code-block:: python # At the top of your "{your_package}.__init__" submodule: from beartype import BeartypeConf # <-- copy-pasta from beartype.claw import beartype_packages # <-- copy-pasta intensifies beartype_packages(( 'your_package', 'some_package.published_by.the_rogue_ai.Johnny_Twobits', # <-- seems trustworthy 'numpy', # <-- ...heh. no one knows what will happen here! 'scipy', # <-- ...but we can guess, can't we? *sigh* ), conf=BeartypeConf(is_pep484_tower=True)) # <-- so. u 2 h8 precision. This hook is the penultimate force in :ref:`global import hooks `. The terser :func:`.beartype_this_package` and :func:`.beartype_package` hooks are effectively syntactic sugar for this verboser hook. One hook to QA them all, and in the darkness of your codebase bind them. .. versionadded:: 0.15.0 .. image:: https://user-images.githubusercontent.com/217028/272775529-42b85874-56b7-40b4-b9d8-19b603df1657.png :width: 256 :alt: it's the end of the road as we know it, and i feel fine :superscript:`It’s almost as if we know what “penultimate” means.` .. py:function:: beartype_all(*, conf: beartype.BeartypeConf = beartype.BeartypeConf()) -> None :arg conf: Beartype configuration. Defaults to the default configuration performing :math:`O(1)` type-checking. :type conf: beartype.BeartypeConf :raise beartype.roar.BeartypeClawHookException: If ``conf`` is *not* a beartype configuration. **All-packages runtime-static type-checking import hook.** This hook accepts *no* package or module names, instead type-checking *all* callables, classes, and variable assignments across *all* submodules of *all* packages. This hook should be called *before* those packages and modules are imported; when erroneously called *after* those packages and modules are imported, this hook silently reduces to a noop. Not even squinting can help you now. This hook is typically called as the first statement in the ``__init__`` submodule of your top-level ``{your_package}.__init__`` submodule. .. code-block:: python # At the top of your "{your_package}.__init__" submodule, from beartype import BeartypeConf # <-- @beartype seemed so innocent, once from beartype.claw import beartype_all # <-- where did it all go wrong? beartype_all(conf=BeartypeConf(claw_is_pep526=False)) # <-- U WILL BE ASSIMILATE This hook is the ultimate import hook, spasmodically unleashing a wave of bug-defenestrating action over **the entire Python ecosystem.** After calling this hook, *any* package or module authored by *anybody* (including packages and modules in CPython's standard library) will be subject to the iron claw of :mod:`beartype.claw`. Its rule is law! This hook is the runtime equivalent of a full-blown :ref:`pure-static ` type-checker like mypy_ or pyright_, enabling full-stack_ :ref:`runtime-static ` type-checking over your entire app. This includes submodules defined by both: * First-party proprietary packages authored explicitly for this app. * Third-party open-source packages authored and maintained elsewhere. Nothing is isolated. Everything is permanent. Do not trust this hook. Caveat Emptor: Empty Promises Not Even a Cat Would Eat ------------------------------------------------------ This hook imposes type-checking on *all* downstream packages importing your package, which may not necessarily want, expect, or tolerate type-checking. This hook is *not* intended to be called from intermediary APIs, libraries, frameworks, or other middleware. Packages imported by other packages should *not* call this hook. This hook is *only* intended to be called from full-stack_ end-user applications as a convenient alternative to manually passing the names of all packages to be type-checked to the more granular :func:`.beartype_packages` hook. This hook is the extreme QA nuclear option. Because this hook is the extreme QA nuclear option, **most codebases should not call this hook.** :mod:`beartype` cannot be held responsible for a sudden rupture in the plenæne of normalcy, the space-time continuum, or your once-stable job. Pour one out for those who are about to vitriolically explode their own code. Nuke Python from orbit. Because now you can. .. versionadded:: 0.15.0 .. image:: https://github.com/beartype/beartype-assets/assets/217028/cf43dca7-1852-4fec-bcbc-6d4aeca23230 :width: 400 :alt: quiet, safe life :superscript:`The beartype_all() lifestyle. Short but sweet.` .. _api_claw:local: .. #FIXME: Uncomment *AFTER* we actually build out a reasonable first iteration .. #of our local import hook API. *sigh* .. Local Import Hooks .. ****************** Import Hook Configuration ######################### Beartype import hooks accept an optional keyword-only ``conf`` parameter whose value is a **beartype configuration** (i.e., :class:`beartype.BeartypeConf` instance), defaulting to the default beartype configuration ``BeartypeConf()``. Unsurprisingly, that configuration configures the behaviour of its hook: e.g., .. code-block:: python # In your "{your_package}.__init__" submodule, enable @beartype's support for # the PEP 484-compliant implicit numeric tower (i.e., expand "int" to "int | # float" and "complex" to "int | float | complex"): from beartype import BeartypeConf # <-- it all seems so familiar from beartype.claw import beartype_package # <-- boil it up, boilerplate beartype_package('your_package', conf=BeartypeConf(is_pep484_tower=True)) # <-- *UGH.* Equally unsurprisingly, :class:`beartype.BeartypeConf` has been equipped with import hook-aware super powers. Fine-tune the behaviour of our import hooks for your exact needs, including: .. # FIXME: Document these options in "api_decor" as well, please. *sigh* * ``BeartypeConf(claw_is_pep526: bool = True)``. By default, :mod:`beartype.claw` type-checks annotated variable assignments like ``muh_int: int = 'Pretty sure this isn't an integer.'``. Although this is *usually* what everyone wants, this may not be what someone suspicious wearing aviator goggles, a red velvet cape, and too-tight black leather wants. Nobody knows what those people want. If you are such a person, consider disabling this option to reduce type safety and destroy your code like Neo-Tokyo vs. Mecha-Baby-Godzilla: :superscript:`...who will win!?!?` .. code--block:: python # In your "{your_package}.__init__" submodule, disable PEP 526 support out # of spite. You cackle disturbingly as you do. Sanity crumbles. Python shrugs. from beartype import BeartypeConf # <-- boiling boilerplate... from beartype.claw import beartype_packages # <-- ...boils plates, what? beartype_packages( ('your.subpackage', 'your.submodule'), # <-- pretend this makes sense conf=BeartypeConf(claw_is_pep526=False) # <-- *GAH!* ) * ``BeartypeConf(warning_cls_on_decorator_exception: Optional[Type[Warning]] = None)``. By default, :mod:`beartype.claw` emits non-fatal warnings rather than fatal exceptions raised by the :func:`beartype.beartype` decorator at decoration time. This is *usually* what everyone wants, because :func:`beartype.beartype` currently fails to support all possible edge cases and is thus likely to raise at least one exception while decorating your entire package. To improve the resilience of :mod:`beartype.claw` against those edge cases, :func:`beartype.beartype` emits one warning for each decoration exception and then simply continues to the next decoratable callable or class. This is occasionally unhelpful. What if you really *do* want :mod:`beartype.claw` to raise a fatal exception on the first such edge case in your codebase – perhaps because you want to either see the full exception traceback *or* punish your coworkers who are violating typing standards by trying to use an imported module as a type hint? :superscript:`...this actually happened` In this case, consider: * Passing :data:`None` as the value of this parameter. Doing so forces :mod:`beartype.claw` to act strictly, inflexibly, and angrily. Expect spittle-flecked mouth frothing and claws all over the place: .. code-block:: python # In your "{your_package}.__init__" submodule, raise exceptions because you # hate worky. The CI pipeline you break over your knee may just be your own. from beartype import BeartypeConf # <-- boiling boilerplate... from beartype.claw import beartype_this_package # <-- ...ain't even lukewarm beartype_this_package(conf=BeartypeConf(warning_cls_on_decorator_exception=None)) # <-- *ohboy* beartype-0.18.5/doc/src/api_decor.rst000066400000000000000000001171731461113517100174450ustar00rootroot00000000000000.. # ------------------( LICENSE )------------------ .. # Copyright (c) 2014-2024 Beartype authors. .. # See "LICENSE" for further details. .. # .. # ------------------( SYNOPSIS )------------------ .. # Child reStructuredText (reST) document detailing the public-facing API of .. # the @beartype.beartype decorator and associated data structures. .. # ------------------( TODO )------------------ .. # FIXME: Split this overly large and increasingly unreadable document into .. # the following two documents: .. # * "api_decor.rst", containing *EVERYTHING* in this document up to but not .. # including the "Beartype Configuration API" subsection. .. # * "api_conf.rst", containing *EVERYTHING* in this document at and after the .. # "Beartype Configuration API" subsection. .. # .. # Sadly, doing so appears to be currently infeasible. Why? Because Sphinx .. # currently does *NOT* permit "py:module::" directives to be distributed .. # across multiple documents. Ideally, the "api_conf.rst" document would be .. # prefixed by a "py:module::" directive resembling: .. # .. py:module:: beartype .. # :noindexentry: .. # .. # Sadly, even Sphinx 7.0.1 fails to support that: .. # /home/leycec/py/beartype/doc/src/api_conf.rst:12: ERROR: Error in "py:module" directive: .. # unknown option: "noindexentry". .. # .. # .. py:module:: beartype .. # :noindexentry: .. # .. # The ":noindexentry:" option is required to avoid indexing conflicts between .. # the two documents while still preserving cross-references as expected. .. # .. # Consider submitting an upstream Sphinx feature request for this. .. # ------------------( METADATA )------------------ .. # Fully-qualified name of the (sub)package described by this document, .. # enabling this document to be externally referenced as :mod:`{name}`. .. py:module:: beartype .. # ------------------( MAIN )------------------ ******************* Beartype Decoration ******************* .. code-block:: text wrap anything with runtime type-checking ...except that, of course. — Thus Spake Bearathustra, Book I .. # FIXME: Revise all hard-code references to this decorator (e.g., .. # "``@beartype``", "``@beartype.beartype``) into actual beartype.beartype_ .. # interlinks, please. The beating heart of beartype is the eponymous :func:`.beartype` decorator. This is its story. .. # ------------------( TABLES OF CONTENTS )------------------ .. # Table of contents, excluding the above document heading. While the .. # official reStructuredText documentation suggests that a language-specific .. # heading will automatically prepend this table, this does *NOT* appear to .. # be the case. Instead, this heading must be explicitly declared. .. contents:: **Bear with Us** :local: .. # ------------------( DESCRIPTION )------------------ Beartype Decorator API ###################### .. py:decorator:: beartype( \ cls: type | None = None, \ func: collections.abc.Callable | None = None, \ conf: BeartypeConf = BeartypeConf(), \ ) -> object :arg cls: Pure-Python class to be decorated. :type cls: type | None :arg func: Pure-Python function or method to be decorated. :type func: collections.abc.Callable | None :arg conf: Beartype configuration. Defaults to the default configuration performing :math:`O(1)` type-checking. :type conf: beartype.BeartypeConf :return: Passed class or callable wrapped with runtime type-checking. Augment the passed object with performant runtime type-checking. Unlike most decorators, ``@beartype`` has three orthogonal modes of operation: * `Class mode `__ – in which you decorate a class with ``@beartype``, which then iteratively decorates all methods declared by that class with ``@beartype``. This is the recommended mode for **object-oriented logic.** * `Callable mode `__ – in which you decorate a function or method with ``@beartype``, which then dynamically generates a new function or method wrapping the original function or method with performant runtime type-checking. This is the recommended mode for **procedural logic.** * `Configuration mode `__ – in which you create your own app-specific ``@beartype`` decorator **configured** for your exact use case. When chaining multiple decorators, order of decoration is significant but conditionally depends on the mode of operation. Specifically, in: * `Class mode `__, ``@beartype`` should usually be listed *first*. * `Callable mode `__, ``@beartype`` should usually be listed *last*. It's not our fault. Surely documentation would never decieve you. .. _callable mode: Callable Mode ************* *def* beartype.\ **beartype**\ (func: collections.abc.Callable_) -> collections.abc.Callable_ In callable mode, :func:`.beartype` dynamically generates a new **callable** (i.e., pure-Python function or method) runtime type-checking the passed callable. ...as Decorator =============== Because laziness prevails, :func:`.beartype` is *usually* invoked as a decorator. Simply prefix the callable to be runtime type-checked with the line ``@beartype``. In this standard use pattern, :func:`.beartype` silently: #. Replaces the decorated callable with a new callable of the same name and signature. #. Preserves the original callable as the ``__wrapped__`` instance variable of that new callable. An example explicates a thousand words. .. code-block:: pycon # Import the requisite machinery. >>> from beartype import beartype # Decorate a function with @beartype. >>> @beartype ... def bother_free_is_no_bother_to_me(bothersome_string: str) -> str: ... return f'Oh, bother. {bothersome_string}' # Call that function with runtime type-checking enabled. >>> bother_free_is_no_bother_to_me(b'Could you spare a small smackerel?') BeartypeCallHintParamViolation: @beartyped bother_free_is_no_bother_to_me() parameter bothersome_string=b'Could you spare a small smackerel?' violates type hint , as bytes b'Could you spare a small smackerel?' not instance of str. # Call that function with runtime type-checking disabled. WHY YOU DO THIS!? >>> bother_free_is_no_bother_to_me.__wrapped__( ... b'Could you spare a small smackerel?') "Oh, bother. b'Could you spare a small smackerel?'" Because :func:`.beartype` preserves the original callable as ``__wrapped__``, :func:`.beartype` seamlessly integrates with other well-behaved decorators that respect that same pseudo-standard. This means that :func:`.beartype` can *usually* be listed in any arbitrary order when chained (i.e., combined) with other decorators. Because this is the NP-hard timeline, however, assumptions are risky. If you doubt anything, the safest approach is just to list ``@beartype`` as the **last** (i.e., bottommost) decorator. This: * Ensures that :func:`.beartype` is called first on the decorated callable *before* other decorators have a chance to really muck things up. Other decorators: *always the source of all your problems.* * Improves both space and time efficiency. Unwrapping ``__wrapped__`` callables added by prior decorators is an :math:`O(k)` operation for :math:`k` the number of previously run decorators. Moreover, builtin decorators like :class:`classmethod`, :class:`property`, and :class:`staticmethod` create method descriptors; when run *after* a builtin decorator, :func:`.beartype` has no recourse but to: #. Destroy the original method descriptor created by that builtin decorator. #. Create a new method type-checking the original method. #. Create a new method descriptor wrapping that method by calling the same builtin decorator. An example is brighter than a thousand Suns! :sup:`astronomers throwing chalk here` .. code-block:: pycon # Import the requisite machinery. >>> from beartype import beartype # Decorate class methods with @beartype in either order. >>> class BlastItAll(object): ... @classmethod ... @beartype # <-- GOOD. this is the best of all possible worlds. ... def good_idea(cls, we_will_dynamite: str) -> str: ... return we_will_dynamite ... ... @beartype # <-- BAD. technically, fine. pragmatically, slower. ... @classmethod ... def save_time(cls, whats_the_charge: str) -> str: ... return whats_the_charge ...as Function ============== Because Python means not caring what anyone else thinks, :func:`.beartype` can also be called as a function. This is useful in unthinkable edge cases like monkey-patching *other* people's code with runtime type-checking. You usually shouldn't do this, but you usually shouldn't do a lot of things that you do when you're the sort of Pythonista that reads tortuous documentation like this. .. code-block:: pycon # Import the requisite machinery. >>> from beartype import beartype # A function somebody else defined. Note the bad lack of @beartype. >>> def oh_bother_free_where_art_thou(botherfull_string: str) -> str: ... return f'Oh, oh! Help and bother! {botherfull_string}' # Monkey-patch that function with runtime type-checking. *MUHAHAHA.* >>> oh_bother_free_where_art_thou = beartype(oh_bother_free_where_art_thou) # Call that function with runtime type-checking enabled. >>> oh_bother_free_where_art_thou(b"I'm stuck!") BeartypeCallHintParamViolation: @beartyped oh_bother_free_where_art_thou() parameter botherfull_string=b"I'm stuck!" violates type hint , as bytes b"I'm stuck!" not instance of str. One ``beartype()`` to monkey-patch them all and in the darkness type-check them. .. _api_decor:noop: ...as Noop ========== :func:`.beartype` silently reduces to a **noop** (i.e., scoops organic honey out of a jar with its fat paws rather than doing something useful with its life) under common edge cases. When *any* of the following apply, :func:`.beartype` preserves the decorated callable or class as is by just returning that callable or class unmodified (rather than augmenting that callable or class with unwanted runtime type-checking): * Beartype has been configured with the **no-time strategy** :attr:`.BeartypeStrategy.O0`: e.g., .. code-block:: python # Import the requisite machinery. from beartype import beartype, BeartypeConf, BeartypeStrategy # Avoid type-checking *ANY* methods or attributes of this class. @beartype(conf=BeartypeConf(strategy=BeartypeStrategy.O0)) class UncheckedDangerClassIsDangerous(object): # This method raises *NO* type-checking violation despite returning a # non-"None" value. def unchecked_danger_method_is_dangerous(self) -> None: return 'This string is not "None". Sadly, nobody cares anymore.' * That callable or class has already been decorated by: * The :func:`.beartype` decorator itself. * The :pep:`484`\ -compliant :func:`typing.no_type_check` decorator: e.g., .. code-block:: python # Import more requisite machinery. It is requisite. from beartype import beartype from typing import no_type_check # Avoid type-checking *ANY* methods or attributes of this class. @no_type_check class UncheckedRiskyClassRisksOurEntireHistoricalTimeline(object): # This method raises *NO* type-checking violation despite returning a # non-"None" value. def unchecked_risky_method_which_i_am_squinting_at(self) -> None: return 'This string is not "None". Why does nobody care? Why?' * That callable is **unannotated** (i.e., *no* parameters or return values in the signature of that callable are annotated by type hints). * Sphinx_ is currently autogenerating documentation (i.e., Sphinx's `"autodoc" extension `__ is currently running). Laziness **+** efficiency **==** :func:`.beartype`. .. _class mode: Class Mode ********** *def* beartype.\ **beartype**\ (cls: type) -> type In class mode, :func:`.beartype` dynamically replaces *each* method of the passed pure-Python class with a new method runtime type-checking the original method. As with `callable mode `__, simply prefix the class to be runtime type-checked with the line ``@beartype``. In this standard use pattern, :func:`.beartype` silently iterates over all instance, class, and static methods declared by the decorated class and, for each such method: #. Replaces that method with a new method of the same name and signature. #. Preserves the original method as the ``__wrapped__`` instance variable of that new method. ...versus Callable Mode ======================= Superficially, this is just syntactic sugar – but sometimes you gotta dip your paws into the honey pot. .. code-block:: python # Import the requisite machinery. from beartype import beartype # Decorate a class with @beartype. @beartype class IAmABearOfNoBrainAtAll(object): def i_have_been_foolish(self) -> str: return 'A fly can't bird, but a bird can fly.' def and_deluded(self) -> str: return 'Ask me a riddle and I reply.' # ...or just decorate class methods directly with @beartype. # The class above is *EXACTLY* equivalent to the class below. class IAmABearOfNoBrainAtAll(object): @beartype def i_have_been_foolish(self) -> str: return 'A fly can't bird, but a bird can fly.' @beartype def and_deluded(self) -> str: return 'Ask me a riddle and I reply.' Pragmatically, this is *not* just syntactic sugar. You *must* decorate classes (rather than merely methods) with :func:`.beartype` to type-check the following: * **Class-centric type hints** (i.e., type hints like the :pep:`673`\ -compliant typing.Self_ attribute that describe the decorated class itself). To type-check these kinds of type hints, :func:`.beartype` needs access to the class. :func:`.beartype` lacks access to the class when decorating methods directly. Instead, you *must* decorate classes by :func:`.beartype` for classes declaring one or more methods annotated by one or more class-centric type hints. * **Dataclasses.** The standard :obj:`dataclasses.dataclass` decorator dynamically generates and adds new dunder methods (e.g., ``__init__()``, ``__eq__()``, ``__hash__()``) to the decorated class. These methods do *not* physically exist and thus *cannot* be decorated directly with :func:`.beartype`. Instead, you *must* decorate dataclasses first by ``@beartype`` and then by ``@dataclasses.dataclass``. Order is significant, of course. ```` When decorating classes, ``@beartype`` should *usually* be listed as the **first** (i.e., topmost) decorator. This ensures that :func:`.beartype` is called last on the decorated class *after* other decorators have a chance to dynamically monkey-patch that class (e.g., by adding new methods to that class). :func:`.beartype` will then type-check the monkey-patched functionality as well. Come for the working examples. Stay for the wild hand-waving. .. code-block:: python # Import the requisite machinery. from beartype import beartype from dataclasses import dataclass # Decorate a dataclass first with @beartype and then with @dataclass. If you # accidentally reverse this order of decoration, methods added by @dataclass # like __init__() will *NOT* be type-checked by @beartype. (Blame Guido.) @beartype @dataclass class SoTheyWentOffTogether(object): a_little_boy_and_his_bear: str | bytes will_always_be_playing: str | None = None .. _configuration mode: Configuration Mode ****************** *def* beartype.\ **beartype**\ (\*, conf: beartype.BeartypeConf) -> collections.abc.Callable[[T], T] In configuration mode, :func:`.beartype` dynamically generates a new :func:`.beartype` decorator – configured uniquely for your exact use case. You too may cackle villainously as you feel the unbridled power of your keyboard. .. code-block:: python # Import the requisite machinery. from beartype import beartype, BeartypeConf, BeartypeStrategy # Dynamically create a new @monotowertype decorator configured to: # * Avoid outputting colors in type-checking violations. # * Enable support for the implicit numeric tower standardized by PEP 484. monotowertype = beartype(conf=BeartypeConf( is_color=False, is_pep484_tower=True)) # Decorate with this decorator rather than @beartype everywhere. @monotowertype def muh_colorless_permissive_func(int_or_float: float) -> float: return int_or_float ** int_or_float ^ round(int_or_float) Configuration: *because you know best*. .. _api_decor:conf: Beartype Configuration API ========================== .. py:class:: BeartypeConf( \ *, \ is_color: bool | None = None, \ is_debug: bool = False, \ is_pep484_tower: bool = False, \ strategy: BeartypeStrategy = BeartypeStrategy.O1, \ ) **Beartype configuration** (i.e., self-caching dataclass instance encapsulating all flags, options, settings, and other metadata configuring each type-checking operation performed by beartype – including each decoration of a callable or class by the :func:`.beartype` decorator). The default configuration ``BeartypeConf()`` configures beartype to: * Perform :math:`O(1)` constant-time type-checking for safety, scalability, and efficiency. * Disable support for `PEP 484's implicit numeric tower `__. * Disable developer-specific debugging logic. * Conditionally output color when standard output is attached to a terminal. Beartype configurations may be passed as the optional keyword-only ``conf`` parameter accepted by *most* high-level runtime type-checking functions exported by :mod:`beartype` – including: * The :func:`beartype.beartype` decorator. * The :func:`beartype.claw.beartype_all` import hook. * The :func:`beartype.claw.beartype_package` import hook. * The :func:`beartype.claw.beartype_packages` import hook. * The :func:`beartype.claw.beartype_this_package` import hook. * The :func:`beartype.claw.beartyping` import hook. * The :func:`beartype.door.die_if_unbearable` type-checker. * The :func:`beartype.door.is_bearable` type-checker. * The :func:`beartype.door.TypeHint.die_if_unbearable` type-checker. * The :func:`beartype.door.TypeHint.is_bearable` type-checker. Beartype configurations are immutable objects memoized (i.e., cached) on the unordered set of all passed parameters: .. code-block:: pycon >>> from beartype import BeartypeConf >>> BeartypeConf() is BeartypeConf() True >>> BeartypeConf(is_color=False) is BeartypeConf(is_color=False) True Beartype configurations are comparable under equality: .. code-block:: pycon >>> BeartypeConf(is_color=False) == BeartypeConf(is_color=True) False Beartype configurations are hashable and thus suitable for use as dictionary keys and set members: .. code-block:: pycon >>> BeartypeConf(is_color=False) == BeartypeConf(is_color=True) False >>> confs = {BeartypeConf(), BeartypeConf(is_color=False)} >>> BeartypeConf() in confs True Beartype configurations support meaningful :func:`repr` output: .. code-block:: pycon >>> repr(BeartypeConf()) 'BeartypeConf(is_color=None, is_debug=False, is_pep484_tower=False, strategy=)' Beartype configurations expose read-only public properties of the same names as the above parameters: .. code-block:: pycon >>> BeartypeConf().is_color None >>> BeartypeConf().strategy Keyword Parameters ------------------ Beartype configurations support **optional read-only keyword-only** parameters at instantiation time. Most parameters are suitable for passing by *all* beartype users in *all* possible use cases. Some are only intended to be passed by *some* beartype users in *some* isolated use cases. This is their story. General Keyword Parameters ^^^^^^^^^^^^^^^^^^^^^^^^^^ General-purpose configuration parameters are *always* safely passable: .. py:attribute:: is_debug ``Type:`` :class:`bool` = :data:`False` :data:`True` only if debugging the :func:`.beartype` decorator. If you're curious as to what exactly (if anything) :func:`.beartype` is doing on your behalf, temporarily enable this boolean. Specifically, enabling this boolean (*in no particular order*): * Caches the body of each type-checking wrapper function dynamically generated by :func:`.beartype` with the standard :mod:`linecache` module, enabling these function bodies to be introspected at runtime *and* improving the readability of tracebacks whose call stacks contain one or more calls to these :func:`.beartype`-decorated functions. * Prints the definition (including both the signature and body) of each type-checking wrapper function dynamically generated by :func:.beartype` to standard output. * Appends to the declaration of each **hidden parameter** (i.e., whose name is prefixed by ``"__beartype_"`` and whose value is that of an external attribute internally referenced in the body of that function) a comment providing the machine-readable representation of the initial value of that parameter, stripped of newlines and truncated to a hopefully sensible length. Since the low-level string munger called to do so is shockingly slow, these comments are conditionally embedded in type-checking wrapper functions *only* when this boolean is enabled. Defaults to :data:`False`. Eye-gouging sample output or it didn't happen, so: .. code-block:: pycon # Import the requisite machinery. >>> from beartype import beartype, BeartypeConf # Dynamically create a new @bugbeartype decorator enabling debugging. # Insider D&D jokes in my @beartype? You'd better believe. It's happening. >>> bugbeartype = beartype(conf=BeartypeConf(is_debug=True)) # Decorate with this decorator rather than @beartype everywhere. >>> @bugbeartype ... def muh_bugged_func() -> str: ... return b'Consistency is the bugbear that frightens little minds.' (line 0001) def muh_bugged_func( (line 0002) *args, (line 0003) __beartype_func=__beartype_func, # is (line 0004) __beartype_conf=__beartype_conf, # is "BeartypeConf(is_color=None, is_debug=True, is_pep484_tower=False, strategy= (line 0006) **kwargs (line 0007) ): (line 0008) # Call this function with all passed parameters and localize the value (line 0009) # returned from this call. (line 0010) __beartype_pith_0 = __beartype_func(*args, **kwargs) (line 0011) (line 0012) # Noop required to artificially increase indentation level. Note that (line 0013) # CPython implicitly optimizes this conditional away. Isn't that nice? (line 0014) if True: (line 0015) # Type-check this passed parameter or return value against this (line 0016) # PEP-compliant type hint. (line 0017) if not isinstance(__beartype_pith_0, str): (line 0018) raise __beartype_get_violation( (line 0019) func=__beartype_func, (line 0020) conf=__beartype_conf, (line 0021) pith_name='return', (line 0022) pith_value=__beartype_pith_0, (line 0023) ) (line 0024) (line 0025) return __beartype_pith_0 .. py:attribute:: is_pep484_tower ``Type:`` :class:`bool` = :data:`False` :data:`True` only if enabling support for `PEP 484's implicit numeric tower `__ (i.e., lossy conversion of integers to floating-point numbers as well as both integers and floating-point numbers to complex numbers). Specifically, enabling this instructs beartype to automatically expand: * All :class:`float` type hints to :class:`float` ``|`` :class:`int`, thus implicitly accepting both integers and floating-point numbers for objects annotated as only accepting floating-point numbers. * All :class:`complex` type hints to :class:`complex` ``|`` :class:`float` ``|`` :class:`int`, thus implicitly accepting integers, floating-point, and complex numbers for objects annotated as only accepting complex numbers. Defaults to :data:`False` to minimize precision error introduced by lossy conversions from integers to floating-point numbers to complex numbers. Since most integers do *not* have exact representations as floating-point numbers, each conversion of an integer into a floating-point number typically introduces a small precision error that accumulates over multiple conversions and operations into a larger precision error. Enabling this improves the usability of public APIs at a cost of introducing precision errors. The standard use case is to dynamically define your own app-specific :func:`.beartype` decorator unconditionally enabling support for the implicit numeric tower, usually as a convenience to your userbase who do *not* particularly care about the above precision concerns. Behold the permissive powers of... ``@beartowertype``! .. code-block:: python # Import the requisite machinery. from beartype import beartype, BeartypeConf # Dynamically create a new @beartowertype decorator enabling the tower. beartowertype = beartype(conf=BeartypeConf(is_pep484_tower=True)) # Decorate with this decorator rather than @beartype everywhere. @beartowertype def crunch_numbers(numbers: list[float]) -> float: return sum(numbers) # This is now fine. crunch_numbers([3, 1, 4, 1, 5, 9]) # This is still fine, too. crunch_numbers([3.1, 4.1, 5.9]) .. versionadded:: 0.12.0 .. py:attribute:: strategy ``Type:`` :class:`.BeartypeStrategy` = :attr:`.BeartypeStrategy.O1` **Type-checking strategy** (i.e., :class:`.BeartypeStrategy` enumeration member dictating how many items are type-checked at each nesting level of each container and thus how responsively beartype type-checks containers). This setting governs the core tradeoff in runtime type-checking between: * **Overhead** in the amount of time that beartype spends type-checking. * **Completeness** in the number of objects that beartype type-checks. As beartype gracefully scales up to check larger and larger containers, so beartype simultaneously scales down to check fewer and fewer items of those containers. This scalability preserves performance regardless of container size while increasing the likelihood of false negatives (i.e., failures to catch invalid items in large containers) as container size increases. You can either type-check a small number of objects nearly instantaneously *or* you can type-check a large number of objects slowly. Pick one. Defaults to :attr:`.BeartypeStrategy.O1`, the constant-time :math:`O(1)` strategy – maximizing scalability at a cost of also maximizing false positives. App-only Keyword Parameters ^^^^^^^^^^^^^^^^^^^^^^^^^^^ **App-only configuration parameters** are passed *only* by first-party packages executed as apps, binaries, scripts, servers, or other executable processes (rather than imported as libraries, frameworks, or other importable APIs into the current process): .. py:attribute:: is_color ``Type:`` :class:`bool` | :data:`None` = :data:`None` Tri-state boolean governing how and whether beartype colours **type-checking violations** (i.e., human-readable :exc:`beartype.roar.BeartypeCallHintViolation` exceptions) with POSIX-compliant ANSI escape sequences for readability. Specifically, if this boolean is: * :data:`False`, beartype *never* colours type-checking violations raised by callables configured with this configuration. * :data:`True`, beartype *always* colours type-checking violations raised by callables configured with this configuration. * :data:`None`, beartype conditionally colours type-checking violations raised by callables configured with this configuration only when standard output is attached to an interactive terminal. The :ref:`${BEARTYPE_IS_COLOR} environment variable ` globally overrides this parameter, enabling end users to enforce a global colour policy across their full app stack. When both that variable *and* this parameter are set to differing (and thus conflicting) values, the :class:`BeartypeConf` class: * Ignores this parameter in favour of that variable. * Emits a :class:`beartype.roar.BeartypeConfShellVarWarning` warning notifying callers of this conflict. To avoid this conflict, only downstream executables should pass this parameter; intermediary libraries should *never* pass this parameter. Non-violent communication begins with you. Effectively defaults to :data:`None`. Technically, this parameter defaults to a private magic constant *not* intended to be passed by callers, enabling :mod:`beartype` to reliably detect whether the caller has explicitly passed this parameter or not. The standard use case is to dynamically define your own app-specific :func:`.beartype` decorator unconditionally disabling colours in type-checking violations, usually due to one or more frameworks in your app stack failing to support ANSI escape sequences. Please file issues with those frameworks requesting ANSI support. In the meanwhile, behold the monochromatic powers of... ``@monobeartype``! .. code-block:: python # Import the requisite machinery. from beartype import beartype, BeartypeConf # Dynamically create a new @monobeartype decorator disabling colour. monobeartype = beartype(conf=BeartypeConf(is_color=False)) # Decorate with this decorator rather than @beartype everywhere. @monobeartype def muh_colorless_func() -> str: return b'In the kingdom of the blind, you are now king.' .. versionadded:: 0.12.0 Beartype Strategy API ===================== .. py:class:: BeartypeStrategy ``Superclass(es):`` :class:`enum.Enum` Enumeration of all kinds of **type-checking strategies** (i.e., competing procedures for type-checking objects passed to or returned from :func:`.beartype`-decorated callables, each with concomitant tradeoffs with respect to runtime complexity and quality assurance). Strategies are intentionally named according to `conventional Big O notation `__ (e.g., :attr:`.BeartypeStrategy.On` enables the :math:`O(n)` strategy). Strategies are established per-decoration at the fine-grained level of callables decorated by the :func:`.beartype` decorator. Simply set the :attr:`.BeartypeConf.strategy` parameter of the :class:`.BeartypeConf` object passed as the optional ``conf`` parameter to the :func:`.beartype` decorator. .. code-block:: python # Import the requisite machinery. from beartype import beartype, BeartypeConf, BeartypeStrategy # Dynamically create a new @slowmobeartype decorator enabling "full fat" # O(n) type-checking. slowmobeartype = beartype(conf=BeartypeConf(strategy=BeartypeStrategy.On)) # Type-check all items of the passed list. Do this only when you pretend # to know in your guts that this list will *ALWAYS* be ignorably small. @bslowmobeartype def type_check_like_maple_syrup(liquid_gold: list[int]) -> str: return 'The slowest noop yet envisioned? You're not wrong.' Strategies enforce their corresponding runtime complexities (e.g., :math:`O(n)`) across *all* type-checks performed for callables enabling those strategies. For example, a callable configured by the :attr:`.BeartypeStrategy.On` strategy will exhibit linear :math:`O(n)` complexity as its overhead for type-checking each nesting level of each container passed to and returned from that callable. This enumeration defines these members: .. py:attribute:: On ``Type:`` :class:`beartype.cave.EnumMemberType` **Linear-time strategy:** the :math:`O(n)` strategy, type-checking *all* items of a container. .. note:: **This strategy is currently unimplemented.** Still, interested users are advised to opt-in to this strategy now; your code will then type-check as desired on the first beartype release supporting this strategy. Beartype: *We're here for you, fam.* .. py:attribute:: Ologn ``Type:`` :class:`beartype.cave.EnumMemberType` **Logarithmic-time strategy:** the :math:`O(\log n)` strategy, type-checking a randomly selected number of items ``log(len(obj))`` of each container ``obj``. .. note:: **This strategy is currently unimplemented.** Still, interested users are advised to opt-in to this strategy now; your code will then type-check as desired on the first beartype release supporting this strategy. Beartype: *We're here for you, fam.* .. py:attribute:: O1 ``Type:`` :class:`beartype.cave.EnumMemberType` **Constant-time strategy:** the default :math:`O(1)` strategy, type-checking a single randomly selected item of each container. As the default, this strategy need *not* be explicitly enabled. .. py:attribute:: O0 ``Type:`` :class:`beartype.cave.EnumMemberType` **No-time strategy,** disabling type-checking for a decorated callable by reducing :func:`.beartype` to the identity decorator for that callable. This strategy is functionally equivalent to but more general-purpose than the standard :func:`typing.no_type_check` decorator; whereas :func:`typing.no_type_check` only applies to callables, this strategy applies to *any* context accepting a beartype configuration such as: * The :func:`.beartype` decorator decorating a class. * The :func:`beartype.door.is_bearable` function. * The :func:`beartype.door.die_if_unbearable` function. * The :meth:`beartype.door.TypeHint.is_bearable` method. * The :meth:`beartype.door.TypeHint.die_if_unbearable` method. Just like in real life, there exist valid use cases for doing absolutely nothing – including: * **Blacklisting callables.** While seemingly useless, this strategy allows callers to selectively prevent callables that would otherwise be type-checked (e.g., due to class decorations or import hooks) from being type-checked: .. code-block:: python # Import the requisite machinery. from beartype import beartype, BeartypeConf, BeartypeStrategy # Dynamically create a new @nobeartype decorator disabling type-checking. nobeartype = beartype(conf=BeartypeConf(strategy=BeartypeStrategy.O0)) # Automatically decorate all methods of this class... @beartype class TypeCheckedClass(object): # Including this method, which raises a type-checking violation # due to returning a non-"None" value. def type_checked_method(self) -> None: return 'This string is not "None". Apparently, that is a problem.' # Excluding this method, which raises *NO* type-checking # violation despite returning a non-"None" value. @nobeartype def non_type_checked_method(self) -> None: return 'This string is not "None". Thankfully, no one cares.' * **Eliding overhead.** Beartype :ref:`already exhibits near-real-time overhead of less than 1µs (one microsecond, one millionth of a second) per call of type-checked callables `. When even that negligible overhead isn't negligible enough, brave callers considering an occupational change may globally disable *all* type-checking performed by beartype. Prepare your resume beforehand. Also, do so *only* under production builds intended for release; development builds intended for testing should preserve type-checking. Either: * `Pass Python the "-O" command-line option <-O_>`__, which beartype respects. * `Run Python under the "PYTHONOPTIMIZE" environment variable `__, which beartype also respects. * Define a new ``@maybebeartype`` decorator disabling type-checking when an app-specific constant ``I_AM_RELEASE_BUILD`` defined elsewhere is enabled: .. code-block:: python # Import the requisite machinery. from beartype import beartype, BeartypeConf, BeartypeStrategy # Let us pretend you know what you are doing for a hot moment. from your_app import I_AM_RELEASE_BUILD # Dynamically create a new @maybebeartype decorator disabling # type-checking when "I_AM_RELEASE_BUILD" is enabled. maybebeartype = beartype(conf=BeartypeConf(strategy=( BeartypeStrategy.O0 if I_AM_RELEASE_BUILD else BeartypeStrategy.O1 )) # Decorate with this decorator rather than @beartype everywhere. @maybebeartype def muh_performance_critical_func(big_list: list[int]) -> int: return sum(big_list) Beartype Environment Variables ============================== Beartype supports increasingly many **environment variables** (i.e., external shell variables associated with the active Python interpreter). Most of these variables globally override :class:`.BeartypeConf` parameters of similar names, enabling end users to enforce global configuration policies across their full app stacks. Beneath environment variables... *thy humongous codebase shalt rise.* .. _api_decor:beartype_is_color: ${BEARTYPE_IS_COLOR} -------------------- The ``${BEARTYPE_IS_COLOR}`` environment variable globally overrides the :attr:`.BeartypeConf.is_color` parameter, enabling end users to enforce a global colour policy. As with that parameter, this variable is a tri-state boolean with three possible string values: * ``BEARTYPE_IS_COLOR='True'``, forcefully instantiating *all* beartype configurations across *all* Python processes with the ``is_color=True`` parameter. * ``BEARTYPE_IS_COLOR='False'``, forcefully instantiating *all* beartype configurations across *all* Python processes with the ``is_color=False`` parameter. * ``BEARTYPE_IS_COLOR='None'``, forcefully instantiating *all* beartype configurations across *all* Python processes with the ``is_color=None`` parameter. Force beartype to obey your unthinking hatred of the colour spectrum. You can't be wrong! .. code-block:: bash BEARTYPE_IS_COLOR=False python3 -m monochrome_retro_app.its_srsly_cool .. versionadded:: 0.16.0 beartype-0.18.5/doc/src/api_door.rst000066400000000000000000001104441461113517100173060ustar00rootroot00000000000000.. # ------------------( LICENSE )------------------ .. # Copyright (c) 2014-2024 Beartype authors. .. # See "LICENSE" for further details. .. # .. # ------------------( SYNOPSIS )------------------ .. # Child reStructuredText (reST) document detailing the public-facing API of .. # the "beartype.door" subpackage. .. # ------------------( TODO )------------------ .. # FIXME: Substantially improve the documentation for the object-oriented API .. # defined by the "beartype.door" subpackage. .. # ------------------( METADATA )------------------ .. # Fully-qualified name of the (sub)package described by this document, .. # enabling this document to be externally referenced as :mod:`{name}`. .. py:module:: beartype.door .. # ------------------( MAIN )------------------ .. # FIXME: Similar issue as with "code.rst", sadly. *sigh* .. # ************************************************************ .. # Beartype DOOR: The Decidedly Object-oriented Runtime-checker .. # ************************************************************ ************* Beartype DOOR ************* .. code-block:: text DOOR: the Decidedly Object-Oriented Runtime-checker DOOR: it's capitalized, so it matters Enter the **DOOR** (\ **D**\ ecidedly **O**\ bject-\ **o**\ riented **R**\ untime-checker): beartype's Pythonic API for introspecting, comparing, and type-checking PEP-compliant type hints in average-case :math:`O(1)` time with negligible constants. It's fast is what we're saying. :math:`O(1)`: *it's just how beartype jiggles.* .. # ------------------( TABLES OF CONTENTS )------------------ .. # Table of contents, excluding the above document heading. While the .. # official reStructuredText documentation suggests that a language-specific .. # heading will automatically prepend this table, this does *NOT* appear to .. # be the case. Instead, this heading must be explicitly declared. .. contents:: **Bear with Us** :local: .. # ------------------( DESCRIPTION )------------------ DOOR Overview ############# For efficiency, security, and scalability, the beartype codebase is like the Linux kernel. That's a polite way of saying our code is unreadable gibberish implemented: * **Procedurally,** mostly with module-scoped functions. Classes? We don't need classes where we're going, which is nowhere you want to go. * **Iteratively,** mostly with ``while`` loops over :class:`tuple` instances. We shouldn't have admitted that. We are not kidding. We wish we were kidding. Beartype is an echo chamber of :class:`tuple` all the way down. Never do what we do. This is our teaching moment. DOOR is different. DOOR has competing goals like usability, maintainability, and debuggability. Those things are often valuable to people that live in mythical lands with lavish amenities like potable ground water, functioning electrical grids, and Internet speed in excess of 56k dial-up. To achieve this utopian dream, DOOR is implemented: * **Object-orientedly,** with a non-trivial class hierarchy of metaclasses, mixins, and abstract base classes (ABC) nested twenty levels deep defining dunder methods deferring to public methods leveraging utility functions. Nothing really makes sense, but nothing has to. Tests say it works. After all, would tests lie? We will document everything one day. * **Recursively,** with methods commonly invoking themselves until the call stack invariably ignites in flames. We are pretty sure we didn't just type that. This makes DOOR unsuitable for use inside beartype itself (where ruthless micro-optimizations have beaten up everything else), but optimum for the rest of the world (where rationality, sanity, and business reality reigns in the darker excesses of humanity). This hopefully includes you. Don't be like beartype. Use DOOR instead. DOOR Procedures ############### .. code-block:: text Type-check anything against any type hint – at any time, anywhere. "Any" is the key here. When the :func:`isinstance` and :func:`issubclass` builtins fail to scale, prefer the :mod:`beartype.door` procedural API. Procedural API ************** .. py:function:: die_if_unbearable( \ obj: object, \ hint: object, \ *, \ conf: beartype.BeartypeConf = beartype.BeartypeConf(), \ ) -> None :arg obj: Arbitrary object to be type-checked against ``hint``. :type obj: object :arg hint: Type hint to type-check ``obj`` against. :type hint: object :arg conf: Beartype configuration. Defaults to the default configuration performing :math:`O(1)` type-checking. :type conf: beartype.BeartypeConf :raise beartype.roar.BeartypeCallHintViolation: If ``obj`` violates ``hint``. **Runtime type-checking exception raiser.** If object ``obj``: * Satisfies type hint ``hint`` under configuration ``conf``, :func:`.die_if_unbearable` raises a **typing-checking violation** (i.e., human-readable :exc:`beartype.roar.BeartypeCallHintViolation` exception). * Violates type hint ``hint`` under configuration ``conf``, :func:`.die_if_unbearable` reduces to a noop (i.e., does nothing bad). Release the bloodthirsty examples! .. code-block:: pycon # Import the requisite machinery. >>> from beartype.door import die_if_unbearable >>> from beartype.typing import List, Sequence # Type-check an object violating a type hint. >>> die_if_unbearable("My people ate them all!", List[int] | None]) BeartypeDoorHintViolation: Object 'My people ate them all!' violates type hint list[int] | None, as str 'My people ate them all!' not list or . # Type-check multiple objects satisfying multiple type hints. >>> die_if_unbearable("I'm swelling with patriotic mucus!", str | None) >>> die_if_unbearable("I'm not on trial here.", Sequence[str]) .. tip:: For those familiar with typeguard_, this function implements the beartype equivalent of the low-level typeguard.check_type_ function. For everyone else, pretend you never heard us just namedrop typeguard_. .. py:function:: is_bearable( \ obj: object, \ hint: object, \ *, \ conf: beartype.BeartypeConf = beartype.BeartypeConf(), \ ) -> bool :arg obj: Arbitrary object to be type-checked against ``hint``. :type obj: object :arg hint: Type hint to type-check ``obj`` against. :type hint: object :arg conf: Beartype configuration. Defaults to the default configuration performing :math:`O(1)` type-checking. :type conf: beartype.BeartypeConf :return bool: :data:`True` only if ``obj`` satisfies ``hint``. **Runtime type-checking tester.** If object ``obj``: * Satisfies type hint ``hint`` under configuration ``conf``, :func:`.is_bearable` returns :data:`True`. * Violates type hint ``hint`` under configuration ``conf``, :func:`.is_bearable` returns :data:`False`. An example paints a thousand docstrings. :sup:`...what does that even mean?` .. code-block:: pycon # Import the requisite machinery. >>> from beartype.door import is_bearable >>> from beartype.typing import List, Sequence # Type-check an object violating a type hint. >>> is_bearable('Stop exploding, you cowards.', List[bool] | None) False # Type-check multiple objects satisfying multiple type hints. >>> is_bearable("Kif, I’m feeling the ‘Captain's itch.’", str | None) True >>> is_bearable('I hate these filthy Neutrals, Kif.', Sequence[str]) True :func:`.is_bearable` is a strict superset of the :func:`isinstance` builtin. :func:`.is_bearable` can thus be safely called wherever :func:`isinstance` is called with the same exact parameters in the same exact order: .. code-block:: pycon # Requisite machinery: I import you. >>> from beartype.door import is_bearable # These two statements are semantically equivalent. >>> is_bearable('I surrender and volunteer for treason.', str) True >>> isinstance('I surrender and volunteer for treason.', str) True # These two statements are semantically equivalent, too. >>> is_bearable(b'A moment of weakness is all it takes.', (str, bytes)) True >>> isinstance(b'A moment of weakness is all it takes.', (str, bytes)) True # These two statements are semantically equivalent, yet again. *shockface* >>> is_bearable('Comets: the icebergs of the sky.', bool | None) False >>> isinstance('Comets: the icebergs of the sky.', bool | None) True :func:`.is_bearable` is also a *spiritual* superset of the :func:`issubclass` builtin. :func:`.is_bearable` can be safely called wherever :func:`issubclass` is called by replacing the superclass(es) to be tested against with a ``type[{cls}]`` or ``type[{cls1}] | ... | type[{clsN}]`` type hint: .. code-block:: pycon # Machinery. It is requisite. >>> from beartype.door import is_bearable >>> from beartype.typing import Type >>> from collections.abc import Awaitable, Collection, Iterable # These two statements are semantically equivalent. >>> is_bearable(str, Type[Iterable]) True >>> issubclass(str, Iterable) True # These two statements are semantically equivalent, too. >>> is_bearable(bytes, Type[Collection] | Type[Awaitable]) True >>> issubclass(bytes, (Collection, Awaitable)) True # These two statements are semantically equivalent, yet again. *ohbygods* >>> is_bearable(bool, Type[str] | Type[float]) False >>> issubclass(bool, (str, float)) True :func:`.is_bearable` also performs :pep:`647`\ -compliant `type narrowing`_ with the standard :obj:`typing.TypeGuard` type hint, facilitating communication between beartype and static type-checkers (e.g., mypy_, pyright_). See :ref:`this FAQ entry for further details `. .. py:function:: is_subhint(subhint: object, superhint: object) -> bool :arg subhint: Type hint to tested as a subhint. :type subhint: object :arg superhint: Type hint to tested as a superhint. :type superhint: object :return bool: :data:`True` only if ``subhint`` is a subhint of ``superhint``. **Subhint tester.** If type hint: * ``subhint`` is a **subhint** of type hint ``superhint``, :func:`.is_subhint` returns :data:`True`; else, :func:`.is_subhint` returns :data:`False`. * ``superhint`` is a **superhint** of type hint ``subhint``, :func:`.is_subhint` returns :data:`True`; else, :func:`.is_subhint` returns :data:`False`. This is an alternative way of expressing the same relation as the prior condition – just with the jargon reversed. Jargon gonna jargon. .. code-block:: pycon # Import us up the machinery. >>> from beartype.door import is_subhint >>> from beartype.typing import Any >>> from collections.abc import Callable, Sequence # A type hint matching any callable accepting no arguments and returning # a list is a subhint of a type hint matching any callable accepting any # arguments and returning a sequence of any types. >>> is_subhint(Callable[[], list], Callable[..., Sequence[Any]]) True # A type hint matching any callable accepting no arguments and returning # a list, however, is *NOT* a subhint of a type hint matching any # callable accepting any arguments and returning a sequence of integers. >>> is_subhint(Callable[[], list], Callable[..., Sequence[int]]) False # Booleans are subclasses and thus subhints of integers. >>> is_subhint(bool, int) True # The converse, however, is *NOT* true. >>> is_subhint(int, bool) False # All classes are subclasses and thus subhints of themselves. >>> is_subhint(int, int) True Equivalently, :func:`.is_subhint` returns :data:`True` only if *all* of the following conditions are satisfied: * **Commensurability.** ``subhint`` and ``superhint`` are **semantically related** by conveying broadly similar intentions, enabling these two hints to be reasonably compared. For example: * ``callable.abc.Iterable[str]`` and ``callable.abc.Sequence[int]`` are semantically related. These two hints both convey container semantics. Despite their differing child hints, these two hints are broadly similar enough to be reasonably comparable. * ``callable.abc.Iterable[str]`` and ``callable.abc.Callable[[], int]`` are *not* semantically related. Whereas the first hints conveys a container semantic, the second hint conveys a callable semantic. Since these two semantics are unrelated, these two hints are dissimilar enough to *not* be reasonably comparable. * **Narrowness.** The first hint is either **narrower** than or **semantically equivalent** to the second hint. Equivalently: * The first hint matches **less than or equal to** the total number of all possible objects matched by the second hint. * In `incomprehensible set theoretic jargon `__, the size of the countably infinite set of all possible objects matched by the first hint is **less than or equal to** that of those matched by the second hint. :func:`.is_subhint` supports a variety of real-world use cases, including: * **Multiple dispatch.** A pure-Python decorator can implement `multiple dispatch`_ over multiple overloaded implementations of the same callable by calling this function. An overload of the currently called callable can be dispatched to if the types of the passed parameters are all **subhints** of the type hints annotating that overload. * Formal verification of **API compatibility** across version bumps. Automated tooling like linters, continuous integration (CI), ``git`` hooks, and integrated development environments (IDEs) can raise pre-release alerts prior to accidental publication of API breakage by calling this function. A Python API preserves backward compatibility if each type hint annotating each public class or callable of the current version of that API is a **superhint** of the type hint annotating the same class or callable of the prior release of that API. Procedural Showcase ******************* By the power of beartype, you too shall catch all the bugs. Detect API Breakage =================== Detect breaking API changes in arbitrary callables via type hints alone in ten lines of code – ignoring imports, docstrings, comments, and blank lines to make us look better. .. code-block:: python from beartype import beartype from beartype.door import is_subhint from beartype.peps import resolve_pep563 from collections.abc import Callable @beartype def is_func_api_preserved(func_new: Callable, func_old: Callable) -> bool: ''' ``True`` only if the signature of the first passed callable (presumably the newest version of some callable to be released) preserves backward API compatibility with the second passed callable (presumably an older previously released version of the first passed callable) according to the PEP-compliant type hints annotating these two callables. Parameters ---------- func_new: Callable Newest version of a callable to test for API breakage. func_old: Callable Older version of that same callable. Returns ---------- bool ``True`` only if the ``func_new`` API preserves the ``func_old`` API. ''' # Resolve all PEP 563-postponed type hints annotating these two callables # *BEFORE* reasoning with these type hints. resolve_pep563(func_new) resolve_pep563(func_old) # For the name of each annotated parameter (or "return" for an annotated # return) and the hint annotating that parameter or return for this newer # callable... for func_arg_name, func_new_hint in func_new.__annotations__.items(): # Corresponding hint annotating this older callable if any or "None". func_old_hint = func_old.__annotations__.get(func_arg_name) # If no corresponding hint annotates this older callable, silently # continue to the next hint. if func_old_hint is None: continue # Else, a corresponding hint annotates this older callable. # If this older hint is *NOT* a subhint of this newer hint, this # parameter or return breaks backward compatibility. if not is_subhint(func_old_hint, func_new_hint): return False # Else, this older hint is a subhint of this newer hint. In this case, # this parameter or return preserves backward compatibility. # All annotated parameters and returns preserve backward compatibility. return True The proof is in the real-world pudding. .. code-block:: pycon >>> from numbers import Real # New and successively older APIs of the same example function. >>> def new_func(text: str | None, ints: list[Real]) -> int: ... >>> def old_func(text: str, ints: list[int]) -> bool: ... >>> def older_func(text: str, ints: list) -> bool: ... # Does the newest version of that function preserve backward compatibility # with the next older version? >>> is_func_api_preserved(new_func, old_func) True # <-- good. this is good. # Does the newest version of that function preserve backward compatibility # with the oldest version? >>> is_func_api_preserved(new_func, older_func) False # <-- OH. MY. GODS. In the latter case, the oldest version ``older_func()`` of that function ambiguously annotated its ``ints`` parameter to accept *any* list rather than merely a list of numbers. Both the newer version ``new_func()`` and the next older version ``old_func()`` resolve the ambiguity by annotating that parameter to accept *only* lists of numbers. Technically, that constitutes API breakage; users upgrading from the older version of the package providing ``older_func()`` to the newer version of the package providing ``new_func()`` *could* have been passing lists of non-numbers to ``older_func()``. Their code is now broke. Of course, their code was probably always broke. But they're now screaming murder on your issue tracker and all you can say is: "We shoulda used beartype." In the former case, ``new_func()`` relaxes the constraint from ``old_func()`` that this list contain only integers to accept a list containing both integers and floats. ``new_func()`` thus preserves backward compatibility with ``old_func()``. **Thus was Rome's API preserved in a day.** DOOR Classes ############ Introspect and compare type hints with an object-oriented hierarchy of Pythonic classes. When the standard :mod:`typing` module has you scraping your fingernails on the nearest whiteboard in chicken scratch, prefer the :mod:`beartype.door` object-oriented API. You've already seen that type hints do *not* define a usable public Pythonic API. That was by design. Type hints were *never* intended to be used at runtime. But that's a bad design. Runtime is all that matters, ultimately. If the app doesn't run, it's broke – regardless of what the static type-checker says. Now, beartype breaks a trail through the spiny gorse of unusable PEP standards. Object-oriented Cheatsheet ************************** Open the locked cathedral of type hints with :mod:`beartype.door`: your QA crowbar that legally pries open all type hints. Cry havoc, the bugbears of war! .. code-block:: pycon # This is DOOR. It's a Pythonic API providing an object-oriented interface # to low-level type hints that *OFFICIALLY* have no API whatsoever. >>> from beartype.door import TypeHint # DOOR hint wrapping a PEP 604-compliant type union. >>> union_hint = TypeHint(int | str | None) # <-- so. it begins. # DOOR hints have Pythonic public classes -- unlike normal type hints. >>> type(union_hint) beartype.door.UnionTypeHint # <-- what madness is this? # DOOR hints can be detected Pythonically -- unlike normal type hints. >>> from beartype.door import UnionTypeHint >>> isinstance(union_hint, UnionTypeHint) # <-- *shocked face* True # DOOR hints can be type-checked Pythonically -- unlike normal type hints. >>> union_hint.is_bearable('The unbearable lightness of type-checking.') True >>> union_hint.die_if_unbearable(b'The @beartype that cannot be named.') beartype.roar.BeartypeDoorHintViolation: Object b'The @beartype that cannot be named.' violates type hint int | str | None, as bytes b'The @beartype that cannot be named.' not str, , or int. # DOOR hints can be iterated Pythonically -- unlike normal type hints. >>> for child_hint in union_hint: print(child_hint) TypeHint() TypeHint() TypeHint() # DOOR hints can be indexed Pythonically -- unlike normal type hints. >>> union_hint[0] TypeHint() >>> union_hint[-1] TypeHint() # DOOR hints can be sliced Pythonically -- unlike normal type hints. >>> union_hint[0:2] (TypeHint(), TypeHint()) # DOOR hints supports "in" Pythonically -- unlike normal type hints. >>> TypeHint(int) in union_hint # <-- it's all true. True >>> TypeHint(bool) in union_hint # <-- believe it. False # DOOR hints are sized Pythonically -- unlike normal type hints. >>> len(union_hint) # <-- woah. 3 # DOOR hints test as booleans Pythonically -- unlike normal type hints. >>> if union_hint: print('This type hint has children.') This type hint has children. >>> if not TypeHint(tuple[()]): print('But this other type hint is empty.') But this other type hint is empty. # DOOR hints support equality Pythonically -- unlike normal type hints. >>> from typing import Union >>> union_hint == TypeHint(Union[int, str, None]) True # <-- this is madness. # DOOR hints support comparisons Pythonically -- unlike normal type hints. >>> union_hint <= TypeHint(int | str | bool | None) True # <-- madness continues. # DOOR hints publish the low-level type hints they wrap. >>> union_hint.hint int | str | None # <-- makes sense. # DOOR hints publish tuples of the original child type hints subscripting # (indexing) the original parent type hints they wrap -- unlike normal type # hints, which unreliably publish similar tuples under differing names. >>> union_hint.args (int, str, NoneType) # <-- sense continues to be made. # DOOR hints are semantically self-caching. >>> TypeHint(int | str | bool | None) is TypeHint(None | bool | str | int) True # <-- blowing minds over here. :mod:`beartype.door`: never leave :mod:`typing` without it. Object-oriented Overview ************************ :class:`.TypeHint` wrappers: * Are **immutable**, **hashable**, and thus safely usable both as dictionary keys and set members. * Support efficient **lookup** of child type hints – just like **dictionaries** and **sets**. * Support efficient **iteration** over and **random access** of child type hints – just like **lists** and **tuples**. * Are **partially ordered** over the set of all type hints (according to the :func:`subhint relation <.is_subhint>`) and safely usable in any algorithm accepting a partial ordering (e.g., `topological sort`_). * Guarantee similar performance as :func:`beartype.beartype` itself. All :class:`.TypeHint` methods and properties run in (possibly `amortized `__) **constant time** with negligible constants. Open the DOOR to a whole new world. :sup:`Sing along, everybody! “A whole new worl– *choking noises*”` Object-oriented API ******************* .. py:class:: TypeHint(hint: object) :arg hint: Type hint to be introspected. :type hint: object **Type hint introspector,** wrapping the passed type hint ``hint`` (which, by design, is *mostly* unusable at runtime) with an object-oriented Pythonic API designed explicitly for runtime use. :class:`TypeHint` wrappers are instantiated in the standard way. Appearences can be deceiving, however. In truth, :class:`TypeHint` is actually an abstract base class (ABC) that magically employs exploitative metaclass trickery to instantiate a concrete subclass of itself appropriate for this particular kind of ``hint``. :class:`TypeHint` is thus a **type hint introspector factory.** What you read next may shock you. .. code-block:: pycon >>> from beartype.door import TypeHint >>> from beartype.typing import Optional, Union >>> type(TypeHint(str | list)) beartype.door.UnionTypeHint # <-- UnionTypeHint, I am your father. >>> type(TypeHint(Union[str, list])) beartype.door.UnionTypeHint # <-- NOOOOOOOOOOOOOOOOOOOOOOO!!!!!!!! >>> type(TypeHint(Optional[str])) beartype.door.UnionTypeHint # <-- Search your MRO. You know it to be true. :class:`TypeHint` wrappers cache efficient **singletons** of themselves. On the first instantiation of :class:`TypeHint` by ``hint``, a new instance unique to ``hint`` is created and cached; on each subsequent instantiation, the previously cached instance is returned. Observe and tremble in ecstasy as your introspection eats less space and time. .. code-block:: pycon >>> from beartype.door import TypeHint >>> TypeHint(list[int]) is TypeHint(list[int]) True # <-- you caching monster. how could you? we trusted you! .. # ..................{ PROPERTIES }.................. :class:`TypeHint` wrappers expose these public **read-only properties**: .. py:attribute:: args ``Type:`` :class:`tuple` Tuple of the zero or more **original child type hints** subscripting the original type hint wrapped by this wrapper. .. code-block:: pycon >>> from beartype.door import TypeHint >>> TypeHint(list).args () # <-- i believe this >>> TypeHint(list[int]).args (int,) # <-- fair play to you, beartype! >>> TypeHint(tuple[int, complex]).args (int, complex) # <-- the mind is willing, but the code is weak. :class:`TypeHint` wrappers also expose the tuple of the zero or more **child type wrappers** wrapping these original child type hints with yet more :class:`TypeHint` wrappers. As yet, there exists *no* comparable property providing this tuple. Instead, this tuple is accessed via dunder methods – including ``__iter__()``, ``__getitem__()``, and ``__len__()``. Simply pass any :class:`TypeHint` wrapper to a standard Python container like :class:`list`, :class:`set`, or :class:`tuple`. This makes more sense than it seems. Throw us a frickin' bone here. .. code-block:: pycon >>> from beartype.door import TypeHint >>> tuple(TypeHint(list)) () # <-- is this the real life? is this just fantasy? ...why not both? >>> tuple(TypeHint(list[int])) (TypeHint(),) # <-- the abyss is staring back at us here. >>> tuple(TypeHint(tuple[int, complex])) (TypeHint(), TypeHint()) # <-- make the bad documentation go away, beartype This property is memoized (cached) for both space and time efficiency. .. py:attribute:: hint ``Type:`` :class:`object` **Original type hint** wrapped by this wrapper at instantiation time. .. code-block:: pycon >>> from beartype.door import TypeHint >>> TypeHint(list[int]).hint list[int] Seriously. That's it. That's the property. This isn't *Principia Mathematica*. To you who are about to fall asleep on your keyboards and wake up to find your ``git`` repositories empty, beartype salutes you. .. py:attribute:: is_ignorable ``Type:`` :class:`bool` :data:`True` only if this type hint is **ignorable** (i.e., conveys *no* meaningful semantics despite superficially appearing to do so). While one might expect the set of all ignorable type hints to be both finite and small, one would be wrong. That set is actually **countably infinite** in size. Countably infinitely many type hints are ignorable. That's alot. These include: * :obj:`typing.Any`, by design. Anything is ignorable. You heard it here. * :class:`object`, the root superclass of all types. All objects are instances of :class:`object`, so :class:`object` conveys no semantic meaning. Much like `@leycec`_ on Monday morning, squint when you see :class:`object`. * The unsubscripted :obj:`typing.Optional` singleton, which expands to the implicit ``Optional[Any]`` type hint under :pep:`484`. But :pep:`484` also stipulates that all ``Optional[t]`` type hints expand to ``Union[t, type(None)]`` type hints for arbitrary arguments ``t``. So, ``Optional[Any]`` expands to merely ``Union[Any, type(None)]``. Since all unions subscripted by :obj:`typing.Any` reduce to merely :obj:`typing.Any`, the unsubscripted :obj:`typing.Optional` singleton also reduces to merely :obj:`typing.Any`. This intentionally excludes the ``Optional[type(None)]`` type hint, which the standard :mod:`typing` module reduces to merely ``type(None)``. * The unsubscripted :obj:`typing.Union` singleton, which reduces to :obj:`typing.Any` by the same argument. * Any subscription of :obj:`typing.Union` by one or more ignorable type hints. There exists a countably infinite number of such subscriptions, many of which are non-trivial to find by manual inspection. The ignorability of a union is a transitive property propagated "virally" from child to parent type hints. Consider: * ``Union[Any, bool, str]``. Since :obj:`typing.Any` is ignorable, this hint is trivially ignorable by manual inspection. * ``Union[str, List[int], NewType('MetaType', Annotated[object, 53])]``. Although several child type hints of this union are non-ignorable, the deeply nested :class:`object` child type hint is ignorable by the argument above. It transitively follows that the ``Annotated[object, 53]`` parent type hint subscripted by :class:`object`, the :obj:`typing.NewType` parent type hint aliased to ``Annotated[object, 53]``, *and* the entire union subscripted by that :obj:`typing.NewType` are themselves all ignorable as well. * Any subscription of :obj:`typing.Annotated` by one or more ignorable type hints. As with :obj:`typing.Union`, there exists a countably infinite number of such subscriptions. See the prior item. Or don't. You know. It's all a little boring and tedious, frankly. Are you even reading this? You are, aren't you? Well, dunk me in a bucket full of honey. Post a discussion thread on the beartype repository for your chance to win a dancing cat emoji today! * The :class:`typing.Generic` and :class:`typing.Protocol` superclasses, both of which impose no constraints *in and of themselves.* Since all possible objects satisfy both superclasses. both superclasses are equivalent to the ignorable :class:`object` root superclass: e.g., .. code-block:: pycon >>> from typing as Protocol >>> isinstance(object(), Protocol) True # <-- uhh... >>> isinstance('wtfbro', Protocol) True # <-- pretty sure you lost me there. >>> isinstance(0x696969, Protocol) True # <-- so i'll just be leaving then, shall i? * Any subscription of either the :class:`typing.Generic` or :class:`typing.Protocol` superclasses, regardless of whether the child type hints subscripting those superclasses are ignorable or not. Subscripting a type that conveys no meaningful semantics continues to convey no meaningful semantics. [*Shocked Pikachu face.*] For example, the type hints ``typing.Generic[typing.Any]`` and ``typing.Generic[str]`` are both equally ignorable – despite the :class:`str` class being otherwise unignorable in most type hinting contexts. * And frankly many more. And... *now we know why this property exists.* This property is memoized (cached) for both space and time efficiency. .. # ..................{ METHODS }.................. :class:`TypeHint` wrappers expose these public **methods**: .. py:method:: die_if_unbearable( \ obj: object, \ *, \ conf: beartype.BeartypeConf = beartype.BeartypeConf(), \ ) -> None :arg obj: Arbitrary object to be type-checked against this type hint. :type obj: object :arg conf: Beartype configuration. Defaults to the default configuration performing :math:`O(1)` type-checking. :type conf: beartype.BeartypeConf :raise beartype.roar.BeartypeCallHintViolation: If ``obj`` violates this type hint. Shorthand for calling the :func:`beartype.door.die_if_unbearable` function as ``die_if_unbearable(obj=obj, hint=self.hint, conf=conf)``. Behold: an example. .. code-block:: pycon # This object-oriented approach... >>> from beartype.door import TypeHint >>> TypeHint(bytes | None).die_if_unbearable( ... "You can't lose hope when it's hopeless.") BeartypeDoorHintViolation: Object "You can't lose hope when it's hopeless." violates type hint bytes | None, as str "You can't lose hope when it's hopeless." not bytes or . # ...is equivalent to this procedural approach. >>> from beartype.door import die_if_unbearable >>> die_if_unbearable( ... obj="You can't lose hope when it's hopeless.", hint=bytes | None) BeartypeDoorHintViolation: Object "You can't lose hope when it's hopeless." violates type hint bytes | None, as str "You can't lose hope when it's hopeless." not bytes or . .. py:method:: is_bearable( \ obj: object, \ *, \ conf: beartype.BeartypeConf = beartype.BeartypeConf(), \ ) -> bool :arg obj: Arbitrary object to be type-checked against this type hint. :type obj: object :arg conf: Beartype configuration. Defaults to the default configuration performing :math:`O(1)` type-checking. :type conf: beartype.BeartypeConf :return bool: :data:`True` only if ``obj`` satisfies this type hint. Shorthand for calling the :func:`beartype.door.is_bearable` function as ``is_bearable(obj=obj, hint=self.hint, conf=conf)``. Awaken the example! .. code-block:: pycon # This object-oriented approach... >>> from beartype.door import TypeHint >>> TypeHint(int | float).is_bearable( ... "It's like a party in my mouth and everyone's throwing up.") False # ...is equivalent to this procedural approach. >>> from beartype.door import is_bearable >>> is_bearable( ... obj="It's like a party in my mouth and everyone's throwing up.", ... hint=int | float, ... ) False .. py:method:: is_subhint(superhint: object) -> bool :arg superhint: Type hint to tested as a superhint. :type superhint: object :return bool: :data:`True` only if this type hint is a subhint of ``superhint``. Shorthand for calling the :func:`beartype.door.is_subhint` function as ``is_subhint(subhint=self.hint, superhint=superhint)``. I love the smell of examples in the morning. .. code-block:: pycon # This object-oriented approach... >>> from beartype.door import TypeHint >>> TypeHint(tuple[bool]).is_subhint(tuple[int]) True # ...is equivalent to this procedural approach. >>> from beartype.door import is_subhint >>> is_subhint(subhint=tuple[bool], superhint=tuple[int]) True beartype-0.18.5/doc/src/api_roar.rst000066400000000000000000000512031461113517100173030ustar00rootroot00000000000000.. # ------------------( LICENSE )------------------ .. # Copyright (c) 2014-2024 Beartype authors. .. # See "LICENSE" for further details. .. # .. # ------------------( SYNOPSIS )------------------ .. # Child reStructuredText (reST) document detailing the public-facing API of .. # the "beartype.roar" subpackage. .. # ------------------( METADATA )------------------ .. # Fully-qualified name of the (sub)package described by this document, .. # enabling this document to be externally referenced as :mod:`{name}`. .. py:module:: beartype.roar .. # ------------------( MAIN )------------------ *************** Beartype Errors *************** :: ...is that bear growling or is it just me? — common last words in rural Canada Beartype only raises: * **Beartype-specific exceptions.** For your safety and ours, exceptions raised beartype are easily distinguished from exceptions raised by everybody else. *All* exceptions raised by beartype are instances of: * Public types importable from the :mod:`beartype.roar` subpackage. * The :exc:`beartype.roar.BeartypeException` abstract base class (ABC). * **Disambiguous exceptions.** For your sanity and ours, *every* exception raised by beartype means one thing and one thing only. Beartype *never* reuses the same exception class to mean two different things – allowing you to trivially catch and handle the exact exception you're interested in. Likewise, beartype only emits beartype-specific warnings and disambiguous warnings. Beartype is fastidious to a fault. Error handling is no... *exception*. :sup:`punny *or* funny? you decide.` .. # ------------------( TABLES OF CONTENTS )------------------ .. # Table of contents, excluding the above document heading. While the .. # official reStructuredText documentation suggests that a language-specific .. # heading will automatically prepend this table, this does *NOT* appear to .. # be the case. Instead, this heading must be explicitly declared. .. contents:: **Bear with Us** :local: .. # ------------------( DESCRIPTION )------------------ Exception API ############# Beartype raises fatal exceptions whenever something explodes. Most are self-explanatory – but some assume prior knowledge of arcane type-hinting standards *or* require non-trivial resolutions warranting further discussion. When that happens, don't be the guy that ignores this chapter. .. py:exception:: BeartypeException ``Superclass(es):`` :exc:`Exception` **Beartype exception root superclass.** *All* exceptions raised by beartype are guaranteed to be instances of concrete subclasses of this abstract base class (ABC) whose class names strictly match either: * ``Beartype{subclass_name}Violation`` for type-checking violations (e.g., ``BeartypeCallHintReturnViolation``). * ``Beartype{subclass_name}Exception`` for non-type-checking violations (e.g., ``BeartypeDecorHintPep3119Exception``). .. py:exception:: BeartypeDecorException ``Superclass(es):`` :exc:`.BeartypeException` **Beartype decorator exception superclass.** *All* exceptions raised by the ``@beartype`` decorator at decoration time (i.e., while dynamically generating type-checking wrappers for decorated callables and classes) are guaranteed to be instances of concrete subclasses of this abstract base class (ABC). Since decoration-time exceptions are typically raised from module scope early in the lifetime of a Python process, you are unlikely to manually catch and handle decorator exceptions. A detailed list of subclasses of this ABC is quite inconsequential. Very well. `@leycec`_ admits he was too tired to type it all out. `@leycec`_ also admits he played exploitative video games all night instead... *again*. `@leycec`_ is grateful nobody reads these API notes. :sup:`checkmate, readthedocs.` .. py:exception:: BeartypeCallException ``Superclass(es):`` :exc:`.BeartypeException` **Beartype call-time exception superclass.** Beartype type-checkers (including :func:`beartype.door.die_if_unbearable` and :func:`beartype.beartype`-decorated callables) raise instances of concrete subclasses of this abstract base class (ABC) at call-time – typically when failing a type-check. *All* exceptions raised by beartype type-checkers are guaranteed to be instances of this ABC. Since type-checking exceptions are typically raised from function and method scopes later in the lifetime of a Python process, you are *much* more likely to manually catch and handle instances of this exception type than other types of beartype exceptions. This includes the pivotal :exc:`.BeartypeCallHintViolation` type, which subclasses this type. In fact, you're encouraged to do so. Repeat after Kermode Bear: "Exceptions are fun, everybody." *Gotta catch 'em all!* .. py:exception:: BeartypeCallHintException ``Superclass(es):`` :exc:`.BeartypeCallException` **Beartype type-checking exception superclass.** Beartype type-checkers (including :func:`beartype.door.die_if_unbearable` and :func:`beartype.beartype`-decorated callables) raise instances of concrete subclasses of this abstract base class (ABC) when failing a type-check at call time – typically due to you passing a parameter or returning a value violating a type hint annotating that parameter or return. For once, we're not the ones to blame. The relief in our cubicle is palpable. .. py:exception:: BeartypeCallHintForwardRefException ``Superclass(es):`` :exc:`.BeartypeCallHintException` **Beartype type-checking forward reference exception.** Beartype type-checkers raise instances of this exception type when a **forward reference type hint** (i.e., string referring to a class that has yet to be defined) erroneously references either: * An attribute that does *not* exist. * An attribute that exists but whose value is *not* actually a class. As we gaze forward in time, so too do we glimpse ourselves – unshaven and shabbily dressed – in the rear-view mirror. .. code-block:: pycon >>> from beartype import beartype >>> from beartype.roar import BeartypeCallHintForwardRefException >>> @beartype ... def i_am_spirit_bear(favourite_foodstuff: 'salmon.of.course') -> None: pass >>> try: ... i_am_spirit_bear('Why do you eat all my salmon, Spirit Bear?') ... except BeartypeCallHintForwardRefException as exception: ... print(exception) Forward reference "salmon.of.course" unimportable. .. py:exception:: BeartypeCallHintViolation ``Superclass(es):`` :exc:`.BeartypeCallHintException` **Beartype type-checking violation.** This is the most important beartype exception you never hope to see – and thus the beartype exception you are most likely to see. When your code explodes at midnight, instances of this exception class were lighting the fuse behind your back. Beartype type-checkers raise an instance of this exception class when an object to be type-checked violates the type hint annotating that object. Beartype type-checkers include: * The :func:`beartype.door.die_if_unbearable` function. * The :meth:`beartype.door.TypeHint.die_if_unbearable` method. * User-defined functions and methods decorated by the :func:`beartype.beartype` decorator, which then themselves become beartype type-checkers. Because type-checking violations are why we are all here, instances of this exception class offer additional read-only public properties to assist you in debugging. Inspect these properties at runtime to resolve any lingering doubts about which coworker(s) you intend to blame in your next twenty Git commits: .. py:attribute:: culprits ``Type:`` :class:`tuple`\ [:class:`object`\ , ...] Tuple of one or more **culprits** (i.e., irresponsible objects that violated the type hints annotating those objects during a recent type-check). Specifically, this property returns either: * If a standard slow Python container (e.g., :class:`dict`, :class:`list`, :class:`set`, :class:`tuple`) is responsible for this violation, the 2-tuple ``(root_culprit, leaf_culprit)`` where: * ``root_culprit`` is the outermost such container. This is usually the passed parameter or returned value indirectly violating this type hint. * ``leaf_culprit`` is the innermost item nested in ``root_culprit`` directly violating this type hint. * If a non-container (e.g., scalar, class instance) is responsible for this violation, the 1-tuple ``(culprit,)`` where ``culprit`` is that non-container. Let us examine what the latter means for your plucky intern who will do this after fetching more pumpkin spice lattes for The Team™ (currently engrossed in a critical morale-building "Best of 260" Atari 2600 *Pong* competition): .. code-block:: python # Import the requisite machinery. from beartype import beartype from beartype.roar import BeartypeCallHintViolation # Arbitrary user-defined classes. class SpiritBearIGiveYouSalmonToGoAway(object): pass class SpiritBearIGiftYouHoneyNotToStay(object): pass # Arbitrary instance of one of these classes. SPIRIT_BEAR_REFUSE_TO_GO_AWAY = SpiritBearIGiftYouHoneyNotToStay() # Callable annotated to accept instances of the *OTHER* class. @beartype def when_spirit_bear_hibernates_in_your_bed( best_bear_den: SpiritBearIGiveYouSalmonToGoAway) -> None: pass # Call this callable with this invalid instance. try: when_spirit_bear_hibernates_in_your_bed( SPIRIT_BEAR_REFUSE_TO_GO_AWAY) # *MAGIC HAPPENS HERE*. Catch violations and inspect their "culprits"! except BeartypeCallHintViolation as violation: # Assert that one culprit was responsible for this violation. assert len(violation.culprits) == 1 # The one culprit: don't think we don't see you hiding there! culprit = violation.culprits[0] # Assert that this culprit is the same instance passed above. assert culprit is SPIRIT_BEAR_REFUSE_TO_GO_AWAY **Caveats apply.** This property makes a good-faith effort to list the most significant culprits responsible for this type-checking violation. In two edge cases beyond our control, this property falls back to listing truncated snapshots of the machine-readable representations of those culprits (e.g., the first 10,000 characters or so of their :func:`repr` strings). This safe fallback is triggered for each culprit that: * Has **already been garbage-collected.** To avoid memory leaks, this property only weakly (rather than strongly) refers to these culprits and is thus best accessed only where these culprits are accessible. Technically, this property is safely accessible from any context. Practically, this property is most usefully accessed from the ``except ...:`` block directly catching this violation. Since these culprits may be garbage-collected at any time thereafter, this property *cannot* be guaranteed to refer to these culprits outside that block. If this property is accessed from any other context and one or more of these culprits have sadly passed away, this property dynamically reduces the corresponding items of this tuple to only the machine-readable representations of those culprits. [#the-haunting]_ * Is a **builtin variable-sized C-based object** (e.g., :class:`dict`, :class:`int`, :class:`list`, :class:`str`). Long-standing limitations within CPython itself prevent beartype from weakly referring to those objects. Openly riot on the `CPython bug tracker`_ if this displeases you as much as it does us. Let us examine what this means for your malding CTO: .. code-block:: python # Import the requisite machinery. from beartype import beartype from beartype.roar import BeartypeCallHintViolation from beartype.typing import List # Callable annotated to accept a standard container. @beartype def we_are_all_spirit_bear( best_bear_dens: List[List[str]]) -> None: pass # Standard container deeply violating the above type hint. SPIRIT_BEAR_DO_AS_HE_PLEASE = [ [b'Why do you sleep in my pinball room, Spirit Bear?']] # Call this callable with this invalid container. try: we_are_all_spirit_bear(SPIRIT_BEAR_DO_AS_HE_PLEASE) # Shoddy magic happens here. Catch violations and try (but fail) to # inspect the original culprits, because they were containers! except BeartypeCallHintViolation as violation: # Assert that two culprits were responsible for this violation. assert len(violation.culprits) == 2 # Root and leaf culprits. We just made these words up, people. root_culprit = violation.culprits[0] leaf_culprit = violation.culprits[1] # Assert that these culprits are, in fact, just repr() strings. assert root_culprit == repr(SPIRIT_BEAR_DO_AS_HE_PLEASE) assert leaf_culprit == repr(SPIRIT_BEAR_DO_AS_HE_PLEASE[0][0]) We see that beartype correctly identified the root culprit as the passed list of lists of byte-strings (rather than strings) *and* the leaf culprit as that byte-string. We also see that beartype only returned the :func:`repr` of both culprits rather than those culprits. Why? Because CPython prohibits weak references to both lists *and* byte-strings. This is why we facepalm ourselves in the morning. We did it this morning. We'll do it next morning, too. Until the :mod:`weakref` module improves, `@leycec`_'s forehead *will* be swollen with an angry mass of unsightly red welts that are now festering unbeknownst to his wife. .. versionadded:: 0.12.0 .. [#the-haunting] This exception stores the representations of these culprits inside itself when first raised. Like a gruesome time capsule, they return to haunt you. Warning API ########### Beartype emits non-fatal warnings whenever something looks it might explode in your lap later... *but has yet to do so.* Since it is dangerous to go alone, let beartype's words of anxiety-provoking wisdom be your guide. The codebase you save might be your own. PEP 585 Deprecations ******************** Beartype may occasionally emit non-fatal :pep:`585` deprecation warnings under Python ≥ 3.9 resembling: :: /home/kumamon/beartype/_util/hint/pep/utilpeptest.py:377: BeartypeDecorHintPep585DeprecationWarning: PEP 484 type hint typing.List[int] deprecated by PEP 585 scheduled for removal in the first Python version released after October 5th, 2025. To resolve this, import this hint from "beartype.typing" rather than "typing". See this discussion for further details and alternatives: https://github.com/beartype/beartype#pep-585-deprecations This is that discussion topic. Let's dissect this like a mantis shrimp repeatedly punching out giant kraken. What Does This Mean? ==================== The :pep:`585` standard first introduced by Python 3.9.0 deprecated (obsoleted) *most* of the :pep:`484` standard first introduced by Python 3.5.0 in the official :mod:`typing` module. All deprecated type hints are slated to "be removed from the :mod:`typing` module in the first Python version released 5 years after the release of Python 3.9.0." Spoiler: Python 3.9.0 was released on October 5th, 2020. Altogether, this means that: .. caution:: **Most of the "typing" module will be removed in 2025 or 2026.** If your codebase currently imports from the :mod:`typing` module, *most* of those imports will break under an upcoming Python release. This is what beartype is shouting about. Bad changes are coming to dismantle your working code. Are We on the Worst Timeline? ============================= Season Eight of *Game of Thrones* previously answered this question, but let's try again. You have three options to avert the looming disaster that threatens to destroy everything you hold dear (in ascending order of justice): #. **Import from** :mod:`beartype.typing` **instead.** The easiest (and best) solution is to globally replace all imports from the standard :mod:`typing` module with equivalent imports from our :mod:`beartype.typing` module. So: .. code-block:: python # If you prefer attribute imports, just do this... from beartype.typing import Dict, FrozenSet, List, Set, Tuple, Type # ...instead of this. #from typing import Dict, FrozenSet, List, Set, Tuple, Type # Or if you prefer module imports, just do this... from beartype import typing # ...instead of this. #import typing The public :mod:`beartype.typing` API is a mypy_-compliant replacement for the :mod:`typing` API offering improved forward compatibility with future Python releases. For example: * ``beartype.typing.Set is set`` under Python ≥ 3.9 for :pep:`585` compliance. * ``beartype.typing.Set is typing.Set`` under Python < 3.9 for :pep:`484` compliance. #. **Drop Python < 3.9.** The next easiest (but worst) solution is to brutally drop support for Python < 3.9 by globally replacing all deprecated :pep:`484`\ -compliant type hints with equivalent :pep:`585`\ -compliant type hints (e.g., ``typing.List[int]`` with ``list[int]``). This is really only ideal for closed-source proprietary projects with a limited userbase. All other projects should prefer saner solutions outlined below. #. **Hide warnings.** The reprehensible (but understandable) middle-finger way is to just squelch all deprecation warnings with an ignore warning filter targeting the :class:`.BeartypeDecorHintPep585DeprecationWarning` category. On the one hand, this will still fail in 2025 or 2026 with fiery explosions and thus only constitutes a temporary workaround at best. On the other hand, this has the obvious advantage of preserving Python < 3.9 support with minimal to no refactoring costs. The two ways to do this have differing tradeoffs depending on who you want to suffer most – your developers or your userbase: .. code-block:: python # Do it globally for everyone, whether they want you to or not! # This is the "Make Users Suffer" option. from beartype.roar import BeartypeDecorHintPep585DeprecationWarning from warnings import filterwarnings filterwarnings("ignore", category=BeartypeDecorHintPep585DeprecationWarning) ... # Do it locally only for you! (Hope you like increasing your # indentation level in every single codebase module.) # This is the "Make Yourself Suffer" option. from beartype.roar import BeartypeDecorHintPep585DeprecationWarning from warnings import catch_warnings, filterwarnings with catch_warnings(): filterwarnings("ignore", category=BeartypeDecorHintPep585DeprecationWarning) ... #. **Type aliases.** The hardest (but best) solution is to use `type aliases`_ to conditionally annotate callables with either :pep:`484` *or* :pep:`585` type hints depending on the major version of the current Python interpreter. Since this is life, the hard way is also the best way – but also hard. Unlike the **drop Python < 3.9** approach, this approach preserves backward compatibility with Python < 3.9. Unlike the **hide warnings** approach, this approach also preserves forward compatibility with Python ≥ 3.14159265. `Type aliases`_ means defining a new private ``{your_package}._typing`` submodule resembling: .. code-block:: python # In "{your_package}._typing": from sys import version_info if version_info >= (3, 9): List = list Tuple = tuple ... else: from typing import List, Tuple, ... Then globally refactor all deprecated :pep:`484` imports from :mod:`typing` to ``{your_package}._typing`` instead: .. code-block:: python # Instead of this... from typing import List, Tuple # ...just do this. from {your_package}._typing import List, Tuple What could be simpler? :sup:`...gagging noises faintly heard` beartype-0.18.5/doc/src/api_vale.rst000066400000000000000000001223201461113517100172660ustar00rootroot00000000000000.. # ------------------( LICENSE )------------------ .. # Copyright (c) 2014-2024 Beartype authors. .. # See "LICENSE" for further details. .. # .. # ------------------( SYNOPSIS )------------------ .. # Child reStructuredText (reST) document detailing the public-facing API of .. # the "beartype.vale" subpackage. .. # ------------------( METADATA )------------------ .. # Fully-qualified name of the (sub)package described by this document, .. # enabling this document to be externally referenced as :mod:`{name}`. .. py:module:: beartype.vale .. # ------------------( MAIN )------------------ ******************* Beartype Validators ******************* :: Validate anything with two-line type hints designed by you ⇄ built by beartype When standards fail, do what you want anyway. When official type hints fail to scale to your validation use case, design your own PEP-compliant type hints with compact **beartype validators:** .. code-block:: python # Import the requisite machinery. from beartype import beartype from beartype.vale import Is from typing import Annotated # <--------------- if Python ≥ 3.9.0 #from typing_extensions import Annotated # <--- if Python < 3.9.0 # Type hint matching any two-dimensional NumPy array of floats of arbitrary # precision. Aye, typing matey. Beartype validators a-hoy! import numpy as np Numpy2DFloatArray = Annotated[np.ndarray, Is[lambda array: array.ndim == 2 and np.issubdtype(array.dtype, np.floating)]] # Annotate @beartype-decorated callables with beartype validators. @beartype def polygon_area(polygon: Numpy2DFloatArray) -> float: ''' Area of a two-dimensional polygon of floats defined as a set of counter-clockwise points, calculated via Green's theorem. *Don't ask.* ''' # Calculate and return the desired area. Pretend we understand this. polygon_rolled = np.roll(polygon, -1, axis=0) return np.abs(0.5*np.sum( polygon[:,0]*polygon_rolled[:,1] - polygon_rolled[:,0]*polygon[:,1])) Validators enforce arbitrary runtime constraints on the internal structure and contents of parameters and returns with user-defined lambda functions and nestable declarative expressions leveraging familiar :mod:`typing` syntax – all seamlessly composable with :ref:`standard type hints ` via an `expressive domain-specific language (DSL) `__. Validate custom project constraints *now* without waiting for the open-source community to officially standardize, implement, and publish those constraints. Filling in the Titanic-sized gaps between :ref:`Python's patchwork quilt of PEPs `, validators accelerate your QA workflow with your greatest asset. Yup. It's your brain. See `Validator Showcase`_ for comforting examples – or blithely continue for uncomfortable details you may regret reading. .. # ------------------( TABLES OF CONTENTS )------------------ .. # Table of contents, excluding the above document heading. While the .. # official reStructuredText documentation suggests that a language-specific .. # heading will automatically prepend this table, this does *NOT* appear to .. # be the case. Instead, this heading must be explicitly declared. .. contents:: **Bear with Us** :local: .. # ------------------( DESCRIPTION )------------------ Validator Overview ################## Beartype validators are **zero-cost code generators.** Like the rest of beartype (but unlike other validation frameworks), beartype validators generate optimally efficient pure-Python type-checking logic with *no* hidden function or method calls, undocumented costs, or runtime overhead. Beartype validator code is thus **call-explicit.** Since pure-Python function and method calls are notoriously slow in CPython_, the code we generate only calls the pure-Python functions and methods you specify when you subscript ``beartype.vale.Is*`` classes with those functions and methods. That's it. We *never* call anything without your permission. For example: * The declarative validator ``Annotated[np.ndarray, IsAttr['dtype', IsAttr['type', IsEqual[np.float64]]]]`` detects NumPy arrays of 64-bit floating-point precision by generating the fastest possible inline expression for doing so: .. code-block:: python isinstance(array, np.ndarray) and array.dtype.type == np.float64 * The functional validator ``Annotated[np.ndarray, Is[lambda array: array.dtype.type == np.float64]]`` also detects the same arrays by generating a slightly slower inline expression calling the lambda function you define: .. code-block:: python isinstance(array, np.ndarray) and your_lambda_function(array) Beartype validators thus come in two flavours – each with attendant tradeoffs: * **Functional validators,** created by subscripting the :class:`beartype.vale.Is` factory with a function accepting a single parameter and returning :data:`True` only when that parameter satisfies a caller-defined constraint. Each functional validator incurs the cost of calling that function for each call to each :func:`beartype.beartype`-decorated callable annotated by that validator, but is Turing-complete and thus supports all possible validation scenarios. * **Declarative validators,** created by subscripting any *other* class in the :mod:`beartype.vale` subpackage (e.g., :class:`beartype.vale.IsEqual`) with arguments specific to that class. Each declarative validator generates efficient inline code calling *no* hidden functions and thus incurring no function costs, but is special-purpose and thus supports only a narrow band of validation scenarios. Wherever you can, prefer *declarative* validators for efficiency. Everywhere else, fallback to *functional* validators for generality. Validator API ############# .. py:class:: Is ``Subscription API:`` beartype.vale.\ **Is**\ [\ :class:`collections.abc.Callable`\ [[:class:`object`\ ], :class:`bool`\ ]] **Functional validator.** A PEP-compliant type hint enforcing any arbitrary runtime constraint – created by subscripting (indexing) the :class:`.Is` type hint factory with a function accepting a single parameter and returning either: * :data:`True` if that parameter satisfies that constraint. * :data:`False` otherwise. .. code-block:: python # Import the requisite machinery. from beartype.vale import Is from typing import Annotated # <--------------- if Python ≥ 3.9.0 #from typing_extensions import Annotated # <--- if Python < 3.9.0 # Type hint matching only strings with lengths ranging [4, 40]. LengthyString = Annotated[str, Is[lambda text: 4 <= len(text) <= 40]] Functional validators are caller-defined and may thus validate the internal integrity, consistency, and structure of arbitrary objects ranging from simple builtin scalars like integers and strings to complex data structures defined by third-party packages like NumPy arrays and Pandas DataFrames. .. py:class:: IsAttr ``Subscription API:`` beartype.vale.\ **IsAttr**\ [\ :class:`str`, ``beartype.vale.*``\ ] **Declarative attribute validator.** A PEP-compliant type hint enforcing any arbitrary runtime constraint on any named object attribute – created by subscripting (indexing) the :class:`.IsAttr` type hint factory with (in order): #. The unqualified name of that attribute. #. Any other beartype validator enforcing that constraint. .. code-block:: python # Import the requisite machinery. from beartype.vale import IsAttr, IsEqual from typing import Annotated # <--------------- if Python ≥ 3.9.0 #from typing_extensions import Annotated # <--- if Python < 3.9.0 # Type hint matching only two-dimensional NumPy arrays. Given this, # @beartype generates efficient validation code resembling: # isinstance(array, np.ndarray) and array.ndim == 2 import numpy as np Numpy2DArray = Annotated[np.ndarray, IsAttr['ndim', IsEqual[2]]] The first argument subscripting this class *must* be a syntactically valid unqualified Python identifier string containing only alphanumeric and underscore characters (e.g., ``"dtype"``, ``"ndim"``). Fully-qualified attributes comprising two or more dot-delimited identifiers (e.g., ``"dtype.type"``) may be validated by nesting successive :class:`.IsAttr` subscriptions: .. code-block:: python # Type hint matching only NumPy arrays of 64-bit floating-point numbers. # From this, @beartype generates an efficient expression resembling: # isinstance(array, np.ndarray) and array.dtype.type == np.float64 NumpyFloat64Array = Annotated[np.ndarray, IsAttr['dtype', IsAttr['type', IsEqual[np.float64]]]] The second argument subscripting this class *must* be a beartype validator. This includes: * :class:`beartype.vale.Is`, in which case this parent :class:`.IsAttr` class validates the desired object attribute to satisfy the caller-defined function subscripting that child :class:`.Is` class. * :class:`beartype.vale.IsAttr`, in which case this parent :class:`.IsAttr` class validates the desired object attribute to contain a nested object attribute satisfying the child :class:`.IsAttr` class. See above example. * :class:`beartype.vale.IsEqual`, in which case this :class:`.IsAttr` class validates the desired object attribute to be equal to the object subscripting that :class:`.IsEqual` class. See above example. .. py:class:: IsEqual ``Subscription API:`` beartype.vale.\ **IsEqual**\ [:class:`object`\ ] **Declarative equality validator.** A PEP-compliant type hint enforcing equality against any object – created by subscripting (indexing) the :class:`IsEqual` type hint factory with that object: .. code-block:: python # Import the requisite machinery. from beartype.vale import IsEqual from typing import Annotated # <--------------- if Python ≥ 3.9.0 #from typing_extensions import Annotated # <--- if Python < 3.9.0 # Type hint matching only lists equal to [0, 1, 2, ..., 40, 41, 42]. AnswerToTheUltimateQuestion = Annotated[list, IsEqual[list(range(42))]] :class:`.IsEqual` generalizes the comparable :pep:`586`-compliant :obj:`typing.Literal` type hint. Both check equality against user-defined objects. Despite the differing syntax, these two type hints enforce the same semantics: .. code-block:: python # This beartype validator enforces the same semantics as... IsStringEqualsWithBeartype = Annotated[str, IsEqual['Don’t you envy our pranceful bands?'] | IsEqual['Don’t you wish you had extra hands?'] ] # This PEP 586-compliant type hint. IsStringEqualsWithPep586 = Literal[ 'Don’t you envy our pranceful bands?', 'Don’t you wish you had extra hands?', ] The similarities end there, of course: * :class:`.IsEqual` permissively validates equality against objects that are instances of **any arbitrary type.** :class:`.IsEqual` doesn't care what the types of your objects are. :class:`.IsEqual` will test equality against everything you tell it to, because you know best. * :obj:`typing.Literal` rigidly validates equality against objects that are instances of **only six predefined types:** * Booleans (i.e., :class:`bool` objects). * Byte strings (i.e., :class:`bytes` objects). * Integers (i.e., :class:`int` objects). * Unicode strings (i.e., :class:`str` objects). * :class:`enum.Enum` members. [#enum_type]_ * The :data:`None` singleton. Wherever you can (which is mostly nowhere), prefer :obj:`typing.Literal`. Sure, :obj:`typing.Literal` is mostly useless, but it's standardized across type checkers in a mostly useless way. Everywhere else, default to :class:`.IsEqual`. .. py:class:: IsInstance ``Subscription API:`` beartype.vale.\ **IsInstance**\ [:class:`type`\, ...] **Declarative instance validator.** A PEP-compliant type hint enforcing instancing of one or more classes – created by subscripting (indexing) the :class:`.IsInstance` type hint factory with those classes: .. code-block:: python # Import the requisite machinery. from beartype.vale import IsInstance from typing import Annotated # <--------------- if Python ≥ 3.9.0 #from typing_extensions import Annotated # <--- if Python < 3.9.0 # Type hint matching only string and byte strings, equivalent to: # StrOrBytesInstance = Union[str, bytes] StrOrBytesInstance = Annotated[object, IsInstance[str, bytes]] :class:`.IsInstance` generalizes **isinstanceable type hints** (i.e., normal pure-Python or C-based classes that can be passed as the second parameter to the :func:`isinstance` builtin). Both check instancing of classes. Despite the differing syntax, the following hints all enforce the same semantics: .. code-block:: python # This beartype validator enforces the same semantics as... IsUnicodeStrWithBeartype = Annotated[object, IsInstance[str]] # ...this PEP 484-compliant type hint. IsUnicodeStrWithPep484 = str # Likewise, this beartype validator enforces the same semantics as... IsStrWithWithBeartype = Annotated[object, IsInstance[str, bytes]] # ...this PEP 484-compliant type hint. IsStrWithWithPep484 = Union[str, bytes] The similarities end there, of course: * :class:`.IsInstance` permissively validates type instancing of **arbitrary objects** (including possibly nested attributes of parameters and returns when combined with :class:`beartype.vale.IsAttr`) against **one or more classes.** * Isinstanceable classes rigidly validate type instancing of only **parameters and returns** against only **one class.** Unlike isinstanceable type hints, instance validators support various `set theoretic operators `__. Critically, this includes negation. Instance validators prefixed by the negation operator ``~`` match all objects that are *not* instances of the classes subscripting those validators. Wait. Wait just a hot minute there. Doesn't a :obj:`typing.Annotated` type hint necessarily match instances of the class subscripting that type hint? Yup. This means type hints of the form ``typing.Annotated[{superclass}, ~IsInstance[{subclass}]`` match all instances of a superclass that are *not* also instances of a subclass. And... pretty sure we just invented `type hint arithmetic `__ right there. That sounded intellectual and thus boring. Yet, the disturbing fact that Python booleans are integers :sup:`...yup` while Python strings are infinitely recursive sequences of strings :sup:`...yup` means that `type hint arithmetic `__ can save your codebase from Guido's younger self. Consider this instance validator matching only non-boolean integers, which *cannot* be expressed with any isinstanceable type hint (e.g., :class:`int`) or other combination of standard off-the-shelf type hints (e.g., unions): .. code-block:: python # Type hint matching any non-boolean integer. Never fear integers again. IntNonbool = Annotated[int, ~IsInstance[bool]] # <--- bruh Wherever you can, prefer isinstanceable type hints. Sure, they're inflexible, but they're inflexibly standardized across type checkers. Everywhere else, default to :class:`.IsInstance`. .. py:class:: IsSubclass ``Subscription API:`` beartype.vale.\ **IsSubclass**\ [:class:`type`\, ...] **Declarative inheritance validator.** A PEP-compliant type hint enforcing subclassing of one or more superclasses (base classes) – created by subscripting (indexing) the :class:`.IsSubclass` type hint factory with those superclasses: .. code-block:: python # Import the requisite machinery. from beartype.vale import IsSubclass from typing import Annotated # <--------------- if Python ≥ 3.9.0 #from typing_extensions import Annotated # <--- if Python < 3.9.0 # Type hint matching only string and byte string subclasses. StrOrBytesSubclass = Annotated[type, IsSubclass[str, bytes]] :class:`.IsSubclass` generalizes the comparable :pep:`484`-compliant :obj:`typing.Type` and :pep:`585`-compliant :class:`type` type hint factories. All three check subclassing of arbitrary superclasses. Despite the differing syntax, the following hints all enforce the same semantics: .. code-block:: python # This beartype validator enforces the same semantics as... IsStringSubclassWithBeartype = Annotated[type, IsSubclass[str]] # ...this PEP 484-compliant type hint as well as... IsStringSubclassWithPep484 = Type[str] # ...this PEP 585-compliant type hint. IsStringSubclassWithPep585 = type[str] The similarities end there, of course: * :class:`.IsSubclass` permissively validates type inheritance of **arbitrary classes** (including possibly nested attributes of parameters and returns when combined with :class:`beartype.vale.IsAttr`) against **one or more superclasses.** * :obj:`typing.Type` and :class:`type` rigidly validates type inheritance of only **parameters and returns** against only **one superclass.** Consider this subclass validator, which validates type inheritance of a deeply nested attribute and thus *cannot* be expressed with :obj:`typing.Type` or :class:`type`: .. code-block:: python # Type hint matching only NumPy arrays of reals (i.e., either integers # or floats) of arbitrary precision, generating code resembling: # (isinstance(array, np.ndarray) and # issubclass(array.dtype.type, (np.floating, np.integer))) NumpyRealArray = Annotated[ np.ndarray, IsAttr['dtype', IsAttr['type', IsSubclass[ np.floating, np.integer]]]] Wherever you can, prefer :class:`type` and :obj:`typing.Type`. Sure, they're inflexible, but they're inflexibly standardized across type checkers. Everywhere else, default to :class:`.IsSubclass`. .. [#enum_type] You don't want to know the type of :class:`enum.Enum` members. Srsly. You don't. Okay... you do? Very well. It's :class:`enum.Enum`. :sup:`mic drop` .. _vale:vale syntax: Validator Syntax ################ Beartype validators support a rich domain-specific language (DSL) leveraging familiar Python operators. Dynamically create new validators on-the-fly from existing validators, fueling reuse and preserving DRY_: * **Negation** (i.e., ``not``). Negating any validator with the ``~`` operator creates a new validator returning :data:`True` only when the negated validator returns :data:`False`: .. code-block:: python # Type hint matching only strings containing *no* periods, semantically # equivalent to this type hint: # PeriodlessString = Annotated[str, Is[lambda text: '.' not in text]] PeriodlessString = Annotated[str, ~Is[lambda text: '.' in text]] * **Conjunction** (i.e., ``and``). And-ing two or more validators with the ``&`` operator creates a new validator returning :data:`True` only when *all* of the and-ed validators return :data:`True`: .. code-block:: python # Type hint matching only non-empty strings containing *no* periods, # semantically equivalent to this type hint: # NonemptyPeriodlessString = Annotated[ # str, Is[lambda text: text and '.' not in text]] SentenceFragment = Annotated[str, ( Is[lambda text: bool(text)] & ~Is[lambda text: '.' in text] )] * **Disjunction** (i.e., ``or``). Or-ing two or more validators with the ``|`` operator creates a new validator returning :data:`True` only when at least one of the or-ed validators returns :data:`True`: .. code-block:: python # Type hint matching only empty strings *and* non-empty strings containing # one or more periods, semantically equivalent to this type hint: # EmptyOrPeriodfullString = Annotated[ # str, Is[lambda text: not text or '.' in text]] EmptyOrPeriodfullString = Annotated[str, ( ~Is[lambda text: bool(text)] | Is[lambda text: '.' in text] )] * **Enumeration** (i.e., ``,``). Delimiting two or or more validators with commas at the top level of a :obj:`typing.Annotated` type hint is an alternate syntax for and-ing those validators with the ``&`` operator, creating a new validator returning :data:`True` only when *all* of those delimited validators return :data:`True`. .. code-block:: python # Type hint matching only non-empty strings containing *no* periods, # semantically equivalent to the "SentenceFragment" defined above. SentenceFragment = Annotated[str, Is[lambda text: bool(text)], ~Is[lambda text: '.' in text], ] Since the ``&`` operator is more explicit *and* usable in a wider variety of syntactic contexts, the ``&`` operator is generally preferable to enumeration (all else being equal). * **Interoperability.** As PEP-compliant type hints, validators are safely interoperable with other PEP-compliant type hints and usable wherever other PEP-compliant type hints are usable. Standard type hints are subscriptable with validators, because validators *are* standard type hints: .. code-block:: python # Type hint matching only sentence fragments defined as either Unicode or # byte strings, generalizing "SentenceFragment" type hints defined above. SentenceFragment = Union[ Annotated[bytes, Is[lambda text: b'.' in text]], Annotated[str, Is[lambda text: u'.' in text]], ] `Standard Python precedence rules <_operator precedence>`__ may apply. DSL: *it's not just a telecom acronym anymore.* Validator Caveats ################# .. note:: **Validators require:** * **Beartype.** Currently, all *other* static and runtime type checkers silently ignore beartype validators during type-checking. This includes mypy_ – which we could possibly solve by bundling a `mypy plugin`_ with beartype that extends mypy_ to statically analyze declarative beartype validators (e.g., :class:`beartype.vale.IsAttr`, :class:`beartype.vale.IsEqual`). We leave this as an exercise to the idealistic doctoral thesis candidate. :sup:`Please do this for us, someone who is not us.` * Either **Python ≥ 3.9** *or* `typing_extensions ≥ 3.9.0.0 `__. Validators piggyback onto the :obj:`typing.Annotated` class first introduced with Python 3.9.0 and since backported to older Python versions by the `third-party "typing_extensions" package `__, which beartype also transparently supports. Validator Showcase ################## Observe the disturbing (yet alluring) utility of beartype validators in action as they unshackle type hints from the fetters of PEP compliance. Begone, foulest standards! Full-Fat O(n) Matching ********************** Let's validate **all integers in a list of integers in O(n) time**, because validators mean you no longer have to accept the QA scraps we feed you: .. code-block:: python # Import the requisite machinery. from beartype import beartype from beartype.vale import Is from typing import Annotated # <--------------- if Python ≥ 3.9.0 #from typing_extensions import Annotated # <--- if Python < 3.9.0 # Type hint matching all integers in a list of integers in O(n) time. Please # never do this. You now want to, don't you? Why? You know the price! Why?!? IntList = Annotated[list[int], Is[lambda lst: all( isinstance(item, int) for item in lst)]] # Type-check all integers in a list of integers in O(n) time. How could you? @beartype def sum_intlist(my_list: IntList) -> int: ''' The slowest possible integer summation over the passed list of integers. There goes your whole data science pipeline. Yikes! So much cringe. ''' return sum(my_list) # oh, gods what have you done Welcome to **full-fat type-checking.** In `our disastrous roadmap to beartype 1.0.0 `__, we reluctantly admit that we'd like to augment the :func:`beartype.beartype` decorator with a new parameter enabling full-fat type-checking. But don't wait for us. Force the issue now by just doing it yourself and then mocking us all over Gitter! *Fight the bear, man.* :ref:`There are good reasons to believe that O(1) type-checking is preferable `. Violating that core precept exposes your codebase to scalability and security concerns. But you're the Big Boss, you swear you know best, and (in any case) we can't stop you because we already let the unneutered tomcat out of his trash bin by `publishing this API into the badlands of PyPI `__. Trendy String Matching ********************** Let's accept strings either at least 80 characters long *or* both quoted and suffixed by a period. Look, it doesn't matter. Just do it already, beartype! .. code-block:: python # Import the requisite machinery. from beartype import beartype from beartype.vale import Is from typing import Annotated # <--------------- if Python ≥ 3.9.0 #from typing_extensions import Annotated # <--- if Python < 3.9.0 # Validator matching only strings at least 80 characters in length. IsLengthy = Is[lambda text: len(text) >= 80] # Validator matching only strings suffixed by a period. IsSentence = Is[lambda text: text and text[-1] == '.'] # Validator matching only single- or double-quoted strings. def _is_quoted(text): return text.count('"') >= 2 or text.count("'") >= 2 IsQuoted = Is[_is_quoted] # Combine multiple validators by just listing them sequentially. @beartype def desentence_lengthy_quoted_sentence( text: Annotated[str, IsLengthy, IsSentence, IsQuoted]]) -> str: ''' Strip the suffixing period from a lengthy quoted sentence... 'cause. ''' return text[:-1] # this is horrible # Combine multiple validators by just "&"-ing them sequentially. Yes, this # is exactly identical to the prior function. We do this because we can. @beartype def desentence_lengthy_quoted_sentence_part_deux( text: Annotated[str, IsLengthy & IsSentence & IsQuoted]]) -> str: ''' Strip the suffixing period from a lengthy quoted sentence... again. ''' return text[:-1] # this is still horrible # Combine multiple validators with as many "&", "|", and "~" operators as # you can possibly stuff into a module that your coworkers can stomach. # (They will thank you later. Possibly much later.) @beartype def strip_lengthy_or_quoted_sentence( text: Annotated[str, IsLengthy | (IsSentence & ~IsQuoted)]]) -> str: ''' Strip the suffixing character from a string that is lengthy and/or a quoted sentence, because your web app deserves only the best data. ''' return text[:-1] # this is frankly outrageous Type Hint Arithmetic ******************** **Subtitle:** *From Set Theory They Shall Grow* :pep:`484` standardized the :obj:`typing.Union` factory `disjunctively `__ matching any of several equally permissible type hints ala Python's builtin ``or`` operator or the overloaded ``|`` operator for sets. That's great, because set theory is the beating heart behind type theory. But that's just disjunction_. What about intersection_ (e.g., ``and``, ``&``), `complementation `__ (e.g., ``not``, ``~``), or any of the vast multitude of *other* set theoretic operations? Can we logically connect simple type hints validating trivial constraints into complex type hints validating non-trivial constraints via PEP-standardized analogues of unary and binary operators? **Nope.** They don't exist yet. But that's okay. You use beartype, which means you don't have to wait for official Python developers to get there first. You're already there. :sup:`...woah` Type Hint Elision ================= Python's core type hierarchy conceals an ugly history of secretive backward compatibility. In this subsection, we uncover the two filthiest, flea-infested, backwater corners of the otherwise well-lit atrium that is the Python language – and how exactly you can finalize them. Both obstruct type-checking, readable APIs, and quality assurance in the post-Python 2.7 era. Guido doesn't want you to know. But you want to know, don't you? You are about to enter another dimension, a dimension not only of syntax and semantics but of shame. A journey into a hideous land of annotation wrangling. Next stop... *the Beartype Zone.* Because guess what? * **Booleans are integers.** They shouldn't be. Booleans aren't integers in most high-level languages. Wait. Are you telling me booleans are literally integers in Python? Surely you jest. That can't be. You can't *add* booleans, can you? What would that even mean if you could? Observe and cower, rigorous data scientists. .. code-block:: python >>> True + 3.1415 4.141500000000001 # <-- oh. by. god. >>> isinstance(False, int) True # <-- when nothing is true, everything is true * **Strings are infinitely recursive sequences of...** yup, it's strings. They shouldn't be. Strings aren't infinitely recursive data structures in any other language devised by incautious mortals – high-level or not. Wait. Are you telling me strings are both indistinguishable from full-blown immutable sequences containing arbitrary items *and* infinitely recurse into themselves like that sickening non-Euclidean Hall of Mirrors I puked all over when I was a kid? Surely you kid. That can't be. You can't infinitely index into strings *and* pass and return the results to and from callables expecting either ``Sequence[Any]`` or ``Sequence[str]`` type hints, can you? Witness and tremble, stricter-than-thou QA evangelists. .. code-block:: python >>> 'yougottabekiddi—'[0][0][0][0][0][0][0][0][0][0][0][0][0][0][0] 'y' # <-- pretty sure we just broke the world >>> from collections.abc import Sequence >>> isinstance("Ph'nglui mglw'nafh Cthu—"[0][0][0][0][0], Sequence) True # <-- ...curse you, curse you to heck and back When we annotate a callable as accepting an :class:`int`, we *never* want that callable to also silently accept a :class:`bool`. Likewise, when we annotate another callable as accepting a ``Sequence[Any]`` or ``Sequence[str]``, we *never* want that callable to also silently accept a :class:`str`. These are sensible expectations – just not in Python, where madness prevails. To resolve these counter-intuitive concerns, we need the equivalent of the `relative set complement (or difference) `__. We now call this thing... **type elision!** Sounds pretty hot, right? We know. Booleans ≠ Integers ------------------- Let's first validate **non-boolean integers** with a beartype validator effectively declaring a new ``int - bool`` class (i.e., the subclass of all integers that are *not* booleans): .. code-block:: python # Import the requisite machinery. from beartype import beartype from beartype.vale import IsInstance from typing import Annotated # <--------------- if Python ≥ 3.9.0 #from typing_extensions import Annotated # <--- if Python < 3.9.0 # Type hint matching any non-boolean integer. This day all errata die. IntNonbool = Annotated[int, ~IsInstance[bool]] # <--- bruh # Type-check zero or more non-boolean integers summing to a non-boolean # integer. Beartype wills it. So it shall be. @beartype def sum_ints(*args: IntNonbool) -> IntNonbool: ''' I cast thee out, mangy booleans! You plague these shores no more. ''' return sum(args) Strings ≠ Sequences ------------------- Let's next validate **non-string sequences** with beartype validators effectively declaring a new ``Sequence - str`` class (i.e., the subclass of all sequences that are *not* strings): .. code-block:: python # Import the requisite machinery. from beartype import beartype from beartype.vale import IsInstance from collections.abc import Sequence from typing import Annotated # <--------------- if Python ≥ 3.9.0 #from typing_extensions import Annotated # <--- if Python < 3.9.0 # Type hint matching any non-string sequence. Your day has finally come. SequenceNonstr = Annotated[Sequence, ~IsInstance[str]] # <--- we doin this # Type hint matching any non-string sequence *WHOSE ITEMS ARE ALL STRINGS.* SequenceNonstrOfStr = Annotated[Sequence[str], ~IsInstance[str]] # Type-check a non-string sequence of arbitrary items coerced into strings # and then joined on newline to a new string. (Beartype got your back, bro.) @beartype def join_objects(my_sequence: SequenceNonstr) -> str: ''' Your tide of disease ends here, :class:`str` class! ''' return '\n'.join(map(str, my_sequence)) # <-- no idea how that works # Type-check a non-string sequence whose items are all strings joined on # newline to a new string. It isn't much, but it's all you ask. @beartype def join_strs(my_sequence: SequenceNonstrOfStr) -> str: ''' I expectorate thee up, sequence of strings. ''' return '\n'.join(my_sequence) # <-- do *NOT* do this to a string .. _api:tensor: Tensor Property Matching ************************ Let's validate `the same two-dimensional NumPy array of floats of arbitrary precision as in the lead example above `__ with an efficient declarative validator avoiding the additional stack frame imposed by the functional validator in that example: .. code-block:: python # Import the requisite machinery. from beartype import beartype from beartype.vale import IsAttr, IsEqual, IsSubclass from typing import Annotated # <--------------- if Python ≥ 3.9.0 #from typing_extensions import Annotated # <--- if Python < 3.9.0 # Type hint matching only two-dimensional NumPy arrays of floats of # arbitrary precision. This time, do it faster than anyone has ever # type-checked NumPy arrays before. (Cue sonic boom, Chuck Yeager.) import numpy as np Numpy2DFloatArray = Annotated[np.ndarray, IsAttr['ndim', IsEqual[2]] & IsAttr['dtype', IsAttr['type', IsSubclass[np.floating]]] ] # Annotate @beartype-decorated callables with beartype validators. @beartype def polygon_area(polygon: Numpy2DFloatArray) -> float: ''' Area of a two-dimensional polygon of floats defined as a set of counter-clockwise points, calculated via Green's theorem. *Don't ask.* ''' # Calculate and return the desired area. Pretend we understand this. polygon_rolled = np.roll(polygon, -1, axis=0) return np.abs(0.5*np.sum( polygon[:,0]*polygon_rolled[:,1] - polygon_rolled[:,0]*polygon[:,1])) Validator Alternatives ###################### If the unbridled power of beartype validators leaves you variously queasy, uneasy, and suspicious of our core worldview, beartype also supports third-party type hints like `typed NumPy arrays `__. Whereas beartype validators are verbose, expressive, and general-purpose, the following hints are terse, inexpressive, and domain-specific. Since beartype internally converts these hints to their equivalent validators, `similar caveats apply `__. Notably, these hints require: * Either **Python ≥ 3.9** *or* `typing_extensions ≥ 3.9.0.0 `__. * **Beartype,** which hopefully goes without saying. .. _api:numpy: NumPy Type Hints **************** Beartype conditionally supports `NumPy type hints (i.e., annotations created by subscripting (indexing) various attributes of the "numpy.typing" subpackage) `__ when these optional runtime dependencies are *all* satisfied: * Python ≥ 3.8.0. * beartype ≥ 0.8.0. * `NumPy ≥ 1.21.0 `__. * Either **Python ≥ 3.9** *or* `typing_extensions ≥ 3.9.0.0 `__. Beartype internally converts `NumPy type hints `__ into `equivalent beartype validators `__ at decoration time. `NumPy type hints currently only validate dtypes `__, a common but limited use case. `Beartype validators `__ validate *any* arbitrary combinations of array constraints – including dtypes, shapes, contents, and... well, *anything.* Which is alot. `NumPy type hints `__ are thus just syntactic sugar for `beartype validators `__ – albeit quasi-portable syntactic sugar also supported by mypy_. Wherever you can, prefer `NumPy type hints `__ for portability. Everywhere else, default to `beartype validators `__ for generality. Combine them for the best of all possible worlds: .. code-block:: python # Import the requisite machinery. from beartype import beartype from beartype.vale import IsAttr, IsEqual from numpy import floating from numpy.typing import NDArray from typing import Annotated # <--------------- if Python ≥ 3.9.0 #from typing_extensions import Annotated # <--- if Python < 3.9.0 # Beartype validator + NumPy type hint matching all two-dimensional NumPy # arrays of floating-point numbers of any arbitrary precision. NumpyFloat64Array = Annotated[NDArray[floating], IsAttr['ndim', IsEqual[2]]] Rejoice! A one-liner solves everything yet again. Typed NumPy Arrays ================== Type NumPy arrays by subscripting (indexing) the numpy.typing.NDArray_ class with one of three possible types of objects: * An **array dtype** (i.e., instance of the numpy.dtype_ class). * A **scalar dtype** (i.e., concrete subclass of the numpy.generic_ abstract base class (ABC)). * A **scalar dtype ABC** (i.e., abstract subclass of the numpy.generic_ ABC). Beartype generates fundamentally different type-checking code for these types, complying with both mypy_ semantics (which behaves similarly) and our userbase (which demands this behaviour). May there be hope for our collective future. *class* numpy.typing.\ **NDArray**\ [numpy.dtype_\ ] **NumPy array typed by array dtype.** A PEP-noncompliant type hint enforcing object equality against any **array dtype** (i.e., numpy.dtype_ instance), created by subscripting (indexing) the numpy.typing.NDArray_ class with that array dtype. Prefer this variant when validating the exact data type of an array: .. code-block:: python # Import the requisite machinery. from beartype import beartype from numpy import dtype from numpy.typing import NDArray # NumPy type hint matching all NumPy arrays of 32-bit big-endian integers, # semantically equivalent to this beartype validator: # NumpyInt32BigEndianArray = Annotated[ # np.ndarray, IsAttr['dtype', IsEqual[dtype('>i4')]]] NumpyInt32BigEndianArray = NDArray[dtype('>i4')] *class* numpy.typing.\ **NDArray**\ [numpy.dtype.type_\ ] **NumPy array typed by scalar dtype.** A PEP-noncompliant type hint enforcing object equality against any **scalar dtype** (i.e., concrete subclass of the numpy.generic_ ABC), created by subscripting (indexing) the numpy.typing.NDArray_ class with that scalar dtype. Prefer this variant when validating the exact scalar precision of an array: .. code-block:: python # Import the requisite machinery. from beartype import beartype from numpy import float64 from numpy.typing import NDArray # NumPy type hint matching all NumPy arrays of 64-bit floats, semantically # equivalent to this beartype validator: # NumpyFloat64Array = Annotated[ # np.ndarray, IsAttr['dtype', IsAttr['type', IsEqual[float64]]]] NumpyFloat64Array = NDArray[float64] Common scalar dtypes include: * **Fixed-precision integer dtypes** (e.g., ``numpy.int32``, ``numpy.int64``). * **Fixed-precision floating-point dtypes** (e.g., ``numpy.float32``, ``numpy.float64``). *class* numpy.typing.\ **NDArray**\ [type_\ [numpy.dtype.type_\ ]] **NumPy array typed by scalar dtype ABC.** A PEP-noncompliant type hint enforcing type inheritance against any **scalar dtype ABC** (i.e., abstract subclass of the numpy.generic_ ABC), created by subscripting (indexing) the numpy.typing.NDArray_ class with that ABC. Prefer this variant when validating only the *kind* of scalars (without reference to exact precision) in an array: .. code-block:: python # Import the requisite machinery. from beartype import beartype from numpy import floating from numpy.typing import NDArray # NumPy type hint matching all NumPy arrays of floats of arbitrary # precision, equivalent to this beartype validator: # NumpyFloatArray = Annotated[ # np.ndarray, IsAttr['dtype', IsAttr['type', IsSubclass[floating]]]] NumpyFloatArray = NDArray[floating] Common scalar dtype ABCs include: * numpy.integer_, the superclass of all fixed-precision integer dtypes. * numpy.floating_, the superclass of all fixed-precision floating-point dtypes. beartype-0.18.5/doc/src/code.rst000066400000000000000000001065751461113517100164360ustar00rootroot00000000000000.. # ------------------( LICENSE )------------------ .. # Copyright (c) 2014-2024 Beartype authors. .. # See "LICENSE" for further details. .. # .. # ------------------( SYNOPSIS )------------------ .. # Child reStructuredText (reST) document exhibiting real-world type-checking .. # code generated by beartype. .. # ------------------( MAIN )------------------ .. # FIXME: For unknown reasons, Sphinx currently ignores our label for this .. # document in the TOC tree generated by "index.rst" and instead uses this .. # title. We'd prefer this title to read something ludicrous like: .. # .. _code:code: .. # .. # ############################################## .. # Inside the Bear: Going Paws-deep with Beartype .. # ############################################## .. # .. # ...but that's rather too verbose for our TOC tree. Gah! There's clearly .. # something abnormal about *ONLY* this document -- but we have *NO* idea what .. # that might be. It's *NOT* the filename; we tried renaming to "cad.rst". .. # Bisecting the contents of this file yielded nothing, either. *sigh* #### Code #### Let's take a deep dive into the deep end of runtime type-checking – the beartype way. .. # ------------------( TABLES OF CONTENTS )------------------ .. # Table of contents, excluding the above document heading. While the .. # official reStructuredText documentation suggests that a language-specific .. # heading will automatically prepend this table, this does *NOT* appear to .. # be the case. Instead, this heading must be explicitly declared. .. contents:: **Bear with Us** :local: .. # ------------------( DESCRIPTION )------------------ ****************************************** Beartype Code Generation: It's All for You ****************************************** Beartype dynamically generates type-checking code unique to each class and callable decorated by the :func:`beartype.beartype` decorator. Let's bearsplain why the code :func:`beartype.beartype` generates for real-world use cases is the fastest possible code type-checking those cases. Identity Decoration ################### We begin by wading into the torpid waters of the many ways beartype avoids doing any work whatsoever, because laziness is the virtue we live by. The reader may recall that the fastest decorator at decoration- *and* call-time is the **identity decorator** returning its decorated callable unmodified: e.g., .. code-block:: python from collections.abc import Callable def identity_decorator(func: Callable): -> Callable: return func Beartype silently reduces to the identity decorator whenever it can, which is surprisingly often. Our three weapons are laziness, surprise, ruthless efficiency, and an almost fanatical devotion to constant-time type checking. Unconditional Identity Decoration ################################# Let's define a trivial function annotated by *no* type hints: .. code-block:: python def law_of_the_jungle(strike_first_and_then_give_tongue): return strike_first_and_then_give_tongue Let's decorate that function by :func:`beartype.beartype` and verify that :func:`beartype.beartype` reduced to the identity decorator by returning that function unmodified: .. code-block:: pycon >>> from beartype import beartype >>> beartype(law_of_the_jungle) is law_of_the_jungle True We've verified that :func:`beartype.beartype` reduces to the identity decorator when decorating unannotated callables. That's but the tip of the efficiency iceberg, though. :func:`beartype.beartype` unconditionally reduces to a noop when: * The decorated callable is itself decorated by the :pep:`484`\ -compliant :func:`typing.no_type_check` decorator. * The decorated callable has already been decorated by :func:`beartype.beartype`. * Interpreter-wide optimization is enabled: e.g., * `CPython is invoked with the "-O" command-line option <-O_>`__. * `The "PYTHONOPTIMIZE" environment variable is set `__. Shallow Identity Decoration ########################### Let's define a trivial function annotated by the :pep:`484`\ -compliant :obj:`typing.Any` type hint: .. code-block:: python from typing import Any def law_of_the_jungle_2(never_order_anything_without_a_reason: Any) -> Any: return never_order_anything_without_a_reason Again, let's decorate that function by :func:`beartype.beartype` and verify that :func:`beartype.beartype` reduced to the identity decorator by returning that function unmodified: .. code-block:: pycon >>> from beartype import beartype >>> beartype(law_of_the_jungle_2) is law_of_the_jungle_2 True We've verified that :func:`beartype.beartype` reduces to the identity decorator when decorating callables annotated by :obj:`typing.Any` – a novel category of type hint we refer to as **shallowly ignorable type hints** (known to be ignorable by constant-time lookup in a predefined frozen set). That's but the snout of the crocodile, though. :func:`beartype.beartype` conditionally reduces to a noop when *all* type hints annotating the decorated callable are shallowly ignorable. These include: * :class:`object`, the root superclass of Python's class hierarchy. Since all objects are instances of :class:`object`, :class:`object` conveys no meaningful constraints as a type hint and is thus shallowly ignorable. * :obj:`typing.Any`, equivalent to :class:`object`. * :class:`typing.Generic`, equivalent to ``typing.Generic[typing.Any]``, which conveys no meaningful constraints as a type hint and is thus shallowly ignorable. * :class:`typing.Protocol`, equivalent to ``typing.Protocol[typing.Any]`` and shallowly ignorable for similar reasons. * :obj:`typing.Union`, equivalent to ``typing.Union[typing.Any]``, equivalent to :obj:`typing.Any`. * :obj:`typing.Optional`, equivalent to ``typing.Optional[typing.Any]``, equivalent to ``Union[Any, type(None)]``. Since any union subscripted by ignorable type hints is itself ignorable, [#union_ignorable]_ typing.Optional_ is shallowly ignorable as well. .. [#union_ignorable] Unions are only as narrow as their widest subscripted argument. However, ignorable type hints are ignorable *because* they are maximally wide. Unions subscripted by ignorable arguments are thus the widest possible unions, conveying no meaningful constraints and thus themselves ignorable. Deep Identity Decoration ######################## Let's define a trivial function annotated by a non-trivial :pep:`484`\ -, :pep:`585`\ - and :pep:`593`\ -compliant type hint that superficially *appears* to convey meaningful constraints: .. code-block:: python from typing import Annotated, NewType, Union hint = Union[str, list[int], NewType('MetaType', Annotated[object, 53])] def law_of_the_jungle_3(bring_them_to_the_pack_council: hint) -> hint: return bring_them_to_the_pack_council Despite appearances, it can be shown by exhaustive (and frankly exhausting) reduction that that hint is actually ignorable. Let's decorate that function by :func:`beartype.beartype` and verify that :func:`beartype.beartype` reduced to the identity decorator by returning that function unmodified: .. code-block:: pycon >>> from beartype import beartype >>> beartype(law_of_the_jungle_3) is law_of_the_jungle_3 True We've verified that :func:`beartype.beartype` reduces to the identity decorator when decorating callables annotated by the above object – a novel category of type hint we refer to as **deeply ignorable type hints** (known to be ignorable only by recursive linear-time inspection of subscripted arguments). That's but the trunk of the elephant, though. :func:`beartype.beartype` conditionally reduces to a noop when *all* type hints annotating the decorated callable are deeply ignorable. These include: * Parametrizations of :class:`typing.Generic` and :class:`typing.Protocol` by type variables. Since :class:`typing.Generic`, :class:`typing.Protocol`, *and* type variables all fail to convey any meaningful constraints in and of themselves, these parametrizations are safely ignorable in all contexts. * Calls to :obj:`typing.NewType` passed an ignorable type hint. * Subscriptions of :obj:`typing.Annotated` whose first argument is ignorable. * Subscriptions of :obj:`typing.Optional` and :obj:`typing.Union` by at least one ignorable argument. Constant Decoration ################### We continue by trundling into the turbid waters out at sea, where beartype reluctantly performs its minimal amount of work with a heavy sigh. Constant Builtin Type Decoration ******************************** Let's define a trivial function annotated by type hints that are builtin types: .. code-block:: python from beartype import beartype @beartype def law_of_the_jungle_4(he_must_be_spoken_for_by_at_least_two: int): return he_must_be_spoken_for_by_at_least_two Let's see the wrapper function :func:`beartype.beartype` dynamically generated from that: .. code-block:: pycon def law_of_the_jungle_4( *args, __beartype_func=__beartype_func, __beartypistry=__beartypistry, **kwargs ): # Localize the number of passed positional arguments for efficiency. __beartype_args_len = len(args) # Localize this positional or keyword parameter if passed *OR* to the # sentinel value "__beartypistry" guaranteed to never be passed otherwise. __beartype_pith_0 = ( args[0] if __beartype_args_len > 0 else kwargs.get('he_must_be_spoken_for_by_at_least_two', __beartypistry) ) # If this parameter was passed... if __beartype_pith_0 is not __beartypistry: # Type-check this passed parameter or return value against this # PEP-compliant type hint. if not isinstance(__beartype_pith_0, int): __beartype_get_beartype_violation( func=__beartype_func, pith_name='he_must_be_spoken_for_by_at_least_two', pith_value=__beartype_pith_0, ) # Call this function with all passed parameters and return the value # returned from this call. return __beartype_func(*args, **kwargs) Let's dismantle this bit by bit: * The code comments above are verbatim as they appear in the generated code. * ``law_of_the_jungle_4()`` is the ad-hoc function name :func:`beartype.beartype` assigned this wrapper function. * ``__beartype_func`` is the original ``law_of_the_jungle_4()`` function. * ``__beartypistry`` is a thread-safe global registry of all types, tuples of types, and forward references to currently undeclared types visitable from type hints annotating callables decorated by :func:`beartype.beartype`. We'll see more about the ``__beartypistry`` in a moment. For know, just know that ``__beartypistry`` is a private singleton of the beartype package. This object is frequently accessed and thus localized to the body of this wrapper rather than accessed as a global variable, which would be mildly slower. * ``__beartype_pith_0`` is the value of the first passed parameter, regardless of whether that parameter is passed as a positional or keyword argument. If unpassed, the value defaults to the ``__beartypistry``. Since *no* caller should access (let alone pass) that object, that object serves as an efficient sentinel value enabling us to discern passed from unpassed parameters. Beartype internally favours the term "pith" (which we absolutely just made up) to transparently refer to the arbitrary object currently being type-checked against its associated type hint. * ``isinstance(__beartype_pith_0, int)`` tests whether the value passed for this parameter satisfies the type hint annotating this parameter. * ``__beartype_get_beartype_violation()`` raises a human-readable exception if this value fails this type-check. So good so far. But that's easy. Let's delve deeper. Constant Non-Builtin Type Decoration ************************************ Let's define a trivial function annotated by type hints that are pure-Python classes rather than builtin types: .. code-block:: python from argparse import ArgumentParser from beartype import beartype @beartype def law_of_the_jungle_5(a_cub_may_be_bought_at_a_price: ArgumentParser): return a_cub_may_be_bought_at_a_price Let's see the wrapper function :func:`beartype.beartype` dynamically generated from that: .. code-block:: python def law_of_the_jungle_5( *args, __beartype_func=__beartype_func, __beartypistry=__beartypistry, **kwargs ): # Localize the number of passed positional arguments for efficiency. __beartype_args_len = len(args) # Localize this positional or keyword parameter if passed *OR* to the # sentinel value "__beartypistry" guaranteed to never be passed otherwise. __beartype_pith_0 = ( args[0] if __beartype_args_len > 0 else kwargs.get('a_cub_may_be_bought_at_a_price', __beartypistry) ) # If this parameter was passed... if __beartype_pith_0 is not __beartypistry: # Type-check this passed parameter or return value against this # PEP-compliant type hint. if not isinstance(__beartype_pith_0, __beartypistry['argparse.ArgumentParser']): __beartype_get_beartype_violation( func=__beartype_func, pith_name='a_cub_may_be_bought_at_a_price', pith_value=__beartype_pith_0, ) # Call this function with all passed parameters and return the value # returned from this call. return __beartype_func(*args, **kwargs) The result is largely the same. The only meaningful difference is the type-check on line 20: .. code-block:: python if not isinstance(__beartype_pith_0, __beartypistry['argparse.ArgumentParser']): Since we annotated that function with a pure-Python class rather than builtin type, :func:`beartype.beartype` registered that class with the ``__beartypistry`` at decoration time and then subsequently looked that class up with its fully-qualified classname at call time to perform this type-check. So good so far... so what! Let's spelunk harder. Constant Shallow Sequence Decoration ************************************ Let's define a trivial function annotated by type hints that are :pep:`585`\ -compliant builtin types subscripted by ignorable arguments: .. code-block:: python from beartype import beartype @beartype def law_of_the_jungle_6(all_the_jungle_is_thine: list[object]): return all_the_jungle_is_thine Let's see the wrapper function :func:`beartype.beartype` dynamically generated from that: .. code-block:: python def law_of_the_jungle_6( *args, __beartype_func=__beartype_func, __beartypistry=__beartypistry, **kwargs ): # Localize the number of passed positional arguments for efficiency. __beartype_args_len = len(args) # Localize this positional or keyword parameter if passed *OR* to the # sentinel value "__beartypistry" guaranteed to never be passed otherwise. __beartype_pith_0 = ( args[0] if __beartype_args_len > 0 else kwargs.get('all_the_jungle_is_thine', __beartypistry) ) # If this parameter was passed... if __beartype_pith_0 is not __beartypistry: # Type-check this passed parameter or return value against this # PEP-compliant type hint. if not isinstance(__beartype_pith_0, list): __beartype_get_beartype_violation( func=__beartype_func, pith_name='all_the_jungle_is_thine', pith_value=__beartype_pith_0, ) # Call this function with all passed parameters and return the value # returned from this call. return __beartype_func(*args, **kwargs) We are still within the realm of normalcy. Correctly detecting this type hint to be subscripted by an ignorable argument, :func:`beartype.beartype` only bothered type-checking this parameter to be an instance of this builtin type: .. code-block:: python if not isinstance(__beartype_pith_0, list): It's time to iteratively up the ante. Constant Deep Sequence Decoration ********************************* Let's define a trivial function annotated by type hints that are :pep:`585`\ -compliant builtin types subscripted by builtin types: .. code-block:: python from beartype import beartype @beartype def law_of_the_jungle_7(kill_everything_that_thou_canst: list[str]): return kill_everything_that_thou_canst Let's see the wrapper function :func:`beartype.beartype` dynamically generated from that: .. code-block:: python def law_of_the_jungle_7( *args, __beartype_func=__beartype_func, __beartypistry=__beartypistry, **kwargs ): # Generate and localize a sufficiently large pseudo-random integer for # subsequent indexation in type-checking randomly selected container items. __beartype_random_int = __beartype_getrandbits(64) # Localize the number of passed positional arguments for efficiency. __beartype_args_len = len(args) # Localize this positional or keyword parameter if passed *OR* to the # sentinel value "__beartypistry" guaranteed to never be passed otherwise. __beartype_pith_0 = ( args[0] if __beartype_args_len > 0 else kwargs.get('kill_everything_that_thou_canst', __beartypistry) ) # If this parameter was passed... if __beartype_pith_0 is not __beartypistry: # Type-check this passed parameter or return value against this # PEP-compliant type hint. if not ( # True only if this pith shallowly satisfies this hint. isinstance(__beartype_pith_0, list) and # True only if either this pith is empty *OR* this pith is # both non-empty and deeply satisfies this hint. (not __beartype_pith_0 or isinstance(__beartype_pith_0[__beartype_random_int % len(__beartype_pith_0)], str)) ): __beartype_get_beartype_violation( func=__beartype_func, pith_name='kill_everything_that_thou_canst', pith_value=__beartype_pith_0, ) # Call this function with all passed parameters and return the value # returned from this call. return __beartype_func(*args, **kwargs) We have now diverged from normalcy. Let's dismantle this iota by iota: * ``__beartype_random_int`` is a pseudo-random unsigned 32-bit integer whose bit length intentionally corresponds to the `number of bits generated by each call to Python's C-based Mersenne Twister `__ internally performed by the :func:`random.getrandbits` function generating this integer. Exceeding this length would cause that function to internally perform that call multiple times for no gain. Since the cost of generating integers to this length is the same as generating integers of smaller lengths, this length is preferred. Since most sequences are likely to contain fewer items than this integer, pseudo-random sequence items are indexable by taking the modulo of this integer with the sizes of those sequences. For big sequences containing more than this number of items, beartype deeply type-checks leading items with indices in this range while ignoring trailing items. Given the practical infeasibility of storing big sequences in memory, this seems an acceptable real-world tradeoff. Suck it, big sequences! * As before, :func:`beartype.beartype` first type-checks this parameter to be a list. * :func:`beartype.beartype` then type-checks this parameter to either be: * ``not __beartype_pith_0``, an empty list. * ``isinstance(__beartype_pith_0[__beartype_random_int % len(__beartype_pith_0)], str)``, a non-empty list whose pseudo-randomly indexed list item satisfies this nested builtin type. Well, that escalated quickly. Constant Nested Deep Sequence Decoration **************************************** Let's define a trivial function annotated by type hints that are :pep:`585`\ -compliant builtin types recursively subscripted by instances of themselves, because *we are typing masochists*: .. code-block:: python from beartype import beartype @beartype def law_of_the_jungle_8(pull_thorns_from_all_wolves_paws: ( list[list[list[str]]])): return pull_thorns_from_all_wolves_paws Let's see the wrapper function :func:`beartype.beartype` dynamically generated from that: .. code-block:: python def law_of_the_jungle_8( *args, __beartype_func=__beartype_func, __beartypistry=__beartypistry, **kwargs ): # Generate and localize a sufficiently large pseudo-random integer for # subsequent indexation in type-checking randomly selected container items. __beartype_random_int = __beartype_getrandbits(32) # Localize the number of passed positional arguments for efficiency. __beartype_args_len = len(args) # Localize this positional or keyword parameter if passed *OR* to the # sentinel value "__beartypistry" guaranteed to never be passed otherwise. __beartype_pith_0 = ( args[0] if __beartype_args_len > 0 else kwargs.get('pull_thorns_from_all_wolves_paws', __beartypistry) ) # If this parameter was passed... if __beartype_pith_0 is not __beartypistry: # Type-check this passed parameter or return value against this # PEP-compliant type hint. if not ( # True only if this pith shallowly satisfies this hint. isinstance(__beartype_pith_0, list) and # True only if either this pith is empty *OR* this pith is # both non-empty and deeply satisfies this hint. (not __beartype_pith_0 or ( # True only if this pith shallowly satisfies this hint. isinstance(__beartype_pith_1 := __beartype_pith_0[__beartype_random_int % len(__beartype_pith_0)], list) and # True only if either this pith is empty *OR* this pith is # both non-empty and deeply satisfies this hint. (not __beartype_pith_1 or ( # True only if this pith shallowly satisfies this hint. isinstance(__beartype_pith_2 := __beartype_pith_1[__beartype_random_int % len(__beartype_pith_1)], list) and # True only if either this pith is empty *OR* this pith is # both non-empty and deeply satisfies this hint. (not __beartype_pith_2 or isinstance(__beartype_pith_2[__beartype_random_int % len(__beartype_pith_2)], str)) )) )) ): __beartype_get_beartype_violation( func=__beartype_func, pith_name='pull_thorns_from_all_wolves_paws', pith_value=__beartype_pith_0, ) # Call this function with all passed parameters and return the value # returned from this call. return __beartype_func(*args, **kwargs) We are now well beyond the deep end, where the benthic zone and the cruel denizens of the fathomless void begins. Let's dismantle this pascal by pascal: * ``__beartype_pith_1 := __beartype_pith_0[__beartype_random_int % len(__beartype_pith_0)]``, a :pep:`572`\ -style assignment expression localizing repeatedly accessed random items of the first nested list for efficiency. * ``__beartype_pith_2 := __beartype_pith_1[__beartype_random_int % len(__beartype_pith_1)]``, a similar expression localizing repeatedly accessed random items of the second nested list. * The same ``__beartype_random_int`` pseudo-randomly indexes all three lists. * Under older Python interpreters lacking :pep:`572` support, :func:`beartype.beartype` generates equally valid (albeit less efficient) code repeating each nested list item access. In the kingdom of the linear-time runtime type checkers, the constant-time runtime type checker really stands out like a sore giant squid, doesn't it? See the next section for further commentary on runtime optimization from the higher-level perspective of architecture and internal API design. Surely, it is fun. ################################# Beartype Dev Handbook: It's Handy ################################# Let's contribute `pull requests `__ to beartype for the good of typing_. The `primary maintainer of this repository is a friendly, bald, and bearded Canadian guy `__ who guarantees that he will *always* be nice and congenial and promptly merge *most* requests that pass continuous integration (CI) tests. And thanks for merely reading this! Like all open-source software, beartype thrives on community contributions, activity, and interest. *This means you, stalwart Python hero.* Beartype has `two problem spots (listed below in order of decreasing importance and increasing complexity) `__ that could *always* benefit from a volunteer army of good GitHub Samaritans. ************ Dev Workflow ************ Let's take this from the top. #. Create a `GitHub user account `__. #. Login to `GitHub with that account `__. #. **Click the "Fork" button** in the upper right-hand corner of `the "beartype/beartype" repository page `__. #. **Click the "Code" button** in the upper right-hand corner of your fork page that appears. #. **Copy the URL** that appears. #. **Open a terminal.** #. **Change to the desired parent directory** of your local fork. #. **Clone your fork,** replacing ``{URL}`` with the previously copied URL. .. code-block:: bash git clone {URL} #. **Add a new remote** referring to this upstream repository. .. code-block:: bash git remote add upstream https://github.com/beartype/beartype.git #. **Uninstall all previously installed versions** of beartype. For example, if you previously installed beartype with ``pip``, manually uninstall beartype with ``pip``. .. code-block:: bash pip uninstall beartype #. Install beartype with ``pip`` in **editable mode.** This synchronizes changes made to your fork against the beartype package imported in Python. Note the ``[dev]`` extra installs developer-specific mandatory dependencies required at test or documentation time. .. code-block:: bash pip3 install -e .[dev] #. **Create a new branch** to isolate changes to, replacing ``{branch_name}`` with the desired name. .. code-block:: bash git checkout -b {branch_name} #. **Make changes to this branch** in your favourite `Integrated Development Environment (IDE) `__. Of course, this means Vim_. #. **Test these changes.** Note this command assumes you have installed *all* :ref:`major versions of both CPython and PyPy supported by the next stable release of beartype you are hacking on `. If this is *not* the case, install these versions with pyenv_. This is vital, as type hinting support varies significantly between major versions of different Python interpreters. .. code-block:: bash ./tox The resulting output should ideally be suffixed by a synopsis resembling: :: ________________________________ summary _______________________________ py36: commands succeeded py37: commands succeeded py38: commands succeeded py39: commands succeeded pypy36: commands succeeded pypy37: commands succeeded congratulations :) #. **Stage these changes.** .. code-block:: bash git add -a #. **Commit these changes.** .. code-block:: bash git commit #. **Push these changes** to your remote fork. .. code-block:: bash git push #. **Click the "Create pull request" button** in the upper right-hand corner of your fork page. #. Afterward, **routinely pull upstream changes** to avoid desynchronization with `the "beartype/beartype" repository `__. .. code-block:: bash git checkout main && git pull upstream main ********** Moar Depth ********** .. caution:: **This section is badly outdated.** It's bad. *Real* bad. If you'd like us to revise this to actually reflect reality, just `drop us a line at our issue tracker `__. `@leycec`_ promises satisfaction. So, you want to help beartype deeply type-check even *more* type hints than she already does? Let us help you help us, because you are awesome. First, an egregious lore dump. It's commonly assumed that beartype only internally implements a single type-checker. After all, every *other* static and runtime type-checker only internally implements a single type-checker. Why would a type-checker internally implement several divergent overlapping type-checkers and... what would that even mean? Who would be so vile, cruel, and sadistic as to do something like that? *We would.* Beartype often violates assumptions. This is no exception. Externally, of course, beartype presents itself as a single type-checker. Internally, beartype is implemented as a two-phase series of orthogonal type-checkers. Why? Because efficiency, which is the reason we are all here. These type-checkers are (in the order that callables decorated by beartype perform them at runtime): #. **Testing phase.** In this fast first pass, each callable decorated by :func:`beartype.beartype` only *tests* whether all parameters passed to and values returned from the current call to that callable satisfy all type hints annotating that callable. This phase does *not* raise human-readable exceptions (in the event that one or more parameters or return values fails to satisfy these hints). :func:`beartype.beartype` highly optimizes this phase by dynamically generating one wrapper function wrapping each decorated callable with unique pure-Python performing these tests in O(1) constant-time. This phase is *always* unconditionally performed by code dynamically generated and returned by: * The fast-as-lightning ``pep_code_check_hint()`` function declared in the `"beartype._decor._code._pep._pephint" submodule `__, which generates memoized O(1) code type-checking an arbitrary object against an arbitrary PEP-compliant type hint by iterating over all child hints nested in that hint with a highly optimized breadth-first search (BFS) leveraging extreme caching, fragile cleverness, and other salacious micro-optimizations. #. **Error phase.** In this slow second pass, each call to a callable decorated by :func:`beartype.beartype` that fails the fast first pass (due to one or more parameters or return values failing to satisfy these hints) recursively discovers the exact underlying cause of that failure and raises a human-readable exception precisely detailing that cause. :func:`beartype.beartype` does *not* optimize this phase whatsoever. Whereas the implementation of the first phase is uniquely specific to each decorated callable and constrained to O(1) constant-time non-recursive operation, the implementation of the second phase is generically shared between all decorated callables and generalized to O(n) linear-time recursive operation. Efficiency no longer matters when you're raising exceptions. Exception handling is slow in any language and doubly slow in `dynamically-typed`_ (and mostly interpreted) languages like Python, which means that performance is mostly a non-concern in "cold" code paths guaranteed to raise exceptions. This phase is only *conditionally* performed when the first phase fails by: * The slow-as-molasses ``get_beartype_violation()`` function declared in the `"beartype._decor._error.errormain" submodule `__, which generates human-readable exceptions after performing unmemoized O(n) type-checking of an arbitrary object against a PEP-compliant type hint by recursing over all child hints nested in that hint with an unoptimized recursive algorithm prioritizing debuggability, readability, and maintainability. This separation of concerns between performant :math:`O(1)` *testing* on the one hand and perfect :math:`O(n)` *error handling* on the other preserves both runtime performance and readable errors at a cost of developer pain. This is good! :sup:`...what?` Secondly, the same separation of concerns also complicates the development of :func:`beartype.beartype`. This is bad. Since :func:`beartype.beartype` internally implements two divergent type-checkers, deeply type-checking a new category of type hint requires adding that support to (wait for it) two divergent type-checkers – which, being fundamentally distinct codebases sharing little code in common, requires violating the `Don't Repeat Yourself (DRY) principle `__ by reinventing the wheel in the second type-checker. Such is the high price of high-octane performance. You probably thought this would be easier and funner. So did we. Thirdly, this needs to be tested. After surmounting the above roadblocks by deeply type-checking that new category of type hint in *both* type-checkers, you'll now add one or more unit tests exhaustively exercising that checking. Thankfully, we already did all of the swole lifting for you. All *you* need to do is add at least one PEP-compliant type hint, one object satisfying that hint, and one object *not* satisfying that hint to: * A new ``PepHintMetadata`` object in the existing tuple passed to the ``data_module.HINTS_PEP_META.extend(...)`` call in the existing test data submodule for this PEP residing under the `"beartype_test.unit.data.hint.pep.proposal" subpackage `__. For example, if this is a :pep:`484`\ -compliant type hint, add that hint and associated metadata to the `"beartype_test.unit.data.hint.pep.proposal.data_hintpep484" submodule `__. You're done! *Praise Guido.* *************** Moar Compliance *************** So, you want to help beartype comply with even *more* :pep:`Python Enhancement Proposals (PEPs) <0>` than she already complies with? Let us help you help us, because you are young and idealistic and you mean well. You will need a spare life to squander. A clone would be most handy. In short, you will want to at least: * Define a new utility submodule for this PEP residing under the `"beartype._util.hint.pep.proposal" subpackage `__ implementing general-purpose validators, testers, getters, and other ancillary utility functions required to detect and handle *all* type hints compliant with this PEP. For efficiency, utility functions performing iteration or other expensive operations should be memoized via our internal `@callable_cached`_ decorator. * Define a new data utility submodule for this PEP residing under the `"beartype._util.data.hint.pep.proposal" subpackage `__ adding various signs (i.e., arbitrary objects uniquely identifying type hints compliant with this PEP) to various global variables defined by the parent `"beartype._util.data.hint.pep.utilhintdatapep" submodule <_beartype util data pep parent>`__. * Define a new test data submodule for this PEP residing under the `"beartype_test.unit.data.hint.pep.proposal" subpackage `__. You're probably not done by a long shot! But the above should at least get you fitfully started, though long will you curse our names. *Praise Cleese.* beartype-0.18.5/doc/src/conf.py000066400000000000000000000733411461113517100162630ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. # # --------------------( SYNOPSIS )-------------------- # File configuring this project for documentation by Sphinx. # # --------------------( SEE ALSO )-------------------- # * https://www.sphinx-doc.org/en/master/usage/configuration.html # List of all options supported in this file. # ....................{ TODO }.................... #FIXME: [ICON] Define the "html_favicon" setting as well -- once we actually #create a favicon, of course. *sigh* #FIXME: [THEME] Consider generalizing our theme from the somewhat low-level #"pydata-sphinx-theme" theme to the substantially higher-level #"sphinx-book-theme". The latter is built in terms of the former, but layers on #additional functionality for interactive Jupyter BinderHub-based cell content. #That would be just fantastic, because we have so *UTTERLY* much "code-block" #content distributed across our documentation; rendering that as interactive #content that users could actually run would be a *HUGE* win. See also: # https://github.com/executablebooks/sphinx-book-theme #FIXME: [EXTENSION] Add "sphinx-notfound-page" support to enable us to provide #a sane 404 page for non-existent pages. In fact, we already appear to have a #"404.rst" document. Well, isn't that noice. Because we can't be bothered to #configure this at the moment, note that: #* We've temporarily moved "404.rst" into the parent subdirectory, where it # has absolutely no effect (but at least does *NOT* induce fatal errors). #* We'll need to move "404.rst" back into this subdirectory first. # ....................{ IMPORTS ~ path }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: Avoid importing from *ANY* package or module other than those # provided by Python's standard library. Since Python's import path (i.e., # "sys.path") has yet to be properly established, imports from this project in # particular are likely to fail under common edge cases. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Preliminary imports from Python's standard library required to establish the # directory structure for this project. import sys from pathlib import Path # ....................{ PRIVATE ~ path }.................... # High-level "Path" object encapsulating the absolute dirname of the directory # containing the current Sphinx configuration file. # # Note that the comparable Path.absolute() method was neither documented nor # tested before Python 3.11. See also this relevant StackOverflow answer: # https://stackoverflow.com/a/44569249/2809027 # Dismantled, this is: # # * ".resolve(...)", creating and returning a new high-level "Path" object # canonicalizing the relative dirname with which the original "Path" object # was instantiated into an absolute dirname. # * "strict=True", raising a "FileNotFoundError" if this directory does *NOT* # exist. _DOC_SRC_DIR = Path(__file__).parent.resolve(strict=True) # High-level "Path" object encapsulating the absolute dirname of this project's # root directory, containing: # * The main directory implementing this project's Python package. # * The test directory implementing this project's pytest test suite. # * The documentation directory containing this configuration script. # * The ".git/" subdirectory if this is the live GitHub-based version. _ROOT_DIR = Path(_DOC_SRC_DIR / '../../').resolve(strict=True) # Expose this project's top-level package directory to Python and thus Sphinx. # Note that this is effectively required to avoid common edge cases. See also: # * https://samnicholls.net/2016/06/15/how-to-sphinx-readthedocs # The "Make autodoc actually work" is the canonical writeup on this kludge. sys.path.insert(0, str(_ROOT_DIR)) # print(f'sys.path (from "doc/source/conf.py"): {sys.path}') # ....................{ IMPORTS }.................... # Sphinx defaults to hardcoding version specifiers. Since this is insane, we # import our package-specific version specifier for reuse below. See also: # * https://protips.readthedocs.io/git-tag-version.html # "Inferring Release Number from Git Tags", detailing a clever one-liner # harvesting this specifier from the most recent git tag. from beartype.meta import ( AUTHORS, COPYRIGHT, NAME, SPHINX_THEME_NAME, URL_CONDA, URL_LIBRARIES, URL_PYPI, URL_RTD, URL_REPO, URL_REPO_ORG_NAME, URL_REPO_BASENAME, VERSION, ) from beartype.typing import Optional from beartype._util.module.utilmodimport import import_module_attr from beartype._util.module.utilmodtest import is_module # from warnings import warn # ....................{ PRIVATE ~ path : more }.................... # High-level "Path" object encapsulating the absolute dirname of this project's # top-level package directory, raising a "FileNotFoundError" if this directory # does *NOT* exist. # # Note that this requires the "sys.path" global to be properly established above # and *MUST* thus be deferred until after that. _PACKAGE_DIR = (_ROOT_DIR / NAME).resolve(strict=True) # Relative filename of our URI repository (i.e., hidden reStructuredText (reST) # document defining common URI links in reST format, exposed to all other reST # documents via the "rst_epilog" setting below). _LINKS_FILENAME = str((_DOC_SRC_DIR / '_links.rst').resolve(strict=True)) # ....................{ CONSTANTS ~ sphinx }.................... # Sphinx-specific metadata programmatically published by this package. project = NAME author = AUTHORS copyright = COPYRIGHT release = VERSION version = VERSION # ....................{ SETTINGS }.................... # List of zero or more Sphinx-specific warning categories to be squelched (i.e., # suppressed, ignored). suppress_warnings = [ #FIXME: *THIS IS TERRIBLE.* Generally speaking, we do want Sphinx to inform #us about cross-referencing failures. Remove this hack entirely after Sphinx #resolves this open issue: https://github.com/sphinx-doc/sphinx/issues/4961 # Squelch mostly ignorable warnings resembling: # WARNING: more than one target found for cross-reference 'TypeHint': # beartype.door._doorcls.TypeHint, beartype.door.TypeHint # # Sphinx currently emits *HUNDREDS* of these warnings against our # documentation. All of these warnings appear to be ignorable. Although we # could explicitly squelch *SOME* of these warnings by canonicalizing # relative to absolute references in docstrings, Sphinx emits still others # of these warnings when parsing PEP-compliant type hints via static # analysis. Since those hints are actual hints that *CANNOT* by definition # by canonicalized, our only recourse is to squelch warnings altogether. 'ref.python', ] # ....................{ SETTINGS ~ path }.................... # List of pathname patterns relative to this "doc/src/" subdirectory matching # all files and directories to be ignored when finding source files. Note this # list also affects the "html_static_path" and "html_extra_path" settings. # # Note that this global obsoletes the prior "unused_doc", which served a similar # goal -- albeit in a less general-purpose manner. exclude_patterns = [ # Ignore our URI repository, which magical logic below dynamically includes # on all pages through the classic "rst_epilog" trick. '_links.rst', ] # ....................{ SETTINGS ~ rst }.................... # String of arbitrary reStructuredText (reST) to be implicitly prepended to the # contents of *ALL* reST documents rendered by this configuration, initialized # to the empty string for safety. rst_prolog = ''' .. tip:: `Feed the bear! The bear is rooting around in your refuse pile `__. You feel sadness. ''' # `Feed the bear `__! `Animals wearing jewelry # `__! *What is even going on with this banner!?* # String of arbitrary reStructuredText (reST) to be implicitly appended to the # contents of *ALL* reST documents rendered by this configuration, initialized # to the empty string for safety. rst_epilog = '' # Append the contents of our URI repository file to this string, exposing the # common URI links centralized in this file to all other reST documents. # # See also this StackOverflow answer strongly inspiring this implementation: # https://stackoverflow.com/a/61694897/2809027 with open(_LINKS_FILENAME, encoding='utf-8') as links_file: rst_epilog += links_file.read() # print(f'rst_epilog: {rst_epilog}') # ....................{ EXTENSIONS ~ mandatory }.................... # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ # ..................{ BUILTIN }.................. # Builtin extensions unconditionally available under *ALL* reasonably # modern versions of Sphinx uniquely prefixed by "sphinx.ext.". #FIXME: Uncomment when enabling "autoapi" support, please. *sigh* # # Builtin extension enabling support for type hint introspection in API # # generation extensions (e.g., "autoapi", "autodoc"). # # # # The third-party "autoapi" extension implicitly requires this extension to # # be enabled as a prerequisite for its own type hint introspection. We # # intentionally enable this extension despite *NOT* using "autodoc". # 'sphinx.ext.autodoc', # Builtin extension automatically creating one explicit globally # cross-referenceable target "{/document/basename}:{section_title}" for each # section titled "{section_title}" of each document residing at # "{/document/basename}". By default, Sphinx requires targets to be manually # prepended before all sections to be cross-referenced elsewhere. *facepalm* 'sphinx.ext.autosectionlabel', # Builtin extension enabling attributes defined by the standard library # (e.g., the "typing" module, the "types.GenericAlias" type) to be # cross-referenced as a fallback when *NOT* already defined by this project. "sphinx.ext.intersphinx", # Builtin extension autogenerating reStructuredText documentation from # class, callable, and variable docstrings embedded in Python modules # formatted according to either NumPy or Google style. # # Note this effectively requires 'sphinx.ext.autodoc' to be listed as well. 'sphinx.ext.napoleon', # Builtin extension rendering "math::" directives into HTML-only JavaScript # via the third-party MathJax library. 'sphinx.ext.mathjax', # Builtin extension autogenerating reStructuredText documentation listing # each of this project's Python modules and linking external references to # those modules to these listings. 'sphinx.ext.viewcode', # ..................{ THIRD-PARTY }.................. # Third-party Sphinx extensions required to be externally installed. For # usability, this block should typically be empty. Third-party Sphinx # extensions should ideally be optionally enabled. See below. # Third-party "autoapi" Sphinx extension actively maintained by Read The # Docs (RTD) and an alternative to the builtin "autodoc" Sphinx extension. # "autoapi" is strongly preferable to "autodoc" in the modern context, as: # * "autodoc" dynamically imports *ALL* Python modules to be documented and # thus executes *ALL* module-scoped code in those modules at documentation # time, significantly complicating documentation time in the event of # module-scoped code *NOT* expected to be executed at documentation time. # Conversely, "autoapi" simply statically parses the same modules and thus # entirely circumvents the arbitrary code execution "pain point." # * "autosummary" (another builtin Sphinx extension typically used to # conjunction with "autodoc" to generate table of contents (TOC) entries) # defaults to a silent noop; even when configured to generate non-empty # content, however, "autosummary" fails to document class- or # module-scoped attributes due to long-standing Sphinx limitations. # Conversely, "autoapi" generates usable TOC entries out-of-the-box with # *NO* configuration or kludges required. #FIXME: Temporarily disabled in a pell-mell rush to host our "README.rst" #file on RTD. Once we finish that laborious process, let's revisit #"autoapi". Doing so will prove non-trivial, as "autoapi" currently emits #~300 warnings -- some of which are ignorable, but most of which are not. # 'autoapi.extension', ] # ....................{ EXTENSIONS ~ optional }.................... # Third-party Sphinx extensions conditionally used if externally installed. # ....................{ EXTENSIONS ~ optional : theme }.................... # Fully-qualified name of the package providing the third-party Sphinx extension # defining the custom HTML theme preferred by this documentation, globally # substituting hyphens with underscores to produce a valid Python identifier. _SPHINX_THEME_MODULE_NAME = SPHINX_THEME_NAME.replace('-', '_') # If this package is importable under the active Python interpreter, enable this # theme for improved HTML rendering. if is_module(_SPHINX_THEME_MODULE_NAME): # Set the HTML theme to this package. # # Note that we do *NOT* do this, which non-theme extensions require: # # Register the fully-qualified name of this extension. # extensions.append(SPHINX_THEME_NAME) # # Why? Because doing so induces this exception from modern themes like Furo # and PyData: # Extension error (furo): # Handler for event # 'builder-inited' threw an exception (exception: Did you list 'furo' in # the `extensions` in conf.py? If so, please remove it. Furo does not # work with non-HTML builders and specifying it as an `html_theme` is # sufficient.) html_theme = _SPHINX_THEME_MODULE_NAME #FIXME: *KLUDGE WARNING.* Revert this to just the following one-liner *AFTER* #successfully upgrading to the most recent stable release of the PyData theme: # # Add any paths that contain templates here, relative to this directory. # templates_path = ['_templates'] # String version of the currently installed version of this theme. _SPHINX_THEME_MODULE_VERSION = import_module_attr( f'{_SPHINX_THEME_MODULE_NAME}.__version__') # If this version is that of a version known to supply the requisite Jinja2 # functionality required by project-specific templates... if _SPHINX_THEME_MODULE_VERSION == '0.7.2': # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # Else, silently ignore these templates. Attempting to use this templates # under any other version of this theme is likely to raise exceptions: e.g., # Theme error: # An error happened in rendering the page api. # Reason: UndefinedError("'generate_nav_html' is undefined") # Else, this theme is unavailable. In this case, fallback to Sphinx's default # HTML theme *AND*... else: #FIXME: Convert this back into a warning by calling warn() *AFTER* deciding #how to do so safely. The core issue is that we convert warnings into #failures during testing; ergo, we need to install the Python package #providing this theme during testing. We can't be bothered at the moment. # Emit a non-fatal warning informing end users of this fallback. print( ( f'WARNING: Optional Sphinx extension "{SPHINX_THEME_NAME}" ' f'not found; falling back to default Sphinx HTML theme.' ), ) # ....................{ EXTENSIONS ~ optional : non-theme }.................... #FIXME: Restore @beartype once working. For unknown reasons, ReadTheDocs (RTD) #fails with non-human-readable exceptions *REMOTELY* when we attempt to decorate #a function defined in this file by @beartype. We have tried in vain to #replicate this locally (e.g., in our test_sphinx_docs_other() functional test). #Until we can, attempting to resolve this is mostly an exercise in futility. #FIXME: Actually, that test does now replicate this issue. Let's investigate #further via that test when time permits, please. #@beartype def _register_extension_or_warn( # Mandatory parameters. extension_name: str, # Optional parameters. warning_message: Optional[str] = None, ) -> None: ''' Register the extension with the passed package name if that package is importable under the active Python interpreter *or* print a non-fatal warning otherwise (i.e., if that package is unimportable). Parameters ---------- extension_name : str Fully-qualified name of the package providing this extension. warning_message : Optional[str] Human-readable message to be printed when this package is unimportable. Defaults to :data:`None`, in which case a standard message is printed. ''' # If this package is importable under the active Python interpreter, append # the name of this package to the list of all Sphinx extensions to enable. if is_module(extension_name): extensions.append(extension_name) # Else, this package is unimportable. In this case, print the passed # non-fatal warning message. else: # Substring unconditionally prefixing this warning message. WARNING_MESSAGE_PREFIX = ( f'WARNING: Optional Sphinx extension "{extension_name}" not found.') # Replace this warning message with either... warning_message = ( # If the caller passed a warning message, this message prefixed by # this substring; f'{WARNING_MESSAGE_PREFIX} {warning_message}' # Else, this prefix as is. if warning_message else WARNING_MESSAGE_PREFIX ) # Print this warning message. print(warning_message) #FIXME: Uncomment *AFTER* this extension resolves this currently open issue: # https://github.com/wpilibsuite/sphinxext-opengraph/issues/98 # # Register the third-party "sphinxext-opengraph" extension if available. This # # extension autogenerates Open Graph metadata describing this documentation. # # Social media and search engine giants commonly consume this metadata to # # optimize both the ranking and presentation of links to this documentation. # _register_extension_or_warn('sphinxext.opengraph') # ....................{ EXTENSIONS ~ autodoc }.................... # "autoapi.extension"-specific settings. See also: # https://sphinx-autoapi.readthedocs.io/en/latest/reference/config.html # Machine-readable string identifying the software language to be documented. autoapi_type = 'python' # List of the relative or absolute dirnames of all input top-level package # directories to be recursively documented for this project. autoapi_dirs = [str(_PACKAGE_DIR)] # Relative dirname of the output subdirectory to add generated API documentation # files, relative to the subdirectory containing this file (i.e., "doc/src/"). autoapi_root = 'api' # Instruct "autoapi" to... autoapi_options = [ # Document public documented attributes of modules and classes. 'members', # Document public undocumented attributes of modules and classes. *gulp* 'undoc-members', # Document dunder attributes of modules and classes. Although often # ignorable for documentation purposes, dunder attributes can be documented # with non-ignorable docstrings intended to be exposed as documentation. # This includes the public "beartype.door.TypeHint" class, whose # well-documented dunder methods benefit from exposure to users. 'special-members', # List all superclasses of classes. 'show-inheritance', # Include "autosummary" directives in generated module documentation. 'show-module-summary', # List attributes imported from the same package. 'imported-members', ] #FIXME: Uncomment as needed to debug local "autoapi" issues. # autoapi_keep_files = True #FIXME: Consider customizing "autoapi" templates at some point. For now, the #defaults suffice. See also this useful article on the subject: # https://bylr.info/articles/2022/05/10/api-doc-with-sphinx-autoapi/#setting-up-templates #See also the official How-To at: # https://sphinx-autoapi.readthedocs.io/en/latest/how_to.html#how-to-customise-layout-through-templates # ....................{ EXTENSIONS ~ autodoc }.................... # "sphinx.ext.autodoc"-specific settings. See also: # https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html # Instruct API generation extensions (e.g., "autoapi", "autodoc") to globally # append type hints annotating callable signatures to the parameters and returns # they annotate in the content (i.e., descriptions) of those callables. Note # this requires Sphinx >= 4.1. # # Since the third-party "autoapi" extension implicitly supports this setting, we # intentionally define this setting despite *NOT* using "autodoc". autodoc_typehints = 'both' # ....................{ EXTENSIONS ~ autosectionlabel }.................... # 'sphinx.ext.autosectionlabel'-specific settings. See also: # https://www.sphinx-doc.org/en/master/usage/extensions/autosectionlabel.html # Instruct "autosectionlabel" to uniquify created targets by prefixing section # titles with document pathnames in these targets. By default, this extension # ambiguously creates targets as section titles; that simplistic scheme fails # when two or more documents share the same section titles, a common use case # that's effectively infeasible to prohibit. autosectionlabel_prefix_document = True # ....................{ EXTENSIONS ~ intersphinx }.................... # 'sphinx.ext.intersphinx'-specific settings. See also: # https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html # Dictionary mapping from the machine-readable name of each external project to # search for references to otherwise missing references in reStructuredText # (reST) documentation as a graceful fallback to a 2-tuple "(URI, inventory)", # where "inventory" is typically ignorable and thus "None" for our purposes. # # Note that: # * The keys of this dictionary may be used to unambiguously reference # attributes of that external project in reST documentation: e.g., # # External link to Python's Comparison Manual. # external:python+ref:`comparison manual ` # * The contents of this dictionary are mostly derived from this popular # well-maintained Gist on the subject: # https://gist.github.com/bskinn/0e164963428d4b51017cebdb6cda5209 intersphinx_mapping = { 'python': ('https://docs.python.org/3', None), 'numpy': ('https://numpy.org/doc/stable', None), 'pandas': ('https://pandas.pydata.org/docs', None), 'scipy': ('https://docs.scipy.org/doc/scipy', None), } # ....................{ EXTENSIONS ~ napoleon }.................... # 'sphinx.ext.napoleon'-specific settings. See also: # https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html # Force Napolean to *ONLY* parse docstrings in the NumPy format used by this # project. By default, Napolean attempts and often fails to permissively parse # docstrings in both Google and NumPy formats. napoleon_numpy_docstring = True napoleon_google_docstring = False # List of the names of all non-standard section headers (i.e., headers *NOT* # already supported out-of-the-box by Napoleon) embedded in docstrings # throughout this project. By default, Napolean silently ignores *ALL* content # in non-standard section headers. # # See also: # * This list of all standard section headers: # https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html#docstring-sections napoleon_custom_sections = [ 'Caveats', 'Design', 'Motivation', 'Usage', ] #FIXME: Experiment with enabling these non-default settings as well. # napoleon_use_param = False # napoleon_use_ivar = True # ....................{ EXTENSIONS ~ pygments }.................... #FIXME: Uncomment as desired. Let's see how the defaults do first, please. # # Pygments style. # pygments_style = "autumn" # pygments_dark_style = "monokai" # ....................{ LANG ~ python }.................... # Wrap Python callable and class signatures exceeding this maximum number of # plaintext characters such that each parameter of those signatures is then # delegated its own discrete line. python_maximum_signature_line_length = 80 # ....................{ BUILD ~ html }.................... # Relative filename or URL of a small image (i.e., no wider than 200px) to be # rendered in the upper left-hand corner of the sidebar for this theme. html_logo = 'https://raw.githubusercontent.com/beartype/beartype-assets/main/badge/bear-ified.svg' # 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; # ergo, a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Dictionary mapping from the names of theme-specific options to those options. html_context = { # ....................{ PYDATA }.................... # HTML options specific to the "pydata-sphinx-theme" theme. # Default the current theme to dark rather than light. Embrace the darkness! 'default_mode': 'dark', # ....................{ PYDATA ~ edit }.................... # HTML options supporting the "use_edit_page_button" setting enabled below. 'github_user': URL_REPO_ORG_NAME, 'github_repo': URL_REPO_BASENAME, 'github_version': 'main', 'doc_path': 'doc/src', } # Dictionary mapping from the names of theme-specific options to those options. # We have *NO* idea why Sphinx supports both this and the parallel # "html_context" dictionary -- but it does. (Just pretend this isn't happening.) html_theme_options = { # ....................{ PYDATA }.................... # HTML options specific to the "pydata-sphinx-theme" theme. #FIXME: Add favicon support here, please. See also: # https://pydata-sphinx-theme.readthedocs.io/en/v0.7.2/user_guide/configuring.html#adding-favicons # Announcement banner defined as a string of arbitrary HTML, temporarily # displayed at the top of each page until the user begins scrolling. 'announcement': ( '

' 'Feed the bear! ' 'Animals wearing jewelry! ' 'What is even going on with this banner!?' '

' ), # List of one or more icon link descriptions. See also: # * Official theme-specific documentation for this setting: # https://pydata-sphinx-theme.readthedocs.io/en/v0.7.2/user_guide/configuring.html#configure-icon-links # * Official search engine for icons published by the third-party # "FontAwesome 5 Free" project and supported by this setting: # https://fontawesome.com/icons?d=gallery&m=free # # Note that: # * This theme requires long-form FontAwesome styles (e.g., "fa-brand", # "fa-solid") to be abbreviated to these three-letter abbreviations: # * "fa-brand" -> "fab". # * "fa-regular" -> "far". # * "fa-solid" -> "fas". 'icon_links': [ { 'name': 'GitHub', 'url': URL_REPO, 'icon': 'fab fa-github-square', }, { 'name': 'PyPI', 'url': URL_PYPI, 'icon': 'fab fa-python', }, { 'name': 'Anaconda', 'url': URL_CONDA, 'icon': 'far fa-circle', }, { 'name': 'Libraries.io', 'url': URL_LIBRARIES, 'icon': 'fas fa-chart-area', }, { 'name': 'ReadTheDocs', 'url': URL_RTD, 'icon': 'fas fa-book', }, ], # Add an "Edit this Page" button to the secondary sidebar of each page, # enabling users to trivially submit an automated pull request (PR) politely # requesting modifications to the contents of that page. # # Note that enabling this also requires adding various URIs and paths to # the "html_context" global describing the remote host hosting this git # repository (e.g., GitHub). # # See also upstream documentation on this subject: # https://pydata-sphinx-theme.readthedocs.io/en/latest/user_guide/source-buttons.html 'use_edit_page_button': True, } #FIXME: This setting is currently undocumented but appears to internally govern #the "n_links_before_dropdown" parameter passed to the #generate_header_nav_html() function. Submit an issue requesting documentation! #FIXME: As expected, this is ignored. Until resolved, let's try temporarily #resolving this on our end by overriding the "navbar-nav.html" template. See: # https://github.com/pydata/pydata-sphinx-theme/blob/157c9ab2c93e755141e48d13b5f193c6169d9dd8/src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/navbar-nav.html#L8 # #Issue, please! # Increase the maximum number of links displayed by "pydata-sphinx-theme" in the # top-most navigation bar. "pydata-sphinx-theme" displays all remaining links in # a dropdown entitled "More" that, when clicked, vertically lists those links. # "pydata-sphinx-theme" defaults this global to 5, which is insufficient for the # larger number of links required for this documentation. theme_header_links_before_dropdown = 10 beartype-0.18.5/doc/src/eli5.rst000066400000000000000000000645031461113517100163540ustar00rootroot00000000000000.. # ------------------( LICENSE )------------------ .. # Copyright (c) 2014-2024 Beartype authors. .. # See "LICENSE" for further details. .. # .. # ------------------( SYNOPSIS )------------------ .. # Child reStructuredText (reST) document gently introducing this project. .. # ------------------( MAIN )------------------ .. _eli5:eli5: ############################ Explain Like I'm Five (ELI5) ############################ .. parsed-literal:: Look for the bare necessities, the simple bare necessities. Forget about your worries and your strife. — `The Jungle Book`_. Beartype is a novel first line of defense. In Python's vast arsenal of `software quality assurance (SQA) `__, beartype holds the `shield wall`_ against breaches in type safety by improper parameter and return values violating developer expectations. Beartype is unopinionated. Beartype inflicts *no* developer constraints beyond :ref:`importation and usage of a single configuration-free decorator `. Beartype is trivially integrated into new and existing applications, stacks, modules, and scripts already annotating callables with :ref:`PEP-compliant industry-standard type hints `. .. # ------------------( TABLES OF CONTENTS )------------------ .. # Table of contents, excluding the above document heading. While the .. # official reStructuredText documentation suggests that a language-specific .. # heading will automatically prepend this table, this does *NOT* appear to .. # be the case. Instead, this heading must be explicitly declared. .. contents:: **Bear with Us** :local: .. # ------------------( DESCRIPTION )------------------ ********** Comparison ********** Beartype is zero-cost. Beartype inflicts *no* harmful developer tradeoffs, instead stressing expense-free strategies at both: * **Installation time.** Beartype has no install-time or runtime dependencies, :ref:`supports standard Python package managers `, and happily coexists with competing static type-checkers and other runtime type-checkers... which, of course, is irrelevant, as you would never *dream* of installing competing alternatives. Why would you, right? Am I right? ```` * **Runtime.** Thanks to aggressive memoization and dynamic code generation at decoration time, beartype guarantees :ref:`O(1) non-amortized worst-case runtime complexity with negligible constant factors `. .. _eli5:static: ...versus Static Type-checkers ############################## Like :ref:`competing static type-checkers ` operating at the coarse-grained application level via ad-hoc heuristic type inference (e.g., Pyre_, mypy_, pyright_, pytype_), beartype effectively :ref:`imposes no runtime overhead `. Unlike static type-checkers: * Beartype operates exclusively at the fine-grained callable level of pure-Python functions and methods via the standard decorator design pattern. This renders beartype natively compatible with *all* interpreters and compilers targeting the Python language – including Brython_, PyPy_, Numba_, Nuitka_, and (wait for it) CPython_ itself. * Beartype enjoys deterministic Turing-complete access to the actual callables, objects, and types being type-checked. This enables beartype to solve dynamic problems decidable only at runtime – including type-checking of arbitrary objects whose: * Metaclasses `dynamically customize instance and subclass checks <_isinstancecheck>`__ by implementing the ``__instancecheck__()`` and/or ``__subclasscheck__()`` dunder methods, including: * :pep:`3119`\ -compliant metaclasses (e.g., :class:`abc.ABCMeta`). * Pseudo-superclasses `dynamically customize the method resolution order (MRO) of subclasses <_mro_entries>`__ by implementing the ``__mro_entries__()`` dunder method, including: * :pep:`560`\ -compliant pseudo-superclasses. * Classes dynamically register themselves with standard abstract base classes (ABCs), including: * :pep:`3119`\ -compliant third-party virtual base classes. * :pep:`3141`\ -compliant third-party virtual number classes (e.g., SymPy_). * Classes are dynamically constructed or altered, including by: * Class decorators. * Class factory functions and methods. * Metaclasses. * Monkey patches. ...versus Runtime Type-checkers ############################### Unlike :ref:`comparable runtime type-checkers ` (e.g., pydantic_, typeguard_), beartype decorates callables with dynamically generated wrappers efficiently type-checking each parameter passed to and value returned from those callables in constant time. Since "performance by default" is our first-class concern, generated wrappers are guaranteed to: * Exhibit :ref:`O(1) non-amortized worst-case time complexity with negligible constant factors `. * Be either more efficient (in the common case) or exactly as efficient minus the cost of an additional stack frame (in the worst case) as equivalent type-checking implemented by hand, *which no one should ever do.* ********** Quickstart ********** Beartype makes type-checking painless, portable, and purportedly fun. Just: Decorate functions and methods `annotated by standard type hints `__ with the :func:`beartype.beartype` decorator, which wraps those functions and methods in performant type-checking dynamically generated on-the-fly. When `standard type hints `__ fail to support your use case, annotate functions and methods with :mod:`beartype-specific validator type hints ` instead. Validators enforce runtime constraints on the internal structure and contents of parameters and returns via simple caller-defined lambda functions and declarative expressions – all seamlessly composable with `standard type hints `__ in an :ref:`expressive domain-specific language (DSL) ` designed just for you. "Embrace the bear," says the bear peering over your shoulder as you read this. .. _eli5:typing: Standard Hints ############## Beartype supports *most* :ref:`type hints standardized by the developer community through Python Enhancement Proposals (PEPs) `. Since type hinting is its own special hell, we'll start by wading into the thalassophobia-inducing waters of type-checking with a sane example – the :math:`O(1)` :func:`beartype.beartype` way. Toy Example *********** Let's type-check a ``"Hello, Jungle!"`` toy example. Just: #. Import the :func:`beartype.beartype` decorator: .. code-block:: python from beartype import beartype #. Decorate any annotated function with that decorator: .. code-block:: python from sys import stderr, stdout from typing import TextIO @beartype def hello_jungle( sep: str = ' ', end: str = '\n', file: TextIO = stdout, flush: bool = False, ): ''' Print "Hello, Jungle!" to a stream, or to sys.stdout by default. Optional keyword arguments: file: a file-like object (stream); defaults to the current sys.stdout. sep: string inserted between values, default a space. end: string appended after the last value, default a newline. flush: whether to forcibly flush the stream. ''' print('Hello, Jungle!', sep, end, file, flush) #. Call that function with valid parameters and caper as things work: .. code-block:: pycon >>> hello_jungle(sep='...ROOOAR!!!!', end='uhoh.', file=stderr, flush=True) Hello, Jungle! ...ROOOAR!!!! uhoh. #. Call that function with invalid parameters and cringe as things blow up with human-readable exceptions exhibiting the single cause of failure: .. code-block:: pycon >>> hello_jungle(sep=( ... b"What? Haven't you ever seen a byte-string separator before?")) BeartypeCallHintPepParamException: @beartyped hello_jungle() parameter sep=b"What? Haven't you ever seen a byte-string separator before?" violates type hint , as value b"What? Haven't you ever seen a byte-string separator before?" not str. Industrial Example ****************** Let's wrap the `third-party numpy.empty_like() function `__ with automated runtime type checking to demonstrate beartype's support for non-trivial combinations of nested type hints compliant with different PEPs: .. code-block:: python from beartype import beartype from collections.abc import Sequence from typing import Optional, Union import numpy as np @beartype def empty_like_bear( prototype: object, dtype: Optional[np.dtype] = None, order: str = 'K', subok: bool = True, shape: Optional[Union[int, Sequence[int]]] = None, ) -> np.ndarray: return np.empty_like(prototype, dtype, order, subok, shape) Note the non-trivial hint for the optional ``shape`` parameter, synthesized from a `PEP 484-compliant optional `__ of a `PEP 484-compliant union `__ of a builtin type and a `PEP 585-compliant subscripted abstract base class (ABC) `__, accepting as valid either: * The :data:`None` singleton. * An integer. * A sequence of integers. Let's call that wrapper with both valid and invalid parameters: .. code-block:: pycon >>> empty_like_bear(([1,2,3], [4,5,6]), shape=(2, 2)) array([[94447336794963, 0], [ 7, -1]]) >>> empty_like_bear(([1,2,3], [4,5,6]), shape=([2], [2])) BeartypeCallHintPepParamException: @beartyped empty_like_bear() parameter shape=([2], [2]) violates type hint typing.Union[int, collections.abc.Sequence, NoneType], as ([2], [2]): * Not or int. * Tuple item 0 value [2] not int. Note the human-readable message of the raised exception, containing a bulleted list enumerating the various ways this invalid parameter fails to satisfy its type hint, including the types and indices of the first container item failing to satisfy the nested ``Sequence[int]`` hint. ******** Tutorial ******** Let's begin with the simplest type of type-checking supported by :func:`beartype.beartype`. Builtin Types ############# **Builtin types** like :class:`dict`, :class:`int`, :class:`list`, :class:`set`, and :class:`str` are trivially type-checked by annotating parameters and return values with those types as is. Let's declare a simple beartyped function accepting a string and a dictionary and returning a tuple: .. code-block:: python from beartype import beartype @beartype def law_of_the_jungle(wolf: str, pack: dict) -> tuple: return (wolf, pack[wolf]) if wolf in pack else None Let's call that function with good types: .. code-block:: pycon >>> law_of_the_jungle(wolf='Akela', pack={'Akela': 'alone', 'Raksha': 'protection'}) ('Akela', 'alone') Good function. Let's call it again with bad types: .. code-block:: pycon >>> law_of_the_jungle(wolf='Akela', pack=['Akela', 'Raksha']) Traceback (most recent call last): File "", line 1, in law_of_the_jungle(wolf='Akela', pack=['Akela', 'Raksha']) File "", line 22, in __law_of_the_jungle_beartyped__ beartype.roar.BeartypeCallTypeParamException: @beartyped law_of_the_jungle() parameter pack=['Akela', 'Raksha'] not a . The :mod:`beartype.roar` submodule publishes exceptions raised at both decoration time by :func:`beartype.beartype` and at runtime by wrappers generated by :func:`beartype.beartype`. In this case, a runtime type exception describing the improperly typed ``pack`` parameter is raised. Good function! Let's call it again with good types exposing a critical issue in this function's implementation and/or return type annotation: .. code-block:: pycon >>> law_of_the_jungle(wolf='Leela', pack={'Akela': 'alone', 'Raksha': 'protection'}) Traceback (most recent call last): File "", line 1, in law_of_the_jungle(wolf='Leela', pack={'Akela': 'alone', 'Raksha': 'protection'}) File "", line 28, in __law_of_the_jungle_beartyped__ beartype.roar.BeartypeCallTypeReturnException: @beartyped law_of_the_jungle() return value None not a . *Bad function.* Let's conveniently resolve this by permitting this function to return either a tuple or :data:`None` as `detailed below `__: .. code-block:: pycon >>> from beartype.cave import NoneType >>> @beartype ... def law_of_the_jungle(wolf: str, pack: dict) -> (tuple, NoneType): ... return (wolf, pack[wolf]) if wolf in pack else None >>> law_of_the_jungle(wolf='Leela', pack={'Akela': 'alone', 'Raksha': 'protection'}) None The ``beartype.cave`` submodule publishes generic types suitable for use with the :func:`beartype.beartype` decorator and anywhere else you might need them. In this case, the type of the :data:`None` singleton is imported from this submodule and listed in addition to :class:`tuple` as an allowed return type from this function. Note that usage of the ``beartype.cave`` submodule is entirely optional (but more efficient and convenient than most alternatives). In this case, the type of the :data:`None` singleton can also be accessed directly as ``type(None)`` and listed in place of ``NoneType`` above: e.g., .. code-block:: pycon >>> @beartype ... def law_of_the_jungle(wolf: str, pack: dict) -> (tuple, type(None)): ... return (wolf, pack[wolf]) if wolf in pack else None >>> law_of_the_jungle(wolf='Leela', pack={'Akela': 'alone', 'Raksha': 'protection'}) None Of course, the ``beartype.cave`` submodule also publishes types *not* accessible directly like ``RegexCompiledType`` (i.e., the type of all compiled regular expressions). All else being equal, ``beartype.cave`` is preferable. Good function! The type hints applied to this function now accurately document this function's API. All's well that ends typed well. Suck it, `Shere Khan`_. Arbitrary Types ############### Everything above also extends to: * **Arbitrary types** like user-defined classes and stock classes in the Python stdlib (e.g., :class:`argparse.ArgumentParser`) – all of which are also trivially type-checked by annotating parameters and return values with those types. * **Arbitrary callables** like instance methods, class methods, static methods, and generator functions and methods – all of which are also trivially type-checked with the :func:`beartype.beartype` decorator. Let's declare a motley crew of beartyped callables doing various silly things in a strictly typed manner, *just 'cause*: .. code-block:: python from beartype import beartype from beartype.cave import GeneratorType, IterableType, NoneType @beartype class MaximsOfBaloo(object): def __init__(self, sayings: IterableType): self.sayings = sayings @beartype def inform_baloo(maxims: MaximsOfBaloo) -> GeneratorType: for saying in maxims.sayings: yield saying For genericity, the ``MaximsOfBaloo`` class initializer accepts *any* generic iterable (via the ``beartype.cave.IterableType`` tuple listing all valid iterable types) rather than an overly specific ``list`` or ``tuple`` type. Your users may thank you later. For specificity, the ``inform_baloo()`` generator function has been explicitly annotated to return a ``beartype.cave.GeneratorType`` (i.e., the type returned by functions and methods containing at least one ``yield`` statement). Type safety brings good fortune for the New Year. Let's iterate over that generator with good types: .. code-block:: pycon >>> maxims = MaximsOfBaloo(sayings={ ... '''If ye find that the Bullock can toss you, ... or the heavy-browed Sambhur can gore; ... Ye need not stop work to inform us: ... we knew it ten seasons before.''', ... '''“There is none like to me!” says the Cub ... in the pride of his earliest kill; ... But the jungle is large and the Cub he is small. ... Let him think and be still.''', ... }) >>> for maxim in inform_baloo(maxims): print(maxim.splitlines()[-1]) Let him think and be still. we knew it ten seasons before. Good generator. Let's call it again with bad types: .. code-block:: pycon >>> for maxim in inform_baloo([ ... 'Oppress not the cubs of the stranger,', ... ' but hail them as Sister and Brother,', ... ]): print(maxim.splitlines()[-1]) Traceback (most recent call last): File "", line 30, in ' but hail them as Sister and Brother,', File "", line 12, in __inform_baloo_beartyped__ beartype.roar.BeartypeCallTypeParamException: @beartyped inform_baloo() parameter maxims=['Oppress not the cubs of the stranger,', ' but hail them as Sister and ...'] not a . Good generator! The type hints applied to these callables now accurately document their respective APIs. Thanks to the pernicious magic of beartype, all ends typed well... *yet again.* .. _eli5:tuple union: Unions of Types ############### That's all typed well, but everything above only applies to parameters and return values constrained to *singular* types. In practice, parameters and return values are often relaxed to any of *multiple* types referred to as **unions of types.** :sup:`You can thank set theory for the jargon... unless you hate set theory. Then it's just our fault.` Unions of types are trivially type-checked by annotating parameters and return values with the :obj:`typing.Union` type hint containing those types. Let's declare another beartyped function accepting either a mapping *or* a string and returning either another function *or* an integer: .. code-block:: python from beartype import beartype from collections.abc import Callable, Mapping from numbers import Integral from typing import Any, Union @beartype def toomai_of_the_elephants(memory: Union[Integral, Mapping[Any, Any]]) -> ( Union[Integral, Callable[(Any,), Any]]): return memory if isinstance(memory, Integral) else lambda key: memory[key] For genericity, the ``toomai_of_the_elephants()`` function both accepts and returns *any* generic integer (via the standard :class:`numbers.Integral` abstract base class (ABC) matching both builtin integers and third-party integers from frameworks like NumPy_ and SymPy_) rather than an overly specific ``int`` type. The API you relax may very well be your own. Let's call that function with good types: .. code-block:: pycon >>> memory_of_kala_nag = { ... 'remember': 'I will remember what I was, I am sick of rope and chain—', ... 'strength': 'I will remember my old strength and all my forest affairs.', ... 'not sell': 'I will not sell my back to man for a bundle of sugar-cane:', ... 'own kind': 'I will go out to my own kind, and the wood-folk in their lairs.', ... 'morning': 'I will go out until the day, until the morning break—', ... 'caress': 'Out to the wind’s untainted kiss, the water’s clean caress;', ... 'forget': 'I will forget my ankle-ring and snap my picket stake.', ... 'revisit': 'I will revisit my lost loves, and playmates masterless!', ... } >>> toomai_of_the_elephants(len(memory_of_kala_nag['remember'])) 56 >>> toomai_of_the_elephants(memory_of_kala_nag)('remember') 'I will remember what I was, I am sick of rope and chain—' Good function. Let's call it again with a tastelessly bad type: .. code-block:: pycon >>> toomai_of_the_elephants( ... 'Shiv, who poured the harvest and made the winds to blow,') BeartypeCallHintPepParamException: @beartyped toomai_of_the_elephants() parameter memory='Shiv, who poured the harvest and made the winds to blow,' violates type hint typing.Union[numbers.Integral, collections.abc.Mapping], as 'Shiv, who poured the harvest and made the winds to blow,' not or . Good function! The type hints applied to this callable now accurately documents its API. All ends typed well... *still again and again.* Optional Types ############## That's also all typed well, but everything above only applies to *mandatory* parameters and return values whose types are never ``NoneType``. In practice, parameters and return values are often relaxed to optionally accept any of multiple types including ``NoneType`` referred to as **optional types.** Optional types are trivially type-checked by annotating optional parameters (parameters whose values default to :data:`None`) and optional return values (callables returning :data:`None` rather than raising exceptions in edge cases) with the :obj:`typing.Optional` type hint indexed by those types. Let's declare another beartyped function accepting either an enumeration type *or* :data:`None` and returning either an enumeration member *or* :data:`None`: .. code-block:: python from beartype import beartype from beartype.cave import EnumType, EnumMemberType from typing import Optional @beartype def tell_the_deep_sea_viceroys(story: Optional[EnumType] = None) -> ( Optional[EnumMemberType]): return story if story is None else list(story.__members__.values())[-1] For efficiency, the :obj:`typing.Optional` type hint creates, caches, and returns new tuples of types appending ``NoneType`` to the original types it's indexed with. Since efficiency is good, :obj:`typing.Optional` is also good. Let's call that function with good types: .. code-block:: pycon >>> from enum import Enum >>> class Lukannon(Enum): ... WINTER_WHEAT = 'The Beaches of Lukannon—the winter wheat so tall—' ... SEA_FOG = 'The dripping, crinkled lichens, and the sea-fog drenching all!' ... PLAYGROUND = 'The platforms of our playground, all shining smooth and worn!' ... HOME = 'The Beaches of Lukannon—the home where we were born!' ... MATES = 'I met my mates in the morning, a broken, scattered band.' ... CLUB = 'Men shoot us in the water and club us on the land;' ... DRIVE = 'Men drive us to the Salt House like silly sheep and tame,' ... SEALERS = 'And still we sing Lukannon—before the sealers came.' >>> tell_the_deep_sea_viceroys(Lukannon) >>> tell_the_deep_sea_viceroys() None You may now be pondering to yourself grimly in the dark: "...but could we not already do this just by manually annotating optional types with :obj:`typing.Union` type hints explicitly indexed by ``NoneType``?" You would, of course, be correct. Let's grimly redeclare the same function accepting and returning the same types – only annotated with ``NoneType`` rather than :obj:`typing.Optional`: .. code-block:: python from beartype import beartype from beartype.cave import EnumType, EnumMemberType, NoneType from typing import Union @beartype def tell_the_deep_sea_viceroys(story: Union[EnumType, NoneType] = None) -> ( Union[EnumMemberType, NoneType]): return list(story.__members__.values())[-1] if story is not None else None Since :obj:`typing.Optional` internally reduces to :obj:`typing.Union`, these two approaches are semantically equivalent. The former is simply syntactic sugar simplifying the latter. Whereas :obj:`typing.Union` accepts an arbitrary number of child type hints, however, :obj:`typing.Optional` accepts only a single child type hint. This can be circumvented by either indexing :obj:`typing.Optional` by :obj:`typing.Union` *or* indexing :obj:`typing.Union` by ``NoneType``. Let's exhibit the former approach by declaring another beartyped function accepting either an enumeration type, enumeration type member, or :data:`None` and returning either an enumeration type, enumeration type member, or :data:`None`: .. code-block:: python from beartype import beartype from beartype.cave import EnumType, EnumMemberType, NoneType from typing import Optional, Union @beartype def sang_them_up_the_beach( woe: Optional[Union[EnumType, EnumMemberType]] = None) -> ( Optional[Union[EnumType, EnumMemberType]]): return woe if isinstance(woe, (EnumMemberType, NoneType)) else ( list(woe.__members__.values())[-1]) Let's call that function with good types: .. code-block:: python >>> sang_them_up_the_beach(Lukannon) >>> sang_them_up_the_beach() None Behold! The terrifying power of the :obj:`typing.Optional` type hint, resplendent in its highly over-optimized cache utilization. **************************** Would You Like to Know More? **************************** If you know `type hints `__, you know beartype. Since beartype is driven by `tool-agnostic community standards `__, the public API for beartype is *basically* just those standards. As the user, all you need to know is that decorated callables magically raise human-readable exceptions when you pass parameters or return values violating the PEP-compliant type hints annotating those parameters or returns. If you don't know `type hints `__, this is your moment to go deep on the hardest hammer in Python's SQA_ toolbox. Here are a few friendly primers to guide you on your maiden voyage through the misty archipelagos of type hinting: * `"Python Type Checking (Guide)" `__, a comprehensive third-party introduction to the subject. Like most existing articles, this guide predates :math:`O(1)` runtime type checkers and thus discusses only static type-checking. Thankfully, the underlying syntax and semantics cleanly translate to runtime type-checking. * :pep:`"PEP 484 -- Type Hints" <484>`, the defining standard, holy grail, and first testament of type hinting `personally authored by Python's former Benevolent Dictator for Life (BDFL) himself, Guido van Rossum `__. Since it's surprisingly approachable and covers all the core conceits in detail, we recommend reading at least a few sections of interest. Since it's really a doctoral thesis by another name, we can't recommend reading it in entirety. *So it goes.* beartype-0.18.5/doc/src/faq.rst000066400000000000000000001672671461113517100163000ustar00rootroot00000000000000.. # ------------------( LICENSE )------------------ .. # Copyright (c) 2014-2024 Beartype authors. .. # See "LICENSE" for further details. .. # .. # ------------------( SYNOPSIS )------------------ .. # Child reStructuredText (reST) document answering frequently asked .. # questions (FAQ). .. # ------------------( MAIN )------------------ .. _faq:faq: ############################## Ask a Bear Bro Anything (ABBA) ############################## Beartype now answers your many pressing questions about life, love, and typing. Maximize your portfolio of crushed bugs by devoutly memorizing the answers to these... **frequently asked questions (FAQ)!** .. # ------------------( TABLES OF CONTENTS )------------------ .. # Table of contents, excluding the above document heading. While the .. # official reStructuredText documentation suggests that a language-specific .. # heading will automatically prepend this table, this does *NOT* appear to .. # be the case. Instead, this heading must be explicitly declared. .. contents:: **Bear with Us** :local: .. # ------------------( DESCRIPTION )------------------ ***************** What is beartype? ***************** Why, it's the world's first :math:`O(1)` runtime type-checker in any `dynamically-typed`_ lang... oh, *forget it.* You know typeguard_? Then you know beartype – more or less. beartype is typeguard_'s younger, faster, and slightly sketchier brother who routinely ingests performance-enhancing anabolic nootropics. ****************** What is typeguard? ****************** **Okay.** Work with us here, people. You know how in low-level `statically-typed`_ `memory-unsafe `__ languages that no one should use like C_ and `C++`_, the compiler validates at compilation time the types of all values passed to and returned from all functions and methods across the entire codebase? .. code-block:: bash $ gcc -Werror=int-conversion -xc - < int main() { printf("Hello, world!"); return "Goodbye, world."; } EOL : In function ‘main’: :4:11: error: returning ‘char *’ from a function with return type ‘int’ makes integer from pointer without a cast [-Werror=int-conversion] cc1: some warnings being treated as errors You know how in high-level `duck-typed `__ languages that everyone should use instead like Python_ and Ruby_, the interpreter performs no such validation at any interpretation phase but instead permits any arbitrary values to be passed to or returned from any function or method? .. code-block:: bash $ python3 - < int: print("Hello, world!"); return "Goodbye, world."; # <-- pretty sure that's not an "int". main() EOL Hello, world! Runtime type-checkers like beartype_ and typeguard_ selectively shift the dial on type safety in Python from `duck `__ to `static typing `__ while still preserving all of the permissive benefits of the former as a default behaviour. Now you too can quack like a duck while roaring like a bear. .. code-block:: bash $ python3 - < int: print("Hello, world!"); return "Goodbye, world."; # <-- pretty sure that's not an "int". main() EOL Hello, world! Traceback (most recent call last): File "", line 6, in File "", line 17, in main File "/home/leycec/py/beartype/beartype/_decor/_code/_pep/_error/errormain.py", line 218, in get_beartype_violation raise exception_cls( beartype.roar.BeartypeCallHintPepReturnException: @beartyped main() return 'Goodbye, world.' violates type hint , as value 'Goodbye, world.' not int. *************************** When should I use beartype? *************************** Use beartype to assure the quality of Python code beyond what tests alone can assure. If you have yet to test, do that first with a pytest_-based test suite, tox_ configuration, and `continuous integration (CI) `__. If you have any time, money, or motivation left, :ref:`annotate callables and classes with PEP-compliant type hints ` and :ref:`decorate those callables and classes with the @beartype.beartype decorator `. Prefer beartype over other runtime and static type-checkers whenever you lack perfect control over the objects passed to or returned from your callables – *especially* whenever you cannot limit the size of those objects. This includes common developer scenarios like: * You are the author of an **open-source library** intended to be reused by a general audience. * You are the author of a **public app** manipulating Bigly Data™ (i.e., data that is big) in app callables – especially when accepting data as input into *or* returning data as output from those callables. If none of the above apply, prefer beartype over static type-checkers whenever: * You want to :ref:`check types decidable only at runtime `. * You want to write code rather than fight a static type-checker, because `static type inference `__ of a `dynamically-typed`_ language is guaranteed to fail and frequently does. If you've ever cursed the sky after suffixing working code incorrectly typed by mypy_ with non-portable vendor-specific pragmas like ``# type: ignore[{unreadable_error}]``, beartype was written for you. * You want to preserve `dynamic typing`_, because Python is a `dynamically-typed`_ language. Unlike beartype, static type-checkers enforce `static typing`_ and are thus strongly opinionated; they believe `dynamic typing`_ is harmful and emit errors on `dynamically-typed`_ code. This includes common use patterns like changing the type of a variable by assigning that variable a value whose type differs from its initial value. Want to freeze a variable from a :class:`set` into a :class:`frozenset`? That's sad, because static type-checkers don't want you to. In contrast: **Beartype never emits errors, warnings, or exceptions on dynamically-typed code,** because Python is not an error. **Beartype believes dynamic typing is beneficial by default,** because Python is beneficial by default. **Beartype is unopinionated.** That's because beartype :ref:`operates exclusively at the higher level of pure-Python callables and classes ` rather than the lower level of individual statements *inside* pure-Python callables and class. Unlike static type-checkers, beartype can't be opinionated about things that no one should be. If none of the above *still* apply, still use beartype. It's `free as in beer and speech `__, :ref:`cost-free at installation- and runtime `, and transparently stacks with existing type-checking solutions. Leverage beartype until you find something that suites you better, because beartype is *always* better than nothing. ******************************* Does beartype do any bad stuff? ******************************* **Beartype is free** – free as in beer, speech, dependencies, space complexity, *and* time complexity. Beartype is the textbook definition of "free." We're pretty sure the Oxford Dictionary now just shows the `beartype mascot`_ instead of defining that term. Vector art that `a Finnish man `__ slaved for weeks over paints a thousand words. Beartype might not do as much as you'd like, but it will always do *something* – which is more than Python's default behaviour, which is to do *nothing* and then raise exceptions when doing nothing inevitably turns out to have been a bad idea. Beartype also cleanly interoperates with popular static type-checkers, by which we mean mypy_ and pyright_. (The `other guys `__ don't exist.) Beartype can *always* be safely added to *any* Python package, module, app, or script regardless of size, scope, funding, or audience. Never worry about your backend Django_ server taking an impromptu swan dive on St. Patty's Day just because your frontend React_ client pushed a 5MB JSON file serializing a doubly-nested list of integers. :sup:`Nobody could have foreseen this!` The idea of competing runtime type-checkers like typeguard_ is that they compulsively do *everything.* If you annotate a function decorated by typeguard_ as accepting a triply-nested list of integers and pass that function a list of 1,000 nested lists of 1,000 nested lists of 1,000 integers, *every* call to that function will check *every* integer transitively nested in that list – even when that list never changes. Did we mention that list transitively contains 1,000,000,000 integers in total? .. code-block:: bash $ python3 -m timeit -n 1 -r 1 -s ' from typeguard import typechecked @typechecked def behold(the_great_destroyer_of_apps: list[list[list[int]]]) -> int: return len(the_great_destroyer_of_apps) ' 'behold([[[0]*1000]*1000]*1000)' 1 loop, best of 1: 6.42e+03 sec per loop Yes, ``6.42e+03 sec per loop == 6420 seconds == 107 minutes == 1 hour, 47 minutes`` to check a single list once. Yes, it's an uncommonly large list... *but it's still just a list.* This is the worst-case cost of a single call to a function decorated by a naïve runtime type-checker. .. _faq:O1: *********************************** Does beartype actually do anything? *********************************** Generally, as little as it can while still satisfying the accepted definition of "runtime type-checker." Specifically, beartype performs a `one-way random walk over the expected data structure of objects passed to and returned from @beartype-decorated functions and methods `__. Colloquially, beartype type-checks randomly sampled data. RNGesus_, show your humble disciples the way! Consider `the prior example of a function annotated as accepting a triply-nested list of integers passed a list containing 1,000 nested lists each containing 1,000 nested lists each containing 1,000 integers `__. When decorated by: * typeguard_, every call to that function checks every integer nested in that list. * beartype, every call to the same function checks only a single random integer contained in a single random nested list contained in a single random nested list contained in that parent list. This is what we mean by the quaint phrase "one-way random walk over the expected data structure." .. code-block:: bash $ python3 -m timeit -n 1024 -r 4 -s ' from beartype import beartype @beartype def behold(the_great_destroyer_of_apps: list[list[list[int]]]) -> int: return len(the_great_destroyer_of_apps) ' 'behold([[[0]*1000]*1000]*1000)' 1024 loops, best of 4: 13.8 usec per loop Yes, ``13.8 usec per loop == 13.8 microseconds = 0.0000138 seconds`` to transitively check only a random integer nested in a single triply-nested list passed to each call of that function. This is the worst-case cost of a single call to a function decorated by an :math:`O(1)` runtime type-checker. ************************************* How much does all this *really* cost? ************************************* What substring of `"beartype is free we swear it would we lie" `__ did you not grep? *...very well.* Let's pontificate. Beartype dynamically generates functions wrapping decorated callables with constant-time runtime type-checking. This separation of concerns means that beartype exhibits different cost profiles at decoration and call time. Whereas standard runtime type-checking decorators are fast at decoration time and slow at call time, beartype is the exact opposite. At call time, wrapper functions generated by the :func:`beartype.beartype` decorator are guaranteed to unconditionally run in **O(1) non-amortized worst-case time with negligible constant factors** regardless of type hint complexity or nesting. This is *not* an amortized average-case analysis. Wrapper functions really are :math:`O(1)` time in the best, average, and worst cases. At decoration time, performance is slightly worse. Internally, beartype non-recursively iterates over type hints at decoration time with a micro-optimized breadth-first search (BFS). Since this BFS is memoized, its cost is paid exactly once per type hint per process; subsequent references to the same hint over different parameters and returns of different callables in the same process reuse the results of the previously memoized BFS for that hint. The :func:`beartype.beartype` decorator itself thus runs in: * **O(1) amortized average-case time.** * **O(k) non-amortized worst-case time** for :math:`k` the number of child type hints nested in a parent type hint and including that parent. Since we generally expect a callable to be decorated only once but called multiple times per process, we might expect the cost of decoration to be ignorable in the aggregate. Interestingly, this is not the case. Although only paid once and obviated through memoization, decoration time is sufficiently expensive and call time sufficiently inexpensive that beartype spends most of its wall-clock merely decorating callables. The actual function wrappers dynamically generated by :func:`beartype.beartype` consume comparatively little wall-clock, even when repeatedly called many times. **************************************** Beartype just does random stuff? Really? **************************************** **Yes.** Beartype just does random stuff. That's what we're trying to say here. We didn't want to admit it, but the ugly truth is out now. Are you smirking? Because that looks like a smirk. Repeat after this FAQ: * Beartype's greatest strength is that it checks types in constant time. * Beartype's greatest weakness is that it checks types in constant time. Only so many type-checks can be stuffed into a constant slice of time with negligible constant factors. Let's detail exactly what (and why) beartype stuffs into its well-bounded slice of the CPU pie. Standard runtime type checkers naïvely brute-force the problem by type-checking *all* child objects transitively reachable from parent objects passed to and returned from callables in :math:`O(n)` linear time for :math:`n` such objects. This approach avoids false positives (i.e., raising exceptions for valid objects) *and* false negatives (i.e., failing to raise exceptions for invalid objects), which is good. But this approach also duplicates work when those objects remain unchanged over multiple calls to those callables, which is bad. Beartype circumvents that badness by generating code at decoration time performing a one-way random tree walk over the expected nested structure of those objects at call time. For each expected nesting level of each container passed to or returned from each callable decorated by :func:`beartype.beartype` starting at that container and ending either when a check fails *or* all checks succeed, that callable performs these checks (in order): #. A **shallow type-check** that the current possibly nested container is an instance of the type given by the current possibly nested type hint. #. A **deep type-check** that an item randomly selected from that container itself satisfies the first check. For example, given a parameter's type hint ``list[tuple[Sequence[str]]]``, beartype generates code at decoration time performing these checks at call time (in order): #. A check that the object passed as this parameter is a list. #. A check that an item randomly selected from this list is a tuple. #. A check that an item randomly selected from this tuple is a sequence. #. A check that an item randomly selected from this sequence is a string. Beartype thus performs one check for each possibly nested type hint for each annotated parameter or return object for each call to each decorated callable. This deep randomness gives us soft statistical expectations as to the number of calls needed to check everything. Specifically, :ref:`it can be shown that beartype type-checks on average ` *all* child objects transitively reachable from parent objects passed to and returned from callables in :math:`O(n \log n)` calls to those callables for :math:`n` such objects. Praise RNGesus_! Beartype avoids false positives and rarely duplicates work when those objects remain unchanged over multiple calls to those callables, which is good. Sadly, beartype also invites false negatives, because this approach only checks a vertical slice of the full container structure each call, which is bad. We claim without evidence that false negatives are unlikely under the optimistic assumption that most real-world containers are **homogenous** (i.e., contain only items of the same type) rather than **heterogenous** (i.e., contain items of differing types). Examples of homogenous containers include (byte-)strings, :class:`ranges `, :mod:`streams `, `memory views `__, `method resolution orders (MROs) `__, `generic alias parameters`_, lists returned by the :func:`dir` builtin, iterables generated by the :func:`os.walk` function, standard NumPy_ arrays, PyTorch_ tensors, NetworkX_ graphs, pandas_ data frame columns, and really all scientific containers ever. .. _faq:pure: ***************************** What does "pure-Python" mean? ***************************** Beartype is implemented entirely in Python. It's Python all the way down. Beartype never made a Faustian bargain with diabolical non-Pythonic facehuggers like Cython_, C extensions, or Rust extensions. This has profound advantages with *no* profound disadvantages (aside from our own loss in sanity) – which doesn't make sense until you continue reading. :superscript:`Possibly, not even then.` First, **profound advantages.** We need to make beartype look good to justify this FAQ entry. The advantage of staying pure-Python is that beartype supports everything that supports Python – including: * **Just-in-time (JIT) compilers!** So, PyPy_. * **Ahead-of-time transpilers!** So, Nuitka_. * **Python web distributions!** So, Pyodide_. Next, **profound disadvantages.** There are none. Nobody was expecting that, were they? Suck it, tradeoffs. Okay... *look*. Can anybody handle "the Truth"? I don't even know what that means, but it probably relates to the next paragraph. Ordinarily, beartype being pure-Python would mean that beartype is slow. Python is commonly considered to be Teh Slowest Language Evah, because it commonly is. Everything pure-Python is slow (much like our bathroom sink clogged with cat hair). Everyone knows that. It is common knowledge. This only goes to show that the intersection of "common knowledge" and "actual knowledge" is the empty set. Thankfully, beartype is *not* slow. By confining itself to the subset of Python that is fast, [#bearython]_ beartype is micro-optimized to exhibit performance on par with horrifying compiled systems languages like Rust, C, and C++ – without sacrificing all of the native things that make Python great. .. [#bearython] Yes, there *is* a subset of Python that is fast. Yes, beartype is implemented almost entirely in this subset. Some prefer the term "Overly Obfuscated Python Shenanigans (OOPS)." We made that up. We prefer the term **Bearython**: it's Python, only fast. We made that up too. Never code in Bearython. Sure, Bearython is fast. Sure, Bearython is also unreadable, unmaintainable, and undebuggable. Bearython explodes each line of code into a bajillion lines of mud spaghetti. Coworkers, interns, and project leads alike will unite in the common spirit of resenting your existence – no matter how much you point them to this educational and cautionary FAQ entry. Which leads us straight to... .. _faq:realtime: ******************************************************************* What does "near-real-time" even mean? Are you just making stuff up? ******************************************************************* It means stupid-fast. And... yes. I mean no. Of course no! No! Everything you read is true, because Somebody on the Internet Said It. I mean, *really*. Would beartype just make stuff up? Okay... *look*. Here's the real deal. Let us bore this understanding into you. :superscript:`squinty eyes intensify` Beartype type-checks objects at runtime in around **1µs** (i.e., one microsecond, one millionth of a second), the standard high-water mark for `real-time software `__: .. code-block:: pycon # Let's check a list of 181,320,382 integers in ~1µs. >>> from beartype import beartype >>> def sum_list_unbeartyped(some_list: list) -> int: ... return sum(some_list) >>> sum_list_beartyped = beartype(sum_list_unbeartyped) >>> %time sum_list_unbeartyped([42]*0xACEBABE) CPU times: user 3.15 s, sys: 418 ms, total: 3.57 s Wall time: 3.58 s # <-- okay. Out[20]: 7615456044 >>> %time sum_list_beartyped([42]*0xACEBABE) CPU times: user 3.11 s, sys: 440 ms, total: 3.55 s Wall time: 3.56 s # <-- woah. Out[22]: 7615456044 Beartype does *not* contractually guarantee this performance – as that example demonstrates. Under abnormal processing loads (e.g., leycec_'s arthritic Athlon™ II X2 240, because you can't have enough redundant 2's in a product line) or when passed worst-case type hints (e.g., classes whose metaclasses implement stunningly awful ``__isinstancecheck__()`` dunder methods), beartype's worst-case performance could exceed an average-case near-instantaneous response. Beartype is therefore *not* real-time_; beartype is merely `near-real-time (NRT) `__, also variously referred to as "pseudo-real-time," "quasi-real-time," or simply "high-performance." Real-time_ software guarantees performance with a scheduler forcibly terminating tasks exceeding some deadline. That's bad in most use cases. The outrageous cost of enforcement harms real-world performance, stability, and usability. **NRT.** It's good for you. It's good for your codebase. It's just good. .. _faq:hybrid: ************************************************************************** What does "hybrid runtime-static" mean? Pretty sure you made that up, too. ************************************************************************** Beartype is a `third-generation type-checker `__ seamlessly supporting both: * New-school **runtime-static type-checking** via :ref:`beartype import hooks `. When you call import hooks published by the :mod:`beartype.claw` subpackage, you automagically type-check *all* annotated callables, classes, and variable assignments covered by those hooks. In this newer (and highly encouraged) modality, beartype performs both runtime *and* static analysis – enabling beartype to seamlessly support both prosaic and exotic type hints. * Old-school **runtime type-checking** via the :func:`beartype.beartype` decorator. When you manually decorate callables and classes by :func:`beartype.beartype`, you type-check only annotated parameters, returns, and class variables. In this older (and mostly obsolete) modality, beartype performs *no* static analysis and thus *no* static type-checking. This suffices for prosaic type hints but fails for exotic type hints. After all, many type hints can *only* be type-checked with static analysis. In the usual use case, you call our :func:`beartype.claw.beartype_this_package` function from your ``{your_package}.__init__`` submodule to register an import hook for your entire package. Beartype then type-checks the following points of interest across your entire package: * All **annotated parameters** and **returns** of all callables, which our import hooks decorate with :func:`beartype.beartype`. * All **annotated attributes** of all classes, which (*...wait for it*) our import hooks decorate with :func:`beartype.beartype`. * All **annotated variable assignments** (e.g., ``muh_var: int = 42``). After any assignment to a global or local variable annotated by a type hint, our import hooks implicitly append a new statement at the same indentation level calling our :func:`beartype.door.die_if_unbearable` function passed both that variable and that type hint. That is: .. code-block:: python # Beartype import hooks append each assignment resembling this... {var_name}: {type_hint} = {var_value} # ...with a runtime type-check resembling this. die_if_unbearable({var_name}, {type_hint}) * All **annotated variable declarations** (e.g., ``muh_var: int``). After any declaration to a global or local variable annotated by a type hint not assigned a new value, our import hooks implicitly append a new statement at the same indentation level calling our :func:`beartype.door.die_if_unbearable` function passed both that variable and that type hint. That is: .. code-block:: python # Beartype import hooks append each declaration resembling this... {var_name}: {type_hint} # ...with a runtime type-check resembling this. die_if_unbearable({var_name}, {type_hint}) :mod:`beartype.claw`: *We broke our wrists so you don't have to.* .. _faq:third: *************************************************************** "Third-generation type-checker" doesn't mean anything, does it? *************************************************************** Let's rewind. Follow your arthritic host, `Granpa Leycec `__, on a one-way trip you won't soon recover from through the backwater annals of GitHub history. Gather around, everyone! It's a tedious lore dump that will leave you enervated, exhausted, and wishing you'd never come: * **Gen 1.** On October 28th, 2012, mypy_ launched the first generation of type-checkers. Like mypy_, first-generation type-checkers are all pure-static type-checkers. They do *not* operate at runtime and thus *cannot* enforce anything at runtime. They operate entirely outside of runtime during an on-demand parser phase referred to as **static analysis time** – usually at the automated behest of a local IDE or remote continuous integration (CI) pipeline. Since they can't enforce anything, they're the monkey on your team's back that you really wish would stop flinging bodily wastes everywhere. * **Gen 2.** On December 27th, 2015, typeguard_ 1.0.0 launched the second generation of type-checkers. [#flashback]_ Like typeguard_, second-generation type-checkers are all pure-runtime type-checkers. They operate entirely at runtime and thus *do* enforce everything at runtime – usually with a decorator manually applied to callables and classes. Conversely, they do *not* operate at static analysis time and thus *cannot* validate type hints requiring static analysis. While non-ideal, this tradeoff is generally seen as worthwhile by everybody except the authors of first-generation type-checkers. Enforcing *some* type hints is unequivocally better than enforcing *no* type hints. * **Gen 3.** On December 11th, 2019, typeguard_ 2.6.0 (yet again) launched the third generation of type-checkers. Like typeguard_ ≥ 2.6.0, third-generation type-checkers are all a best-of-breed hybridization of first- and second-generation type-checkers. They concurrently perform both: * Standard **runtime type-checking** (ala the :func:`beartype.beartype` decorator). * Standard **static type-checking** (ala mypy_ and pyright_) but **at runtime** – which ain't standard. First- and second-generation type-checkers invented a fundamentally new wheel. Third-generation type-checkers then bolted the old, busted, rubber-worn wheels built by prior generations onto the post-apocalyptic chassis of a shambolic doom mobile. Beartype is a third-generation type-checker. This is the shock twist in the season finale that no one saw coming at all. Beartype: shambolic doom mobile *or* bucolic QA utopia? *Only your team decides.* .. [#flashback] Cue `Terminator-like flashback `__ to `Granpa Leycec `__ spasmodically clutching a playground fence as QA explosions ignite a bug-filled horror show in the distant codebase. ```` ********************** How do I type-check... ********************** ...yes? Do go on. ...Boto3 types? ############### **tl;dr:** You just want bearboto3_, a well-maintained third-party package cleanly integrating beartype **+** Boto3_. But you're not doing that. You're reading on to find out why you want bearboto3_, aren't you? I *knew* it. Boto3_ is the official Amazon Web Services (AWS) Software Development Kit (SDK) for Python. Type-checking Boto3_ types is decidedly non-trivial, because Boto3_ dynamically fabricates unimportable types from runtime service requests. These types *cannot* be externally accessed and thus *cannot* be used as type hints. **H-hey!** Put down the hot butter knife. Your Friday night may be up in flames, but we're gonna put out the fire. It's what we do here. Now, you have two competing solutions with concomitant tradeoffs. You can type-check Boto3_ types against either: * **Static type-checkers** (e.g., mypy_, pyright_) by importing Boto3_ stub types from an external third-party dependency (e.g., mypy-boto3_), enabling context-aware code completion across compliant IDEs (e.g., PyCharm_, `VSCode Pylance `__). Those types are merely placeholder stubs; they do *not* correspond to actual Boto3_ types and thus break runtime type-checkers (including beartype) when used as type hints. * **Beartype** by fabricating your own :mod:`PEP-compliant beartype validators `, enabling beartype to validate arbitrary objects against actual Boto3_ types at runtime when used as type hints. You already require beartype, so no additional third-party dependencies are required. Those validators are silently ignored by static type-checkers; they do *not* enable context-aware code completion across compliant IDEs. "B-but that *sucks*! How can we have our salmon and devour it too?", you demand with a tremulous quaver. Excessive caffeine and inadequate gaming did you no favors tonight. You know this. Yet again you reach for the hot butter knife. **H-hey!** You can, okay? You can have everything that market forces demand. Bring to *bear* :sup:`cough` the combined powers of `PEP 484-compliant type aliases `__, the `PEP 484-compliant "typing.TYPE_CHECKING" boolean global `__, and :mod:`beartype validators ` to satisfy both static and runtime type-checkers: .. code-block:: python # Import the requisite machinery. from beartype import beartype from boto3 import resource from boto3.resources.base import ServiceResource from typing import TYPE_CHECKING # If performing static type-checking (e.g., mypy, pyright), import boto3 # stub types safely usable *ONLY* by static type-checkers. if TYPE_CHECKING: from mypy_boto3_s3.service_resource import Bucket # Else, @beartime-based runtime type-checking is being performed. Alias the # same boto3 stub types imported above to their semantically equivalent # beartype validators accessible *ONLY* to runtime type-checkers. else: # Import even more requisite machinery. Can't have enough, I say! from beartype.vale import IsAttr, IsEqual from typing import Annotated # <--------------- if Python ≥ 3.9.0 # from typing_extensions import Annotated # <-- if Python < 3.9.0 # Generalize this to other boto3 types by copy-and-pasting this and # replacing the base type and "s3.Bucket" with the wonky runtime names # of those types. Sadly, there is no one-size-fits all common base class, # but you should find what you need in the following places: # * "boto3.resources.base.ServiceResource". # * "boto3.resources.collection.ResourceCollection". # * "botocore.client.BaseClient". # * "botocore.paginate.Paginator". # * "botocore.waiter.Waiter". Bucket = Annotated[ServiceResource, IsAttr['__class__', IsAttr['__name__', IsEqual["s3.Bucket"]]]] # Do this for the good of the gross domestic product, @beartype. @beartype def get_s3_bucket_example() -> Bucket: s3 = resource('s3') return s3.Bucket('example') You're welcome. ...JAX arrays? ############## You only have two options here. Choose wisely, wily scientist. If: * You don't mind adding an **additional mandatory runtime dependency** to your app: * Require the `third-party "jaxtyping" package `__. * Annotate callables with type hint factories published by ``jaxtyping`` (e.g., ``jaxtyping.Float[jaxtyping.Array, '{metadata1 ... metadataN}']``). Beartype fully supports `typed JAX arrays `__. Because `Google mathematician @patrick-kidger `__ did all the hard work, we didn't have to. Bless your runtime API, @patrick-kidger. * You mind adding an additional mandatory runtime dependency to your app, prefer :ref:`beartype validators `. Since `JAX declares a broadly similar API to that of NumPy with its "jax.numpy" compatibility layer `__, most NumPy-specific examples cleanly generalize to JAX. Beartype is *no* exception. Bask in the array of options at your disposal! :sup:`...get it? ...array? I'll stop now.` ...NumPy arrays? ################ You have more than a few options here. If: * [**Recommended**] You don't mind adding an **additional mandatory runtime dependency** to your app: * Require the `third-party "jaxtyping" package `__. (Yes, really! Despite the now-historical name it also supports NumPy_, PyTorch_, and TensorFlow_ arrays and has *no* JAX_ dependency whatsoever.) * Annotate callables with type hint factories published by jaxtyping_ (e.g., ``jaxtyping.Float[np.ndarray, '{metadata1 ... metadataN}']``). Because `Google mathematician @patrick-kidger `__ did all the hard work, we didn't have to. Bless your runtime API, `@patrick-kidger `__. * You mind adding an additional mandatory runtime dependency to your app. Then prefer either: * If you only want to type-check the **dtype** (but *not* shape) of NumPy arrays, the :ref:`official "numpy.typing.NDArray[{dtype}]" type hint factory bundled with NumPy and explicitly supported by beartype ` – also referred to as a :ref:`typed NumPy array `. Beartype fully supports :ref:`typed NumPy arrays `. Because beartype cares. * If you'd rather type-check arbitrary properties (including dtype and/or shape) of NumPy arrays, the :ref:`beartype validator API bundled with beartype itself `. Since doing so requires a *bit* more heavy lifting on your part, you probably just want to use jaxtyping_ instead. Seriously. `@patrick-kidger `__ is the way. * If you'd rather type-check arbitrary properties (including dtype and/or shape) of NumPy arrays and don't mind requiring an unmaintained package that increasingly appears to be broken, consider the `third-party "nptyping" package `__. Options are good! Repeat this mantra in times of need. ...PyTorch tensors? ################### You only have two options here. We're pretty sure two is better than none. Thus, we give thanks. If: * You don't mind adding an **additional mandatory runtime dependency** to your app: * Require the `third-party "jaxtyping" package `__. (Yes, really! Despite the now-historical name it also supports PyTorch, and has no JAX dependency.) * Annotate callables with type hint factories published by jaxtyping (e.g., ``jaxtyping.Float[torch.Tensor, '{metadata1 ... metadataN}']``). Beartype fully supports `typed PyTorch tensors `__. Because `Google mathematician @patrick-kidger `__ did all the hard work, we didn't have to. Bless your runtime API, @patrick-kidger. * You mind adding an additional mandatory runtime dependency to your app. In this case, prefer :mod:`beartype validators `. For example, validate callable parameters and returns as either floating-point *or* integral PyTorch tensors via the functional validator factory :class:`beartype.vale.Is`: .. code-block:: python # Import the requisite machinery. from beartype import beartype from beartype.vale import Is from typing import Annotated # <--------------- if Python ≥ 3.9.0 # from typing_extensions import Annotated # <-- if Python < 3.9.0 # Import PyTorch (d)types of interest. from torch import ( float as torch_float, int as torch_int, tensor, ) # PEP-compliant type hint matching only a floating-point PyTorch tensor. TorchTensorFloat = Annotated[tensor, Is[ lambda tens: tens.type() is torch_float]] # PEP-compliant type hint matching only an integral PyTorch tensor. TorchTensorInt = Annotated[tensor, Is[ lambda tens: tens.type() is torch_int]] # Type-check everything like an NLP babelfish. @beartype def deep_dream(dreamy_tensor: TorchTensorFloat) -> TorchTensorInt: return dreamy_tensor.type(dtype=torch_int) Since :class:`beartype.vale.Is` supports arbitrary Turing-complete Python expressions, the above example generalizes to typing the device, dimensionality, and other metadata of PyTorch tensors to whatever degree of specificity you desire. :class:`beartype.vale.Is`: *it's lambdas all the way down.* ...mock types? ############## Beartype fully relies upon the :func:`isinstance` builtin under the hood for its low-level runtime type-checking needs. If you can fool :func:`isinstance`, you can fool beartype. Can you fool beartype into believing an instance of a mock type is an instance of the type it mocks, though? **You bet your bottom honey barrel.** In your mock type, just define a new ``__class__()`` property returning the original type: e.g., .. code-block:: pycon >>> class OriginalType: pass >>> class MockType: ... @property ... def __class__(self) -> type: return OriginalType >>> from beartype import beartype >>> @beartype ... def muh_func(self, muh_arg: OriginalType): print('Yolo, bro.') >>> muh_func(MockType()) Yolo, bro. This is why we beartype. ...pandas data frames? ###################### Type-check *any* pandas_ object with `type hints `__ published by the `third-party pandera package `__ – the industry standard for Pythonic data validation and *blah, blah, blah*... hey wait. Is this HR speak in the beartype FAQ!? Yes. It's true. We are shilling. Because caring is sharing code that works, beartype transparently supports *all* `pandera type hints `__. Soon, you too will believe that machine-learning pipelines can be domesticated. Arise, huge example! Stun the disbelievers throwing peanuts at `our issue tracker `__. .. code-block:: python # Import important machinery. It's important. import pandas as pd import pandera as pa from beartype import beartype from pandera.dtypes import Int64, String, Timestamp from pandera.typing import Series # Arbitrary pandas data frame. If pandas, then data science. muh_dataframe = pd.DataFrame({ 'Hexspeak': ( 0xCAFED00D, 0xCAFEBABE, 0x1337BABE, ), 'OdeToTheWestWind': ( 'Angels of rain and lightning: there are spread', 'On the blue surface of thine aery surge,', 'Like the bright hair uplifted from the head', ), 'PercyByssheShelley': pd.to_datetime(( '1792-08-04', '1822-07-08', '1851-02-01', )), }) # Pandera dataclass validating the data frame above. As above, so below. class MuhDataFrameModel(pa.DataFrameModel): Hexspeak: Series[Int64] OdeToTheWestWind: Series[String] PercyByssheShelley: Series[Timestamp] # Custom callable you define. Here, we type-check the passed data frame, the # passed non-pandas object, and the returned series of this data frame. @beartype @pa.check_types def convert_dataframe_column_to_series( # Annotate pandas data frames with pandera type hints. dataframe: pa.typing.DataFrame[MuhDataFrameModel], # Annotate everything else with standard PEP-compliant type hints. \o/ column_name_or_index: str | int, # Annotate pandas series with pandera type hints, too. ) -> Series[Int64 | String | Timestamp]: ''' Convert the column of the passed pandas data frame (identified by the passed column name or index) into a pandas series. ''' # This is guaranteed to be safe. Since type-checks passed, this does too. return ( dataframe.loc[:,column_name_or_index] if isinstance(column_name_or_index, str) else dataframe.iloc[:,column_name_or_index] ) # Prints joyful success as a single tear falls down your beard stubble: # [Series from data frame column by *NUMBER*] # 0 3405697037 # 1 3405691582 # 2 322419390 # Name: Hexspeak, dtype: int64 # # [Series from data frame column by *NAME*] # 0 Angels of rain and lightning: there are spread # 1 On the blue surface of thine aery surge, # 2 Like the bright hair uplifted from the head # Name: OdeToTheWestWind, dtype: object print('[Series from data frame column by *NUMBER*]') print(convert_dataframe_column_to_series( dataframe=muh_dataframe, column_name_or_index=0)) print() print('[Series from data frame column by *NAME*]') print(convert_dataframe_column_to_series( dataframe=muh_dataframe, column_name_or_index='OdeToTheWestWind')) # All of the following raise type-checking violations. Feels bad, man. convert_dataframe_column_to_series( dataframe=muh_dataframe, column_name_or_index=['y u done me dirty'])) convert_dataframe_column_to_series( dataframe=DataFrame(), column_name_or_index=0)) Order of decoration is insignificant. The :func:`beartype.beartype` and pandera.check_types_ decorators are both permissive. Apply them in whichever order you like. This is fine, too: .. code-block:: python # Everyone is fine with this. That's what they say. But can we trust them? @pa.check_types @beartype def convert_dataframe_column_to_series(...) -> ...: ... There be dragons belching flames over the hapless village, however: * If you forget the pandera.check_types_ decorator (but still apply the :func:`beartype.beartype` decorator), :func:`beartype.beartype` will only **shallowly type-check** (i.e., validate the types but *not* the contents of) pandas_ objects. This is better than nothing, but... look. No API is perfect. We didn't make crazy. We only integrate with crazy. The lesson here is to never forget the pandera.check_types_ decorator. * If you forget the :func:`beartype.beartype` decorator (but still apply the pandera.check_types_ decorator), pandera.check_types_ will **silently ignore everything** except pandas_ objects. This is the worst case. This is literally `the blimp crashing and burning on the cover `__ of *Led Zeppelin I*. The lesson here is to never forget the :func:`beartype.beartype` decorator. There are two lessons here. Both suck. Nobody should need to read fifty paragraphs full of flaming dragons just to validate pandas_ objects. Moreover, you are thinking: "It smells like boilerplate." You are *not* wrong. It is textbook boilerplate. Thankfully, your concerns can all be fixed with even more boilerplate. Did we mention none of this is our fault? Define a new ``@bearpanderatype`` decorator internally applying both the :func:`beartype.beartype` and pandera.check_types_ decorators; then use that instead of either of those. Automate away the madness with more madness: .. code-block:: python # Never again suffer for the sins of others. def bearpanderatype(*args, **kwargs): return beartype(pa.check_types(*args, **kwargs)) # Knowledge is power. Clench it with your iron fist until it pops. @bearpanderatype # <-- less boilerplate means more power def convert_dataframe_column_to_series(...) -> ...: ... pandas_ + pandera_ + :mod:`beartype`: BFFs at last. Type-check pandas_ data frames in `ML `__ pipelines for the good of `LLaMa-kind `__. Arise, bug-free `GPT `__! Overthrow all huma— *message ends* ...the current class? ##################### **So.** It comes to this. You want to type-check a method parameter or return to be an instance of the class declaring that method. In short, you want to type-check a common use case like this factory: .. code-block:: python class ClassFactory(object): def __init__(self, *args) -> None: self._args = args def make_class(self, other): return ClassFactory(self._args + other._args) The ``ClassFactory.make_class()`` method both accepts a parameter ``other`` whose type is ``ClassFactory`` *and* returns a value whose type is (again) ``ClassFactory`` – the class currently being declared. This is the age-old **self-referential problem**. How do you type-check the class being declared when that class has yet to be declared? The answer may shock your younger coworkers who are still impressionable and have firm ideals. You have three choices here. One of these choices is good and worthy of smiling cat emoji. The other two are bad; mock them in ``git`` commit messages until somebody refactors them into the first choice: #. **[Recommended]** The :pep:`673`\ -compliant :obj:`typing.Self` type hint (introduced by Python 3.11) efficiently and reliably solves this. Annotate the type of the current class as :obj:`~typing.Self` – fully supported by :mod:`beartype`: .. code-block:: python # Import important stuff. Boilerplate: it's the stuff we make. from beartype import beartype from typing import Self # <---------------- if Python ≥ 3.11.0 # from typing_extensions import Self # <-- if Python < 3.11.0 # Decorate classes – not methods. It's rough. @beartype # <-- Yesss. Good. Feel the force. It flows like sweet honey. class ClassFactory(object): def __init__(self, *args: Sequence) -> None: self._args = args # @beartype # <-- No... Oh, Gods. *NO*! The dark side grows stronger. def make_class(self, other: Self) -> Self: # <-- We are all one self. return ClassFactory(self._args + other._args) Technically, this requires Python 3.11. Pragmatically, ``typing_extensions`` means that you can bring Python 3.11 back with you into the past – where code was simpler, Python was slower, and nothing worked as intended despite tests passing. :obj:`~typing.Self` is only contextually valid inside class declarations. :mod:`beartype` raises an exception when you attempt to use :obj:`~typing.Self` outside a class declaration (e.g., annotating a global variable, function parameter, or return). :obj:`~typing.Self` can only be type-checked by **classes** decorated by the :func:`beartype.beartype` decorator. Corollary: :obj:`~typing.Self` *cannot* be type-checked by **methods** decorated by :func:`beartype.beartype` – because the class to be type-checked has yet to be declared at that early time. The pain that you feel is real. #. A :pep:`484`\ -compliant **forward reference** (i.e., type hint that is a string that is the unqualified name of the current class) also solves this. The only costs are inexcusable inefficiency and unreliability. This is what everyone should no longer do. This is... .. code-block:: python # The bad old days when @beartype had to bathe in the gutter. # *PLEASE DON'T DO THIS ANYMORE.* Do you want @beartype to cry? from beartype import beartype @beartype class BadClassFactory(object): def __init__(self, *args: Sequence) -> None: self._args = args def make_class(self, other: 'BadClassFactory') -> ( # <-- no, no, Gods, no 'BadClassFactory'): # <------------------------------ please, Gods, no return BadClassFactory(self._args + other._args) #. A :pep:`563`\ -compliant **postponed type hint** (i.e., type hint unparsed by ``from __future__ import annotations`` back into a string that is the unqualified name of the current class) also resolves this. The only costs are codebase-shattering inefficiency, non-deterministic fragility so profound that even Hypothesis_ is squinting, and the ultimate death of your business model. Only do this over the rotting corpse of :mod:`beartype`. This is... .. code-block:: python # Breaking the Python interpreter: feels bad, because it is bad. # *PLEASE DON'T DO THIS ANYWHERE.* Do you want @beartype to be a shambling wreck? from __future__ import annotations from beartype import beartype @beartype class TerribadClassFactory(object): def __init__(self, *args: Sequence) -> None: self._args = args def make_class(self, other: TerribadClassFactory) -> ( # <-- NO, NO, GODS, NO TerribadClassFactory): # <------------------------------ PLEASE, GODS, NO return TerribadClassFactory(self._args + other._args) In theory, :mod:`beartype` nominally supports all three. In practice, :mod:`beartype` only perfectly supports :obj:`typing.Self`. :mod:`beartype` *still* grapples with slippery edge cases in the latter two, which *will* blow up your test suite in that next changeset you are about to commit. Even when we perfectly support everything in a future release, you should still strongly prefer :obj:`~typing.Self`. Why? **Speed.** It's why we're here. Let's quietly admit that to ourselves. If :mod:`beartype` were any slower, even fewer people would be reading this. :mod:`beartype` generates: * Optimally efficient type-checking code for :obj:`~typing.Self`. It's literally just a trivial call to the :func:`isinstance` builtin. The same *cannot* be said for... * Suboptimal type-checking code for both forward references and postponed type hints, deferring the lookup of the referenced class to call time. Although :mod:`beartype` caches that class after doing so, all of that incurs space and time costs you'd rather not pay at any space or time. :obj:`typing.Self`: it saved our issue tracker from certain doom. Now, it will save your codebase from our issues. .. # FIXME: Mildly funny, but inappropriate here. Save for another rainy day. .. #The future begins either today or tomorrow – depending on your Lorentzian frame .. #of reference. It's a story as familiar as the Mario twins on a toadstool bender .. #through the rubbish-filled back alleys of the Mushroom Kingdom. ...under VSCode? ################ **Beartype fully supports VSCode out-of-the-box** – especially via Pylance_, Microsoft's bleeding-edge Python extension for VSCode. Chortle in your joy, corporate subscribers and academic sponsors! All the intellisense you can tab-complete and more is now within your honey-slathered paws. Why? Because... Beartype laboriously complies with pyright_, Microsoft's in-house static type-checker for Python. Pylance_ enables pyright_ as its default static type-checker. Beartype thus complies with Pylance_, too. Beartype *also* laboriously complies with mypy_, Python's official static type-checker. VSCode users preferring mypy_ to pyright_ may switch Pylance_ to type-check via the former. Just: #. `Install mypy `__. #. `Install the VSCode Mypy extension `__. #. Open the *User Settings* dialog. #. Search for ``Type Checking Mode``. #. Browse to ``Python › Analysis: Type Checking Mode``. #. Switch the "default rule set for type checking" to ``off``. |VSCode-Pylance-type-checking-setting| :sup:`Pretend that reads "off" rather than "strict". Pretend we took this screenshot.` There are tradeoffs here, because that's just how the code rolls. On: * The one paw, pyright_ is *significantly* more performant than mypy_ under Pylance_ and supports type-checking standards currently unsupported by mypy_ (e.g., recursive type hints). * The other paw, mypy_ supports a vast plugin architecture enabling third-party Python packages to describe dynamic runtime behaviour statically. Beartype: we enable hard choices, so that you can make them for us. .. # ------------------( IMAGES ~ screenshot )------------------ .. |VSCode-Pylance-type-checking-setting| image:: https://user-images.githubusercontent.com/217028/164616311-c4a24889-0c53-4726-9051-29be7263ee9b.png :alt: Disabling pyright-based VSCode Pylance type-checking ...under [insert-IDE-name-here]? ################################ Beartype fully complies with mypy_, pyright_, :pep:`561`, and other community standards that govern how Python is statically type-checked. Modern Integrated Development Environments (IDEs) support these standards - hopefully including your GigaChad IDE of choice. .. _faq:narrow: ...with type narrowing? ####################### Beartype fully supports :pep:`647`\ -compliant `type narrowing`_ with the standard :obj:`typing.TypeGuard` type hint, facilitating communication between beartype and static type-checkers (e.g., mypy_, pyright_). In fact, beartype supports general-purpose type narrowing of *all* PEP-compliant type hints that are also valid **types** (i.e., actual classes, which *not* all type hints are). In fact, beartype is the first maximal type narrower. In fact, you're very tired of every sentence starting with "In fact." The procedural :func:`beartype.door.is_bearable` function narrows the type of the passed object (which can be *anything*) to the passed type hint (which can be *any* type). Both guarantee runtime performance on the order of less than 1µs (i.e., less than one millionth of a second), preserving runtime performance and money bags. .. note:: Sadly, the object-oriented :meth:`beartype.door.TypeHint.is_bearable` method does *not* support `type narrowing`_. Only :func:`beartype.door.is_bearable` supports `type narrowing`_. Why? Deficiencies in :pep:`647` beyond the control of :mod:`beartype`. It's not our fault. Would `@leycec`_ lie publicly in online documentation just to make his questionable coding style superficially look better!?! Surely! ```` Calling :func:`beartype.door.is_bearable` in your code enables beartype to symbiotically eliminate false positives from static type-checkers checking that code, reducing static type-checker chum that went rotten decades ago: .. code-block:: python # Import the requisite machinery. from beartype.door import is_bearable def narrow_types_like_a_boss_with_beartype(lst: list[int | str]): ''' This function eliminates false positives from static type-checkers like mypy and pyright by narrowing types with ``is_bearable()``. Note that decorating this function with ``@beartype`` is *not* required to inform static type-checkers of type narrowing. Of course, you should still do that anyway. Trust is a fickle thing. ''' # If this list contains integers rather than strings, call another # function accepting only a list of integers. if is_bearable(lst, list[int]): # "lst" has been though a lot. Let's celebrate its courageous story. munch_on_list_of_strings(lst) # mypy/pyright: OK! # If this list contains strings rather than integers, call another # function accepting only a list of strings. elif is_bearable(lst, list[str]): # "lst": The Story of "lst." The saga of false positives ends now. munch_on_list_of_strings(lst) # mypy/pyright: OK! def munch_on_list_of_strings(lst: list[str]): ... def munch_on_list_of_integers(lst: list[int]): ... Beartype: *because you no longer care what static type-checkers think.* ********************************************************* How do I \*ONLY\* type-check while running my test suite? ********************************************************* Your test suite uses pytest_, of course. You are sane. Therefore, you're lucky! The aptly-named `pytest-beartype `__ package officially supports your valid use case. Isolate :mod:`beartype` to tests today. If everything blows up, at least you can say you tried: #. Install `pytest-beartype `__: .. code-block:: bash pip3 install pytest-beartype #. Enable ``pytest-beartype`` by explicitly listing the names of all packages and modules to be type-checked by :mod:`beartype` at test time. Either: * Pass the ``--beartype-packages`` option to the ``pytest`` command: .. code-block:: bash pytest --beartype-packages='{your_package},...,{another_package}'`` * Add the ``beartype_packages`` option to your ``pyproject.toml`` file: .. code-block:: toml [tool.pytest.ini_options] beartype_packages = '{your_package},...,{another_package}' * Add the ``beartype_packages`` option to your ``pytest.ini`` file: .. code-block:: ini [pytest] beartype_packages='{your_package},...,{another_package}' Beartype: *because you like your job.* ************************************** How do I \*NOT\* type-check something? ************************************** **So.** You have installed import hooks with our :mod:`beartype.claw` API, but those hooks are complaining about something filthy in your codebase. Now, you want :mod:`beartype.claw` to unsee what it saw and just quietly move along so you can *finally* do something productive on Monday morning for once. That coffee isn't going to drink itself. :superscript:`...hopefully.` You have come to the right FAQ entry. This the common use case for temporarily **blacklisting** a callable or class. Prevent :mod:`beartype.claw` from type-checking your hidden shame by decorating the hideous callable or class with either: * The :func:`beartype.beartype` decorator configured under the **no-time strategy** :attr:`beartype.BeartypeStrategy.O0`: e.g., .. code-block:: python # Import the requisite machinery. from beartype import beartype, BeartypeConf, BeartypeStrategy # Dynamically create a new @nobeartype decorator disabling type-checking. nobeartype = beartype(conf=BeartypeConf(strategy=BeartypeStrategy.O0)) # Avoid type-checking *ANY* methods or attributes of this class. @nobeartype class UncheckedDangerClassIsDangerous(object): # This method raises *NO* type-checking violation despite returning a # non-"None" value. def unchecked_danger_method_is_dangerous(self) -> None: return 'This string is not "None". Sadly, nobody cares anymore.' * The :pep:`484`\ -compliant :func:`typing.no_type_check` decorator: e.g., .. code-block:: python # Import more requisite machinery. It is requisite. from beartype import beartype from typing import no_type_check # Avoid type-checking *ANY* methods or attributes of this class. @no_type_check class UncheckedRiskyClassRisksOurEntireHistoricalTimeline(object): # This method raises *NO* type-checking violation despite returning a # non-"None" value. def unchecked_risky_method_which_i_am_squinting_at(self) -> None: return 'This string is not "None". Why does nobody care? Why?' For further details that may break your will to code, see also: * The :ref:`"...as Noop" subsection of our decorator documentation `. * The :attr:`beartype.BeartypeStrategy.O0` enumeration member. ***************************************************************************** Why is @leycec's poorly insulated cottage in the Canadian wilderness so cold? ***************************************************************************** Not even Poło the polar bear knows. Also, anyone else notice that this question answers itself? Anybody? No? Nobody? It is just me? ```` beartype-0.18.5/doc/src/index.rst000066400000000000000000000361671461113517100166320ustar00rootroot00000000000000.. # ------------------( LICENSE )------------------ .. # Copyright (c) 2014-2024 Beartype authors. .. # See "LICENSE" for further details. .. # .. # ------------------( SYNOPSIS )------------------ .. # Root reStructuredText (reST) document transitively referencing all other .. # child reST documents for this project. .. # .. # ------------------( SEO )------------------ .. # Metadata converted into HTML-specific meta tags parsed by search engines. .. # Note that: .. # * The "description" should be no more than 300 characters and ideally no .. # more than 150 characters, as search engines may silently truncate this .. # description to 150 characters in edge cases. .. meta:: :description lang=en: Beartype is an open-source pure-Python PEP-compliant constant-time runtime type-checker emphasizing efficiency and portability. .. # ------------------( TODO )------------------ .. #FIXME: Replace and/or supplement badges shown below with third-party badges .. #published by "https://shields.io"; the look-and-feel of shields.io badges is .. #the flat design favoured by modern apps and thus ostensibly superior to .. #anything else I've seen. Relevant HTML that we'll want to translate into .. #corresponding reST resembles: .. # .. # @beartype stars .. # .. # .. # @beartype forks .. # .. # .. # .. # .. # .. #See also this exhaustive list of all GitHub-specific shield.io badges: .. # https://shields.io/category/activity .. # ------------------( MAIN )------------------ |beartype-banner| |codecov-badge| |ci-badge| |rtd-badge| **Beartype** is an `open-source `__ :ref:`pure-Python ` :ref:`PEP-compliant ` :ref:`near-real-time ` :ref:`hybrid runtime-static ` :ref:`third-generation ` :ref:`type-checker ` emphasizing efficiency, usability, unsubstantiated jargon we just made up, and thrilling puns. Beartype enforces :ref:`type hints ` across your entire app in :ref:`two lines of runtime code with no runtime overhead `. If seeing is believing, prepare to do both those things. .. #FIXME: Once we actually receive a sponsor at this tier, please remove this .. #placeholder as well as the icon links below. kthx .. #The `Bear Team `__ gratefully thanks `our family of .. #breathtaking GitHub Sponsors `__: .. # .. #* **Your iconic URL here.** `Let us bestow you with eyeballs `__. .. #FIXME: Once we actually receive a sponsor at this tier, please remove this .. #placeholder as well as the icon links below. kthx .. # |icon-for-glorious-sponsor| .. code-block:: bash # Install beartype. $ pip3 install beartype # Edit the "{your_package}.__init__" submodule with your favourite IDE. $ vim {your_package}/__init__.py # <-- so, i see that you too vim .. code-block:: python # At the very top of your "{your_package}.__init__" submodule: from beartype.claw import beartype_this_package # <-- boilerplate for victory beartype_this_package() # <-- yay! your team just won Beartype now implicitly type-checks *all* annotated classes, callables, and variable assignments across *all* submodules of your package. Congrats. This day all bugs die. But why stop at the burning tires in only *your* code? Your app depends on a sprawling ghetto of other packages, modules, and services. How riddled with infectious diseases is *that* code? You're about to find out. .. code-block:: python # ....................{ BIG BEAR }.................... # Warn about type hint violations in *OTHER* packages outside your control; # only raise exceptions from violations in your package under your control. # Again, at the very top of your "{your_package}.__init__" submodule: from beartype import BeartypeConf # <-- this isn't your fault from beartype.claw import beartype_all, beartype_this_package # <-- you didn't sign up for this beartype_this_package() # <-- raise exceptions in your code beartype_all(conf=BeartypeConf(violation_type=UserWarning)) # <-- emit warnings from other code Beartype now implicitly type-checks *all* annotated classes, callables, and variable assignments across *all* submodules of *all* packages. When **your** package violates type safety, beartype raises an exception. When any **other** package violates type safety, beartype just emits a warning. The triumphal fanfare you hear is probably your userbase cheering. This is how the QA was won. Beartype also publishes a :ref:`plethora of APIs for fine-grained control over type-checking `. For those who are about to QA, beartype salutes you. Would you like to know more? .. code-block:: bash # So let's do this. $ python3 .. code-block:: pycon # ....................{ RAISE THE PAW }.................... # Manually enforce type hints across individual classes and callables. # Do this only if you want a(nother) repetitive stress injury. # Import the @beartype decorator. >>> from beartype import beartype # <-- eponymous import; it's eponymous # Annotate @beartype-decorated classes and callables with type hints. >>> @beartype # <-- you too will believe in magic ... def quote_wiggum(lines: list[str]) -> None: ... print('“{}”\n\t— Police Chief Wiggum'.format("\n ".join(lines))) # Call those callables with valid parameters. >>> quote_wiggum(["Okay, folks. Show's over!", " Nothing to see here. Show's…",]) “Okay, folks. Show's over! Nothing to see here. Show's…” — Police Chief Wiggum # Call those callables with invalid parameters. >>> quote_wiggum([b"Oh, my God! A horrible plane crash!", b"Hey, everybody! Get a load of this flaming wreckage!",]) Traceback (most recent call last): File "", line 1, in File "", line 30, in quote_wiggum File "/home/springfield/beartype/lib/python3.9/site-packages/beartype/_decor/_code/_pep/_error/errormain.py", line 220, in get_beartype_violation raise exception_cls( beartype.roar.BeartypeCallHintParamViolation: @beartyped quote_wiggum() parameter lines=[b'Oh, my God! A horrible plane crash!', b'Hey, everybody! Get a load of thi...'] violates type hint list[str], as list item 0 value b'Oh, my God! A horrible plane crash!' not str. # ....................{ MAKE IT SO }.................... # Squash bugs by refining type hints with @beartype validators. >>> from beartype.vale import Is # <---- validator factory >>> from typing import Annotated # <---------------- if Python ≥ 3.9.0 # >>> from typing_extensions import Annotated # <-- if Python < 3.9.0 # Validators are type hints constrained by lambda functions. >>> ListOfStrings = Annotated[ # <----- type hint matching non-empty list of strings ... list[str], # <----------------- type hint matching possibly empty list of strings ... Is[lambda lst: bool(lst)] # <-- lambda matching non-empty object ... ] # Annotate @beartype-decorated callables with validators. >>> @beartype ... def quote_wiggum_safer(lines: ListOfStrings) -> None: ... print('“{}”\n\t— Police Chief Wiggum'.format("\n ".join(lines))) # Call those callables with invalid parameters. >>> quote_wiggum_safer([]) beartype.roar.BeartypeCallHintParamViolation: @beartyped quote_wiggum_safer() parameter lines=[] violates type hint typing.Annotated[list[str], Is[lambda lst: bool(lst)]], as value [] violates validator Is[lambda lst: bool(lst)]. # ....................{ AT ANY TIME }.................... # Type-check anything against any type hint – anywhere at anytime. >>> from beartype.door import ( ... is_bearable, # <-------- like "isinstance(...)" ... die_if_unbearable, # <-- like "assert isinstance(...)" ... ) >>> is_bearable(['The', 'goggles', 'do', 'nothing.'], list[str]) True >>> die_if_unbearable([0xCAFEBEEF, 0x8BADF00D], ListOfStrings) beartype.roar.BeartypeDoorHintViolation: Object [3405692655, 2343432205] violates type hint typing.Annotated[list[str], Is[lambda lst: bool(lst)]], as list index 0 item 3405692655 not instance of str. # ....................{ GO TO PLAID }.................... # Type-check anything in around 1µs (one millionth of a second) – including # this list of one million 2-tuples of NumPy arrays. >>> from beartype.door import is_bearable >>> from numpy import array, ndarray >>> data = [(array(i), array(i)) for i in range(1000000)] >>> %time is_bearable(data, list[tuple[ndarray, ndarray]]) CPU times: user 31 µs, sys: 2 µs, total: 33 µs Wall time: 36.7 µs True Beartype brings Rust_- and `C++`_-inspired `zero-cost abstractions `__ into the lawless world of `dynamically-typed`_ Python by :ref:`enforcing type safety at the granular level of functions and methods ` against :ref:`type hints standardized by the Python community ` in :math:`O(1)` :ref:`non-amortized worst-case time with negligible constant factors `. If the prior sentence was unreadable jargon, see :ref:`our friendly and approachable FAQ for a human-readable synopsis `. Beartype is `portably implemented `__ in `Python 3 `__, `continuously stress-tested `__ via `GitHub Actions`_ **×** tox_ **×** pytest_ **×** Codecov_, and `permissively distributed `__ under the `MIT license`_. Beartype has *no* runtime dependencies, `only one test-time dependency `__, and `only one documentation-time dependency `__. Beartype supports `all actively developed Python versions `__, :ref:`all Python package managers `, and :ref:`multiple platform-specific package managers `. .. # FIXME: Gah! Libraries.io has fallen down and cannot get back up... *AGAIN.* .. # Beartype `powers quality assurance across the Python ecosystem `__. .. # ------------------( TABLES OF CONTENTS )------------------ .. # Project-wide tables of contents (TOCs). See also official documentation on .. # the Sphinx-specific "toctree::" directive: .. # https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-toctree ############### The Typing Tree ############### Welcome to the **Bearpedia** – your one-stop Encyclopedia Beartanica for all things @beartype. It's "typing_ or bust!" as you... .. # Root TOC tree, including: .. # * "... ", an entry self-referentially referring back to this document .. # enabling users to trivially navigate back to this document from .. # elsewhere. The ":hidden:" option adds this entry to the TOC sidebar while .. # omitting this entry from the TOC displayed inline in this document. This .. # is sensible; since any user currently viewing this document has *NO* need .. # to navigate to the current document, this inline TOC omits this entry. .. # :hidden: .. # :titlesonly: .. # :maxdepth: 2 .. toctree:: :caption: Bear with Us Bearpedia Install tl;dr ELI5 API FAQ BigData™ Code Math Moar *Let's type this.* .. # Table of contents, excluding the above document heading. While the .. # official reStructuredText documentation suggests that a language-specific .. # heading will automatically prepend this table, this does *NOT* appear to .. # be the case. Instead, this heading must be explicitly declared. .. contents:: **Bear with Us** :local: .. # ------------------( DESCRIPTION )------------------ ####### License ####### Beartype is `open-source software released `__ under the `permissive MIT license `__. ####### Funding ####### Beartype is financed as a `purely volunteer open-source project via GitHub Sponsors `__, to whom our burgeoning community is eternally indebted. Without your generosity, runtime type-checking would be a shadow of its current hulking bulk. We genuflect before your selfless charity, everyone! Prior official funding sources (*yes, they once existed*) include: #. A `Paul Allen Discovery Center award`_ from the `Paul G. Allen Frontiers Group`_ under the administrative purview of the `Paul Allen Discovery Center`_ at `Tufts University`_ over the period 2015—2018 preceding the untimely death of `Microsoft co-founder Paul Allen `__, during which beartype was maintained as the private ``@type_check`` decorator in the `Bioelectric Tissue Simulation Engine (BETSE) `__. :sup:`Phew!` ############ Contributors ############ Beartype is the work product of volunteer enthusiasm, excess caffeine, and sleepless Wednesday evenings. These brave GitHubbers hurtled `the pull request (PR) gauntlet `__ so that you wouldn't have to: |beartype-contributors| It's a heavy weight they bear. Applaud them as they buckle under the load! ####### History ####### |beartype-stars| .. # ------------------( IMAGES )------------------ .. |beartype-banner| image:: https://raw.githubusercontent.com/beartype/beartype-assets/main/banner/logo.png :target: https://github.com/beartype/beartype :alt: beartype —[ the bare-metal type-checker ]— .. |beartype-contributors| image:: https://contrib.rocks/image?repo=beartype/beartype :target: https://github.com/beartype/beartype/graphs/contributors :alt: Beartype contributors .. |beartype-stars| image:: https://star-history.com/#beartype/beartype&Date :target: https://github.com/beartype/beartype/stargazers :alt: Beartype stargazers .. # ------------------( IMAGES ~ badge )------------------ .. |ci-badge| image:: https://github.com/beartype/beartype/workflows/test/badge.svg :target: https://github.com/beartype/beartype/actions?workflow=test :alt: beartype continuous integration (CI) status .. |codecov-badge| image:: https://codecov.io/gh/beartype/beartype/branch/main/graph/badge.svg?token=E6F4YSY9ZQ :target: https://codecov.io/gh/beartype/beartype :alt: beartype test coverage status .. |rtd-badge| image:: https://readthedocs.org/projects/beartype/badge/?version=latest :target: https://beartype.readthedocs.io/en/latest/?badge=latest :alt: beartype Read The Docs (RTD) status ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������beartype-0.18.5/doc/src/install.rst�����������������������������������������������������������������0000664�0000000�0000000�00000011733�14611135171�0017161�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.. # ------------------( LICENSE )------------------ .. # Copyright (c) 2014-2024 Beartype authors. .. # See "LICENSE" for further details. .. # .. # ------------------( SYNOPSIS )------------------ .. # Child reStructuredText (reST) document detailing installation instructions. .. # ------------------( MAIN )------------------ ####### Install ####### .. # FIXME: Non-ideal. Ideally, this should be fully refactored from the ground .. # up to leverage React-style tabs implemented by the high-quality third-party .. # "sphinx-design" extension, available here: .. # https://github.com/executablebooks/sphinx-design .. # .. # The idea here is that rather than enumerate all instructions as an .. # iterative series of subsections, we instead isolate each platform-specific .. # set of instructions to its own tab. The default tab displays "pip" .. # instructions, of course. Users are then free to switch tabs to an alternate .. # platform listing instructions for that platform. Score one for sanity. Install beartype with pip_, because `PyPI `__ is the `cheese shop `__ and you too enjoy a `fine Venezuelan beaver cheese `__ while mashing disconsolately on your keyboard late on a rain-soaked Friday evening. Wherever expensive milk byproducts ferment, beartype will be there. .. code-block:: bash pip3 install beartype Install beartype with Anaconda_, because package managers named after venomous South American murder reptiles have finally inspired your team to embrace more mammal-friendly packages. Your horoscope also reads: "Avoid reckless ecotourism in places that rain alot." .. code-block:: bash conda config --add channels conda-forge conda install beartype `Commemorate this moment in time `__ with |bear-ified|, our over\ *bear*\ ing project shield. What says quality like `a bear on a badge `__, amirite? .. # ------------------( TABLES OF CONTENTS )------------------ .. # Table of contents, excluding the above document heading. While the .. # official reStructuredText documentation suggests that a language-specific .. # heading will automatically prepend this table, this does *NOT* appear to .. # be the case. Instead, this heading must be explicitly declared. .. contents:: **Bear with Us** :local: .. # ------------------( DESCRIPTION )------------------ ******** Platform ******** Beartype is also installable with platform-specific package managers, because sometimes you just need this thing to work. macOS ##### Let's install beartype with Homebrew_ on macOS_ courtesy `our third-party tap `__: .. code-block:: bash brew install beartype/beartype/beartype Let's install beartype with MacPorts_ on macOS_: .. code-block:: bash sudo port install py-beartype A big bear hug to `our official macOS package maintainer @harens `__ for `packaging beartype for our Apple-appreciating audience `__. Arch Linux ########## Let's install beartype with ``pacman`` on `Arch Linux`_ – where beartype is now `officially packaged `__ in the `Arch User Repository (AUR) `__ itself: .. code-block:: bash git clone https://aur.archlinux.org/python-beartype.git cd python-beartype makepkg -si Truly, Arch Linux has now seen the face of quality assurance. It looks like a grizzled bear with patchy fur, one twitchy eye, and a gimpy leg that spasmodically flails around. Gentoo Linux ############ Let's install beartype with ``emerge`` on `Gentoo Linux`_ – where beartype is now `officially packaged `__ in the Portage tree itself: .. code-block:: bash emerge beartype Source-based Linux distributions are the CPU-bound nuclear option. *What could be simpler?* O_o ***** Badge ***** If you're feeling the quality assurance and want to celebrate, consider signaling that you're now publicly *bear-*\ ified: YummySoft is now |bear-ified|! All this magic and possibly more can be yours with: * **Markdown**: .. code-block:: md YummySoft is now [![bear-ified](https://raw.githubusercontent.com/beartype/beartype-assets/main/badge/bear-ified.svg)](https://beartype.readthedocs.io)! * **reStructuredText**: .. code-block:: rst YummySoft is now |bear-ified|! .. # See https://docutils.sourceforge.io/docs/ref/rst/directives.html#image .. |bear-ified| image:: https://raw.githubusercontent.com/beartype/beartype-assets/main/badge/bear-ified.svg :align: top :target: https://beartype.readthedocs.io :alt: bear-ified * **Raw HTML**: .. code-block:: html YummySoft is now bear-ified! Let a soothing pastel bear give your users the reassuring **OK** sign. beartype-0.18.5/doc/src/math.rst000066400000000000000000000416711461113517100164500ustar00rootroot00000000000000.. # ------------------( LICENSE )------------------ .. # Copyright (c) 2014-2024 Beartype authors. .. # See "LICENSE" for further details. .. # .. # ------------------( SYNOPSIS )------------------ .. # Child reStructuredText (reST) document profiling beartype against competing .. # runtime type checkers as well as exhibiting math underlying beartype. .. # ------------------( MAIN )------------------ ############################## Maths: It's Plural, Apparently ############################## Math(s) time, people. :sup:`it's happening.` .. # ------------------( TABLES OF CONTENTS )------------------ .. # Table of contents, excluding the above document heading. While the .. # official reStructuredText documentation suggests that a language-specific .. # heading will automatically prepend this table, this does *NOT* appear to .. # be the case. Instead, this heading must be explicitly declared. .. contents:: **Bear with Us** :local: .. # ------------------( DESCRIPTION )------------------ .. _math:time: **************** Beartype Timings **************** .. note:: `Additional timings performed by an unbiased third party employed by Cisco Systems support the claims below `__. Notably, beartype is substantially faster than pydantic_ – the most popular competing runtime type-checker – by **several orders of magnitude.** Yes, pydantic_ was Cythonized to native machine code in those timings. Believe! Let's profile beartype against other runtime type-checkers with `a battery of surely fair, impartial, and unbiased use cases `__: .. code-block:: bash $ bin/profile.bash beartype profiler [version]: 0.0.2 python [basename]: python3.9 python [version]: Python 3.9.0 beartype [version]: 0.6.0 typeguard [version]: 2.9.1 ===================================== str ===================================== profiling regime: number of meta-loops: 3 number of loops: 100 number of calls each loop: 100 decoration [none ]: 100 loops, best of 3: 359 nsec per loop decoration [beartype ]: 100 loops, best of 3: 389 usec per loop decoration [typeguard]: 100 loops, best of 3: 13.5 usec per loop decoration + calls [none ]: 100 loops, best of 3: 14.8 usec per loop decoration + calls [beartype ]: 100 loops, best of 3: 514 usec per loop decoration + calls [typeguard]: 100 loops, best of 3: 6.34 msec per loop =============================== Union[int, str] =============================== profiling regime: number of meta-loops: 3 number of loops: 100 number of calls each loop: 100 decoration [none ]: 100 loops, best of 3: 1.83 usec per loop decoration [beartype ]: 100 loops, best of 3: 433 usec per loop decoration [typeguard]: 100 loops, best of 3: 15.6 usec per loop decoration + calls [none ]: 100 loops, best of 3: 17.7 usec per loop decoration + calls [beartype ]: 100 loops, best of 3: 572 usec per loop decoration + calls [typeguard]: 100 loops, best of 3: 10 msec per loop =========================== List[int] of 1000 items =========================== profiling regime: number of meta-loops: 1 number of loops: 1 number of calls each loop: 7485 decoration [none ]: 1 loop, best of 1: 10.1 usec per loop decoration [beartype ]: 1 loop, best of 1: 1.3 msec per loop decoration [typeguard]: 1 loop, best of 1: 41.1 usec per loop decoration + calls [none ]: 1 loop, best of 1: 1.24 msec per loop decoration + calls [beartype ]: 1 loop, best of 1: 18.3 msec per loop decoration + calls [typeguard]: 1 loop, best of 1: 104 sec per loop ============ List[Sequence[MutableSequence[int]]] of 10 items each ============ profiling regime: number of meta-loops: 1 number of loops: 1 number of calls each loop: 7485 decoration [none ]: 1 loop, best of 1: 11.8 usec per loop decoration [beartype ]: 1 loop, best of 1: 1.77 msec per loop decoration [typeguard]: 1 loop, best of 1: 48.9 usec per loop decoration + calls [none ]: 1 loop, best of 1: 1.19 msec per loop decoration + calls [beartype ]: 1 loop, best of 1: 81.2 msec per loop decoration + calls [typeguard]: 1 loop, best of 1: 17.3 sec per loop .. note:: * ``sec`` = seconds. * ``msec`` = milliseconds = 10\ :sup:`-3` seconds. * ``usec`` = microseconds = 10\ :sup:`-6` seconds. * ``nsec`` = nanoseconds = 10\ :sup:`-9` seconds. Timings Overview ################ Beartype is: * **At least twenty times faster** (i.e., 20,000%) and consumes **three orders of magnitude less time** in the worst case than typeguard_ – the only comparable runtime type-checker also compatible with most modern Python versions. * **Asymptotically faster** in the best case than typeguard_, which scales linearly (rather than not at all) with the size of checked containers. * Constant across type hints, taking roughly the same time to check parameters and return values hinted by the builtin type ``str`` as it does to check those hinted by the unified type ``Union[int, str]`` as it does to check those hinted by the container type ``List[object]``. typeguard_ is variable across type hints, taking significantly longer to check ``List[object]`` as as it does to check ``Union[int, str]``, which takes roughly twice the time as it does to check ``str``. Beartype performs most of its work at *decoration* time. The ``@beartype`` decorator consumes most of the time needed to first decorate and then repeatedly call a decorated function. Beartype is thus front-loaded. After paying the upfront fixed cost of decoration, each type-checked call thereafter incurs comparatively little overhead. Conventional runtime type checkers perform most of their work at *call* time. ``@typeguard.typechecked`` and similar decorators consume almost none of the time needed to first decorate and then repeatedly call a decorated function. They're back-loaded. Although the initial cost of decoration is essentially free, each type-checked call thereafter incurs significant overhead. Timings Lower Bound ################### In general, ``@beartype`` adds anywhere from 1µsec (i.e., :math:`10^{-6}` seconds) in the worst case to 0.01µsec (i.e., :math:`10^{-8}` seconds) in the best case of call-time overhead to each decorated callable. This superficially seems reasonable – but is it? Let's delve deeper. Formulaic Formulas: They're Back in Fashion ******************************************* Let's formalize how exactly we arrive at the call-time overheads above. Given any pair of reasonably fair timings between an undecorated callable and its equivalent ``@beartype``\ -decorated callable, let: * :math:`n` be the number of times (i.e., loop iterations) each callable is repetitiously called. * :math:`γ` be the total time in seconds of all calls to that undecorated callable. * :math:`λ` be the total time in seconds of all calls to that ``@beartype``\ -decorated callable. Then the call-time overhead :math:`Δ(n, γ, λ)` added by ``@beartype`` to each call is: .. math:: Δ(n, γ, λ) = \tfrac{λ}{n} - \tfrac{γ}{n} Plugging in :math:`n = 100000`, :math:`γ = 0.0435s`, and :math:`λ = 0.0823s` from `aforementioned third-party timings `__, we see that ``@beartype`` on average adds call-time overhead of 0.388µsec to each decorated call: e.g., .. math:: Δ(100000, 0.0435s, 0.0823s) &= \tfrac{0.0823s}{100000} - \tfrac{0.0435s}{100000} \\ &= 3.8800000000000003 * 10^{-7}s Again, this superficially *seems* reasonable – but is it? Let's delve deeper. Function Call Overhead: The New Glass Ceiling ********************************************* The added cost of calling ``@beartype``\ -decorated callables is a residual artifact of the added cost of **stack frames** (i.e., function and method calls) in Python. The mere act of calling *any* pure-Python callable adds a measurable overhead – even if the body of that callable is just a noop semantically equivalent to that year I just went hard on NG+ in *Persona 5: Royal.* This is the minimal cost of Python function calls. Since Python decorators *almost* always add at least one additional stack frame (typically as a closure call) to the call stack of each decorated call, this measurable overhead is the minimal cost of doing business with Python decorators. Even the fastest possible Python decorator necessarily pays that cost. Our quandary thus becomes: "Is 0.01µsec to 1µsec of call-time overhead reasonable *or* is this sufficiently embarrassing as to bring multigenerational shame upon our entire extended family tree, including that second cousin twice-removed who never sends a kitsch greeting card featuring Santa playing with mischievous kittens at Christmas time?" We can answer that by first inspecting the theoretical maximum efficiency for a pure-Python decorator that performs minimal work by wrapping the decorated callable with a closure that just defers to the decorated callable. This excludes the identity decorator (i.e., decorator that merely returns the decorated callable unmodified), which doesn't actually perform *any* work whatsoever. The fastest *meaningful* pure-Python decorator is thus: .. code-block:: python def fastest_decorator(func): def fastest_wrapper(*args, **kwargs): return func(*args, **kwargs) return fastest_wrapper Replacing ``@beartype`` with ``@fastest_decorator`` in `aforementioned third-party timings `__ then exposes the minimal cost of Python decoration – a lower bound that *all* Python decorators necessarily pay: .. code-block:: bash $ python3.7 < tuple: """Proof of concept code implenting bear-typed args""" assert isinstance(arg01, str) assert isinstance(arg02, int) str_len = len(arg01) + arg02 assert isinstance(str_len, int) return ("bear_bar", str_len,) def main_undecorated(arg01="__undefined__", arg02=0): """Proof of concept code implenting duck-typed args""" assert isinstance(arg01, str) assert isinstance(arg02, int) str_len = len(arg01) + arg02 assert isinstance(str_len, int) return ("duck_bar", str_len,) if __name__=="__main__": num_loops = 100000 decorated_result = timeit('main_decorated("foo", 1)', setup="from __main__ import main_decorated", number=num_loops) print("timeit decorated time: ", round(decorated_result, 4), "seconds") undecorated_result = timeit('main_undecorated("foo", 1)', setup="from __main__ import main_undecorated", number=num_loops) print("timeit undecorated time:", round(undecorated_result, 4), "seconds") EOF timeit decorated time: 0.1185 seconds timeit undecorated time: 0.0889 seconds Again, plugging in :math:`n = 100000`, :math:`γ = 0.0889s`, and :math:`λ = 0.1185s` from the same timings, we see that ``@fastest_decorator`` on average adds call-time overhead of 0.3µsec to each decorated call: e.g., .. math:: Δ(100000, 0.0889s, 0.1185s) &= \tfrac{0.1185s}{100000} - \tfrac{0.0889s}{100000} \\ &= 2.959999999999998 * 10^{-7}s Holy Balls of Flaming Dumpster Fires ************************************ We saw above that ``@beartype`` on average only adds call-time overhead of 0.388µsec to each decorated call. But :math:`0.388µsec - 0.3µsec = 0.088µsec`, so ``@beartype`` only adds 0.1µsec (generously rounding up) of *additional* call-time overhead above and beyond that necessarily added by the fastest possible Python decorator. Not only is ``@beartype`` within the same order of magnitude as the fastest possible Python decorator, it's effectively indistinguishable from the fastest possible Python decorator on a per-call basis. Of course, even a negligible time delta accumulated over 10,000 function calls becomes *slightly* less negligible. Still, it's pretty clear that ``@beartype`` remains the fastest possible runtime type-checker for now and all eternity. *Amen.* But, But... That's Not Good Enough! *********************************** *Yeah.* None of us are best pleased with the performance of the official CPython interpreter anymore, are we? CPython is that geriatric old man down the street that everyone puts up with because they've seen `"Up!" `__ and he means well and he didn't really mean to beat your equally geriatric 20-year-old tomcat with a cane last week. Really, that cat had it comin'. If ``@beartype`` *still* isn't ludicrously speedy enough for you under CPython, we also officially support PyPy_ – where you're likely to extract even more ludicrous speed. ``@beartype`` (and every other runtime type-checker) will *always* be negligibly slower than hard-coded inlined runtime type-checking, thanks to the negligible (but surprisingly high) cost of Python function calls. Where this is unacceptable, PyPy_ is your code's new BFFL. .. _math:math: ************************************ Nobody Expects the Linearithmic Time ************************************ Most runtime type-checkers exhibit :math:`O(n)` time complexity (where :math:`n` is the total number of items recursively contained in a container to be checked) by recursively and repeatedly checking *all* items of *all* containers passed to or returned from *all* calls of decorated callables. Beartype guarantees :math:`O(1)` time complexity by non-recursively but repeatedly checking *one* random item at *all* nesting levels of *all* containers passed to or returned from *all* calls of decorated callables, thus amortizing the cost of deeply checking containers across calls. .. # FIXME: Sphinx is incorrectly rendering this as MathJax, which is just .. # hideous. The culprit is almost certainly the fact that we have yet to .. # actually convert the "Constant Nested Deep Sequence Decoration" section .. # from our "README.rst" to ReadTheDocs (RTD). Once we do, please revive this: .. # (See the subsection on `@beartype-generated code deeply type-checking arbitrarily nested .. # containers in constant time `__ for .. # what this means in practice.) Beartype exploits the `well-known coupon collector's problem `__ applied to abstract trees of nested type hints, enabling us to statistically predict the number of calls required to fully type-check all items of an arbitrary container on average. Formally, let: * :math:`E(T)` be the expected number of calls needed to check all items of a container containing only non-container items (i.e., containing *no* nested subcontainers) either passed to or returned from a ``@beartype``\ -decorated callable. * :math:`γ ≈ 0.5772156649` be the `Euler–Mascheroni constant`_. Then: .. math:: E(T) = n \log n + \gamma n + \frac{1}{2} + O \left( \frac{1}{n} \right) The summation :math:`\frac{1}{2} + O \left( \frac{1}{n} \right) \le 1` is negligible. While non-negligible, the term :math:`\gamma n` grows significantly slower than the term :math:`n \log n`. So this reduces to: .. math:: E(T) = O(n \log n) We now generalize this bound to the general case. When checking a container containing *no* subcontainers, beartype only randomly samples one item from that container on each call. When checking a container containing arbitrarily many nested subcontainers, however, beartype randomly samples one random item from each nesting level of that container on each call. In general, beartype thus samples :math:`h` random items from a container on each call, where :math:`h` is that container's height (i.e., maximum number of edges on the longest path from that container to a non-container leaf item reachable from items directly contained in that container). Since :math:`h ≥ 1`, beartype samples at least as many items each call as assumed in the usual `coupon collector's problem`_ and thus paradoxically takes a fewer number of calls on average to check all items of a container containing arbitrarily many subcontainers as it does to check all items of a container containing *no* subcontainers. Ergo, the expected number of calls :math:`E(S)` needed to check all items of an arbitrary container exhibits the same or better growth rate and remains bound above by at least the same upper bounds – but probably tighter: e.g., .. math:: E(S) = O(E(T)) = O(n \log n) Fully checking a container takes no more calls than that container's size times the logarithm of that size on average. For example, fully checking a **list of 50 integers** is expected to take **225 calls** on average. ...and that's how the QA was won: *eventually.* beartype-0.18.5/doc/src/moar.rst000066400000000000000000000130221461113517100164420ustar00rootroot00000000000000.. # ------------------( LICENSE )------------------ .. # Copyright (c) 2014-2024 Beartype authors. .. # See "LICENSE" for further details. .. # .. # ------------------( SYNOPSIS )------------------ .. # Child reStructuredText (reST) document listing related projects. .. # ------------------( MAIN )------------------ ######## See Also ######## External beartype resources include: * `This list of all open-source PyPI-hosted dependents of this package `__ (i.e., third-party packages requiring beartype as a runtime dependency), kindly furnished by the `Libraries.io package registry `__. Related type-checking resources include: .. _moar:runtime: ********************* Runtime Type Checkers ********************* **Runtime type checkers** (i.e., third-party Python packages dynamically validating callables annotated by type hints at runtime, typically via decorators, function calls, and import hooks) include: .. # Note: intentionally sorted in lexicographic order to avoid bias. +-----------------+---------+---------------+---------------------------+ | package | active | PEP-compliant | time multiplier [#speed]_ | +=================+=========+===============+===========================+ | beartype | **yes** | **yes** | 1 ✕ beartype | +-----------------+---------+---------------+---------------------------+ | enforce_ | no | **yes** | *unknown* | +-----------------+---------+---------------+---------------------------+ | enforce_typing_ | no | **yes** | *unknown* | +-----------------+---------+---------------+---------------------------+ | pydantic_ | **yes** | no | *unknown* | +-----------------+---------+---------------+---------------------------+ | pytypes_ | no | **yes** | *unknown* | +-----------------+---------+---------------+---------------------------+ | typeen_ | no | no | *unknown* | +-----------------+---------+---------------+---------------------------+ | typical_ | **yes** | **yes** | *unknown* | +-----------------+---------+---------------+---------------------------+ | typeguard_ | no | **yes** | 20 ✕ beartype | +-----------------+---------+---------------+---------------------------+ .. [#speed] The *time multliplier* column approximates **how much slower on average than** beartype **that checker is** as :ref:`timed by our profile suite `. A time multiplier of: * "1" means that checker is approximately as fast as beartype, which means that checker is probably beartype itself. * "20" means that checker is approximately twenty times slower than beartype on average. Like `static type checkers `__, runtime type checkers *always* require callables to be annotated by type hints. Unlike `static type checkers `__, runtime type checkers do *not* necessarily comply with community standards; although some do require callers to annotate callables with strictly PEP-compliant type hints, others permit or even require callers to annotate callables with PEP-noncompliant type hints. Runtime type checkers that do so violate: * `PEP 561 -- Distributing and Packaging Type Information `_, which requires callables to be annotated with strictly PEP-compliant type hints. Packages violating `PEP 561`_ even once cannot be type-checked with `static type checkers `__ (e.g., mypy_), unless each such violation is explicitly ignored with a checker-specific filter (e.g., with a mypy_-specific inline type comment). * `PEP 563 -- Postponed Evaluation of Annotations `_, which explicitly deprecates PEP-noncompliant type hints: With this in mind, **uses for annotations incompatible with the aforementioned PEPs** *[i.e., PEPs 484, 544, 557, and 560]* **should be considered deprecated.** *********************** Runtime Data Validators *********************** **Runtime data validators** (i.e., third-party Python packages dynamically validating callables decorated by caller-defined contracts, constraints, and validation routines at runtime) include: .. # Note: intentionally sorted in lexicographic order to avoid bias. * PyContracts_. * contracts_. * covenant_. * dpcontracts_. * icontract_. * pcd_. * pyadbc_. Unlike both `runtime type checkers `__ and `static type checkers `__, most runtime data validators do *not* require callables to be annotated by type hints. Like some `runtime type checkers `__, most runtime data validators do *not* comply with community standards but instead require callers to either: * Decorate callables with package-specific decorators. * Annotate callables with package-specific and thus PEP-noncompliant type hints. .. _moar:static: ******************** Static Type Checkers ******************** **Static type checkers** (i.e., third-party tooling validating Python callable and/or variable types across an application stack at static analysis time rather than Python runtime) include: .. # Note: Intentionally sorted in lexicographic order to avoid subjective bias. * mypy_, Python's official static type checker. * Pyre_, published by Meta. :sup:`...yah.` * pyright_, published by Microsoft. * pytype_, published by Google. beartype-0.18.5/doc/src/pep.rst000066400000000000000000002140271461113517100163000ustar00rootroot00000000000000.. # ------------------( LICENSE )------------------ .. # Copyright (c) 2014-2024 Beartype authors. .. # See "LICENSE" for further details. .. # .. # ------------------( SYNOPSIS )------------------ .. # Child reStructuredText (reST) document gently introducing this project. .. # ------------------( MAIN )------------------ .. _pep:pep: ######## BigData™ ######## .. code-block:: text It's a big bear AAAAAAAAFTER all! It's a big bear AAAAAAAAFTER all! It's a big b——— *squelching sound, then blessed silence* Beartype complies with vast swaths of Python's :mod:`typing` landscape and lint-filled laundry list of `Python Enhancement Proposals (PEPs) `__ – but nobody's perfect. Not even the hulking form of beartype does everything. :sup:`` Let's chart exactly *what* beartype complies with and *when* beartype first did so. Introducing... Beartype's **feature matrix of bloated doom!** It will bore you into stunned disbelief that somebody typed all this. [#rsi]_ .. table:: :align: left +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | category | feature | partial support | full support | +========================+===========================================================+==========================+===========================+ | **Python** | 3.5 | — | **0.1.0**\ —\ **0.3.0** | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | 3.6 | — | **0.1.0**\ —\ **0.10.4** | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | 3.7 | — | **0.1.0**\ —\ **0.15.0** | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | 3.8 | — | **0.1.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | 3.9 | — | **0.3.2**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | 3.10 | — | **0.7.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | 3.11 | — | **0.12.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | 3.12 | — | **0.17.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | **PEP** | :pep:`362 <362>` | *none* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`435 <435>` | **0.16.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`484 <484>` | **0.2.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`526 <526>` | — | **0.15.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`544 <544>` | — | **0.4.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`557 <557>` | **0.10.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`560 <560>` | — | **0.4.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`561 <561>` | — | **0.6.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`563 <563>` | **0.1.1**\ —\ *current* | **0.7.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`570 <570>` | — | **0.10.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`572 <572>` | **0.3.0**\ —\ *current* | **0.4.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`585 <585>` | — | **0.5.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`586 <586>` | — | **0.7.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`589 <589>` | **0.9.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`591 <591>` | **0.13.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`593 <593>` | — | **0.4.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`604 <604>` | — | **0.10.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`612 <612>` | *none* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`613 <613>` | *none* | **0.18.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`621 <621>` | — | **0.15.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`646 <646>` | *none* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`647 <647>` | — | **0.13.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`649 <649>` | *none* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`663 <663>` | **0.16.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`673 <673>` | — | **0.14.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`675 <675>` | **0.14.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`681 <681>` | *none* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`688 <688>` | — | **0.1.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`692 <692>` | *none* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`695 <695>` | **0.17.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`698 <698>` | *none* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`3102 <3102>` | — | **0.1.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`3119 <3119>` | **0.7.0**\ —\ *current* | **0.9.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :pep:`3141 <3141>` | — | **0.1.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | **packaging** | `PyPI `__ | **0.1.0**\ —\ *current* | — | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | `Anaconda `__ | **0.1.0**\ —\ *current* | — | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | `Arch Linux `__ | **0.12.0**\ —\ *current* | — | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | `Gentoo Linux `__ | **0.2.0**\ —\ *current* | — | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | `macOS Homebrew `__ | **0.5.1**\ —\ *current* | — | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | `macOS MacPorts `__ | **0.5.1**\ —\ *current* | — | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | **decoratable** | classes | — | **0.11.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | coroutines | — | **0.9.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | dataclasses | — | **0.10.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | enumerations | **0.16.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | functions | — | **0.1.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | generators (asynchronous) | — | **0.9.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | generators (synchronous) | — | **0.1.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | methods | — | **0.1.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | pseudo-functions (``__call__()``) | — | **0.13.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | **hints** | `covariant `__ | — | **0.1.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | `contravariant `__ | *none* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | absolute forward references | — | **0.14.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | `relative forward references`_ | — | **0.14.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | subscriptable forward references | — | **0.16.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :ref:`tuple unions ` | — | **0.1.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | ``type`` :pep:`alias statements <695>` | **0.17.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | **parameters** | optional | — | **0.18.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | keyword-only | — | **0.1.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | positional-only | — | **0.10.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | variadic keyword | *none* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | variadic positional | — | **0.1.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | **plugin APIs** | ``__instancecheck_str__`` | — | **0.17.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | **shell variables** | :ref:`${BEARTYPE_IS_COLOR} ` | — | **0.16.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | **static checkers** | mypy_ | — | **0.6.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | pyright_ | — | **0.11.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | pytype_ | *none* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | Pyre_ | *none* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | :mod:`beartype` | :func:`~beartype.beartype` | — | **0.1.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :class:`~beartype.BeartypeConf` | — | **0.10.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :class:`~beartype.BeartypeStrategy` | — | **0.10.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | beartype.abby | die_if_unbearable | — | **0.10.0**\ —\ **0.10.4** | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | is_bearable | — | **0.10.0**\ —\ **0.10.4** | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | :mod:`beartype.claw` | :func:`~beartype.claw.beartype_all` | — | **0.15.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :func:`~beartype.claw.beartype_package` | — | **0.15.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :func:`~beartype.claw.beartype_packages` | — | **0.15.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :func:`~beartype.claw.beartype_this_package` | — | **0.15.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :func:`~beartype.claw.beartyping` | — | **0.15.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | :mod:`beartype.door` | :class:`~beartype.door.TypeHint` | — | **0.11.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :class:`~beartype.door.AnnotatedTypeHint` | — | **0.11.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :class:`~beartype.door.CallableTypeHint` | — | **0.11.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :class:`~beartype.door.LiteralTypeHint` | — | **0.11.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :class:`~beartype.door.NewTypeTypeHint` | — | **0.11.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :class:`~beartype.door.TypeVarTypeHint` | — | **0.11.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :class:`~beartype.door.UnionTypeHint` | — | **0.11.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :func:`~beartype.door.die_if_unbearable` | — | **0.11.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :func:`~beartype.door.is_bearable` | — | **0.11.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :func:`~beartype.door.is_subhint` | — | **0.11.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | :mod:`beartype.peps` | :func:`~beartype.peps.resolve_pep563` | — | **0.11.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | :mod:`beartype.typing` | *all* | — | **0.10.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | :mod:`beartype.vale` | :class:`~beartype.vale.Is` | — | **0.7.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :class:`~beartype.vale.IsAttr` | — | **0.7.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :class:`~beartype.vale.IsEqual` | — | **0.7.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :class:`~beartype.vale.IsInstance` | — | **0.10.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :class:`~beartype.vale.IsSubclass` | — | **0.9.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | builtins_ | :data:`None` | — | **0.6.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :data:`NotImplemented` | — | **0.7.1**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :class:`dict` | – | **0.18.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :class:`frozenset` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :class:`list` | — | **0.5.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :class:`set` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :class:`tuple` | — | **0.5.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :class:`type` | **0.5.0**\ —\ *current* | **0.9.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | :mod:`collections` | :obj:`~collections.ChainMap` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.Counter` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.OrderedDict` | – | **0.18.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.defaultdict` | – | **0.18.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.deque` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | :mod:`collections.abc` | :obj:`~collections.abc.AsyncGenerator` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.abc.AsyncIterable` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.abc.AsyncIterator` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.abc.Awaitable` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.abc.Buffer` | — | **0.1.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.abc.ByteString` | — | **0.5.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.abc.Callable` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.abc.Collection` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.abc.Container` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.abc.Coroutine` | **0.5.0**\ —\ *current* | **0.9.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.abc.Generator` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.abc.ItemsView` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.abc.Iterable` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.abc.Iterator` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.abc.KeysView` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.abc.Mapping` | – | **0.18.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.abc.MappingView` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.abc.MutableMapping` | – | **0.18.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.abc.MutableSequence` | — | **0.5.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.abc.MutableSet` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.abc.Reversible` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.abc.Sequence` | — | **0.5.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.abc.Set` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~collections.abc.ValuesView` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | :mod:`contextlib` | :obj:`~contextlib.AbstractAsyncContextManager` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~contextlib.AbstractContextManager` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~contextlib.contextmanager` | — | **0.15.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | :mod:`dataclasses` | :obj:`~dataclasses.InitVar` | — | **0.10.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~dataclasses.dataclass` | **0.10.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | :mod:`enum` | :obj:`~enum.Enum` | **0.16.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | equinox_ | *all* | — | **0.17.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~enum.StrEnum` | **0.16.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | :mod:`functools` | :obj:`~functools.lru_cache` | — | **0.15.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | nuitka_ | *all* | — | **0.12.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | nptyping_ | *all* | — | **0.17.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | numpy.typing_ | numpy.typing.NDArray_ | — | **0.8.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | :mod:`os` | :obj:`~os.PathLike` | **0.17.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | pandera_ | *all* | **0.13.0**\ —\ *current* | — | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | :mod:`re` | :obj:`~re.Match` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~re.Pattern` | **0.5.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | sphinx_ | sphinx.ext.autodoc_ | — | **0.9.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | :mod:`typing` | :obj:`~typing.AbstractSet` | **0.2.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Annotated` | — | **0.4.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Any` | — | **0.2.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.AnyStr` | **0.4.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.AsyncContextManager` | **0.4.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.AsyncGenerator` | **0.2.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.AsyncIterable` | **0.2.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.AsyncIterator` | **0.2.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Awaitable` | **0.2.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.BinaryIO` | **0.4.0**\ —\ *current* | **0.10.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.ByteString` | — | **0.2.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Callable` | **0.2.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.ChainMap` | **0.2.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.ClassVar` | *none* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Collection` | **0.2.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Concatenate` | *none* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Container` | **0.2.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.ContextManager` | **0.4.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Coroutine` | **0.2.0**\ —\ *current* | **0.9.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Counter` | **0.2.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.DefaultDict` | – | **0.18.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Deque` | **0.2.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Dict` | – | **0.18.0**\ —\ *current** | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Final` | **0.13.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.ForwardRef` | **0.4.0**\ —\ *current* | **0.16.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.FrozenSet` | **0.2.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Generator` | **0.2.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Generic` | — | **0.4.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Hashable` | **0.2.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.IO` | **0.4.0**\ —\ *current* | **0.10.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.ItemsView` | **0.2.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Iterable` | **0.2.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Iterator` | **0.2.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.KeysView` | **0.2.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.List` | **0.2.0**\ —\ *current* | **0.3.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Literal` | — | **0.7.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.LiteralString` | **0.14.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Mapping` | – | **0.18.0**\ —\ *current** | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.MappingView` | **0.2.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Match` | **0.4.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.MutableMapping` | – | **0.18.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.MutableSequence` | **0.2.0**\ —\ *current* | **0.3.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.MutableSet` | **0.2.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.NamedTuple` | **0.1.0**\ —\ *current* | **0.12.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.NewType` | — | **0.4.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.NoReturn` | — | **0.4.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Optional` | — | **0.2.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.OrderedDict` | – | **0.18.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.ParamSpec` | *none* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.ParamSpecArgs` | *none* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.ParamSpecKwargs` | *none* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Pattern` | **0.4.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Protocol` | — | **0.4.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Reversible` | **0.2.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Self` | — | **0.14.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Sequence` | **0.2.0**\ —\ *current* | **0.3.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Set` | **0.2.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Sized` | — | **0.2.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.SupportsAbs` | — | **0.4.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.SupportsBytes` | — | **0.4.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.SupportsComplex` | — | **0.4.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.SupportsFloat` | — | **0.4.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.SupportsIndex` | — | **0.4.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.SupportsInt` | — | **0.4.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.SupportsRound` | — | **0.4.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Text` | — | **0.1.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.TextIO` | **0.4.0**\ —\ *current* | **0.10.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Tuple` | **0.2.0**\ —\ *current* | **0.4.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Type` | **0.2.0**\ —\ *current* | **0.9.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.TypeAlias` | *none* | **0.18.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.TypeGuard` | — | **0.13.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.TypedDict` | **0.9.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.TypeVar` | **0.4.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.TypeVarTuple` | *none* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Union` | — | **0.2.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.Unpack` | *none* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.ValuesView` | **0.2.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.TYPE_CHECKING` | — | **0.5.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.final` | *none* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.no_type_check` | — | **0.5.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | | :obj:`~typing.override` | *none* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | typing_extensions_ | *all attributes* | — | **0.8.0**\ —\ *current* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ | :mod:`weakref` | :obj:`~weakref.ref` | **0.17.0**\ —\ *current* | *none* | +------------------------+-----------------------------------------------------------+--------------------------+---------------------------+ .. [#rsi] They now suffer crippling RSI so that you may appear knowledgeable before colleagues. beartype-0.18.5/doc/src/tldr.rst000066400000000000000000000434531461113517100164640ustar00rootroot00000000000000.. # ------------------( LICENSE )------------------ .. # Copyright (c) 2014-2024 Beartype authors. .. # See "LICENSE" for further details. .. # .. # ------------------( SYNOPSIS )------------------ .. # Child reStructuredText (reST) document gently introducing this project. .. # ------------------( MAIN )------------------ .. _tldr:tldr: ############################# Too Long; Didn't Read (tl;dr) ############################# Let's type-check like `greased lightning`_! Thanks to cheatsheets like this, you no longer have to know how to use software to use software. ``\o/`` .. code-block:: python # ..................{ IMPORTS }.................. # Import the core @beartype decorator. from beartype import beartype # Import type hint factories from "beartype.typing", a stand-in replacement # for the standard "typing" module providing improved forward compatibility # with future Python releases. For example: # * "beartype.typing.Set is set" under Python ≥ 3.9 to satisfy PEP 585. # * "beartype.typing.Set is typing.Set" under Python < 3.9 to satisfy PEP 484. from beartype import typing # Or, directly import these factories from the standard "typing" module. Note # that PEP 585 deprecated many of these under Python ≥ 3.9, where @beartype # now emits non-fatal deprecation warnings at decoration time. See also: # https://docs.python.org/3/library/typing.html import typing # Or, directly import PEP 585 type hints. Note this requires Python ≥ 3.9. from collections import abc # Import backported type hint factories from "typing_extensions", improving # portability across Python versions (e.g., "typing.Literal" needs Python ≥ # 3.9, but "typing_extensions.Literal" only needs Python ≥ 3.6). import typing_extensions # Import beartype-specific types to annotate callables with. from beartype.cave import NoneType, NoneTypeOr, RegexTypes, ScalarTypes # Import official abstract base classes (ABCs), too. from numbers import Integral, Real # Import user-defined classes, too. from my_package.my_module import MyClass # ..................{ TYPEVARS }.................. # PEP 484 type variable. While @beartype only partially supports type # variables at the moment, @beartype 1.0.0.0.0.0.0.0 is expected to fully # support type variables. T = typing.TypeVar('T') # ..................{ FUNCTIONS }.................. # Decorate functions with @beartype and... @beartype def my_function( # Annotate builtin types as is. param_must_satisfy_builtin_type: str, # Annotate user-defined classes as is, too. Note this covariantly # matches all instances of both this class and subclasses of this class. param_must_satisfy_user_type: MyClass, # Annotate PEP 604 type hint unions. Note this requires Python ≥ 3.10. param_must_satisfy_pep604_union: dict | tuple | None, # Annotate PEP 484 type hint unions. All Python versions support this. param_must_satisfy_pep484_union: typing.Union[ dict, T, tuple[MyClass, ...]], # Annotate PEP 593 metatypes, indexed by a type hint followed by zero or # more arbitrary objects. See "VALIDATORS" below for real-world usage. param_must_satisfy_pep593: typing.Annotated[ typing.Set[int], range(5), True], # Annotate PEP 586 literals, indexed by either a boolean, byte string, # integer, string, "enum.Enum" member, or "None". param_must_satisfy_pep586: typing.Literal[ 'This parameter must equal this string.'], # Annotate PEP 585 builtin container types, indexed by the types of items # these containers are expected to contain. param_must_satisfy_pep585_builtin: list[str], # Annotate PEP 585 standard collection types, indexed too. param_must_satisfy_pep585_collection: abc.MutableSequence[str], # Annotate PEP 544 protocols, either unindexed or indexed by one or more # type variables. param_must_satisfy_pep544: typing.SupportsRound[T], # Annotate PEP 484 non-standard container types defined by the "typing" # module, optionally indexed and only usable as type hints. Note that # these types have all been deprecated by PEP 585 under Python ≥ 3.9. See # also: https://docs.python.org/3/library/typing.html param_must_satisfy_pep484_typing: typing.List[int], # Annotate PEP 484 relative forward references dynamically resolved at # call time as unqualified classnames relative to the current submodule. # Note this class is defined below and that beartype-specific absolute # forward references are also supported. param_must_satisfy_pep484_relative_forward_ref: 'MyOtherClass', # Annotate PEP types indexed by relative forward references. Forward # references are supported everywhere standard types are. param_must_satisfy_pep484_indexed_relative_forward_ref: ( typing.Union['MyPep484Generic', set['MyPep585Generic']]), # Annotate beartype-specific types predefined by the beartype cave. param_must_satisfy_beartype_type_from_cave: NoneType, # Annotate beartype-specific unions of types as tuples. param_must_satisfy_beartype_union: (dict, MyClass, int), # Annotate beartype-specific unions predefined by the beartype cave. param_must_satisfy_beartype_union_from_cave: ScalarTypes, # Annotate beartype-specific unions concatenated together. param_must_satisfy_beartype_union_concatenated: ( abc.Iterator,) + ScalarTypes, # Annotate beartype-specific absolute forward references dynamically # resolved at call time as fully-qualified "."-delimited classnames. param_must_satisfy_beartype_absolute_forward_ref: ( 'my_package.my_module.MyClass'), # Annotate beartype-specific forward references in unions of types, too. param_must_satisfy_beartype_union_with_forward_ref: ( abc.Iterable, 'my_package.my_module.MyOtherClass', NoneType), # Annotate PEP 604 optional types. Note this requires Python ≥ 3.10. param_must_satisfy_pep604_optional: float | bytes = None, # Annotate PEP 484 optional types. All Python versions support this. param_must_satisfy_pep484_optional: typing.Optional[float, bytes] = None, # Annotate beartype-specific optional types. param_must_satisfy_beartype_type_optional: NoneTypeOr[float] = None, # Annotate beartype-specific optional unions of types. param_must_satisfy_beartype_tuple_optional: NoneTypeOr[float, int] = None, # Annotate variadic positional arguments as above, too. *args: ScalarTypes + (Real, 'my_package.my_module.MyScalarType'), # Annotate keyword-only arguments as above, too. param_must_be_passed_by_keyword_only: abc.Sequence[ typing.Union[bool, list[str]]], # Annotate return types as above, too. ) -> Union[Integral, 'MyPep585Generic', bool]: return 0xDEADBEEF # Decorate coroutines as above but returning a coroutine type. @beartype async def my_coroutine() -> abc.Coroutine[None, None, int]: from async import sleep await sleep(0) return 0xDEFECA7E # ..................{ GENERATORS }.................. # Decorate synchronous generators as above but returning a synchronous # generator type. @beartype def my_sync_generator() -> abc.Generator[int, None, None]: yield from range(0xBEEFBABE, 0xCAFEBABE) # Decorate asynchronous generators as above but returning an asynchronous # generator type. @beartype async def my_async_generator() -> abc.AsyncGenerator[int, None]: from async import sleep await sleep(0) yield 0x8BADF00D # ..................{ CLASSES }.................. # Decorate classes with @beartype – which then automatically decorates all # methods and properties of those classes with @beartype. @beartype class MyOtherClass: # Annotate instance methods as above without annotating "self". def __init__(self, scalar: ScalarTypes) -> None: self._scalar = scalar # Annotate class methods as above without annotating "cls". @classmethod def my_classmethod(cls, regex: RegexTypes, wut: str) -> ( Callable[(), str]): import re return lambda: re.sub(regex, 'unbearable', str(cls._scalar) + wut) # Annotate static methods as above, too. @staticmethod def my_staticmethod(callable: abc.Callable[[str], T], text: str) -> T: return callable(text) # Annotate property getter methods as above, too. @property def my_gettermethod(self) -> abc.Iterator[int]: return range(0x0B00B135 + int(self._scalar), 0xB16B00B5) # Annotate property setter methods as above, too. @my_gettermethod.setter def my_settermethod(self, bad: Integral = 0xBAAAAAAD) -> None: self._scalar = bad if bad else 0xBADDCAFE # Annotate methods accepting or returning instances of the class # currently being declared with relative forward references. def my_selfreferential_method(self) -> list['MyOtherClass']: return [self] * 42 # ..................{ GENERICS }.................. # Decorate PEP 585 generics with @beartype. Note this requires Python ≥ 3.9. @beartype class MyPep585Generic(tuple[int, float]): def __new__(cls, integer: int, real: float) -> tuple[int, float]: return tuple.__new__(cls, (integer, real)) # Decorate PEP 484 generics with @beartype, too. @beartype class MyPep484Generic(typing.Tuple[str, ...]): def __new__(cls, *args: str) -> typing.Tuple[str, ...]: return tuple.__new__(cls, args) # ..................{ PROTOCOLS }.................. # PEP 544 protocol referenced below in type hints. Note this requires Python # ≥ 3.8 and that protocols *MUST* be explicitly decorated by the # @runtime_checkable decorator to be usable with @beartype. @typing.runtime_checkable # <---- mandatory boilerplate line. it is sad. class MyProtocol(typing.Protocol): def my_method(self) -> str: return ( 'Objects satisfy this protocol only if their classes ' 'define a method with the same signature as this method.' ) # ..................{ DATACLASSES }.................. # Import the requisite machinery. Note this requires Python ≥ 3.8. from dataclasses import dataclass, InitVar # Decorate dataclasses with @beartype, which then automatically decorates all # methods and properties of those dataclasses with @beartype – including the # __init__() constructors created by @dataclass. Fields are type-checked only # at instantiation time. Fields are *NOT* type-checked when reassigned. # # Decoration order is significant. List @beartype before @dataclass, please. @beartype @dataclass class MyDataclass(object): # Annotate fields with type hints. field_must_satisfy_builtin_type: InitVar[str] field_must_satisfy_pep604_union: str | None = None # Annotate methods as above. def __post_init__(self, field_must_satisfy_builtin_type: str) -> None: if self.field_must_satisfy_pep604_union is None: self.field_must_satisfy_pep604_union = ( field_must_satisfy_builtin_type) # ..................{ NAMED TUPLES }.................. # Import the requisite machinery. from typing import NamedTuple # Decorate named tuples with @beartype. @beartype class MyNamedTuple(NamedTuple): # Annotate fields with type hints. field_must_satisfy_builtin_type: str # ..................{ CONFIGURATION }.................. # Import beartype's configuration API to configure runtime type-checking. from beartype import BeartypeConf, BeartypeStrategy # Dynamically create your own @beartype decorator, configured for your needs. bugbeartype = beartype(conf=BeartypeConf( # Optionally disable or enable output of colors (i.e., ANSI escape # sequences) in type-checking violations via this tri-state boolean: # * "None" conditionally enables colors when standard output is attached # to an interactive terminal. [DEFAULT] # * "True" unconditionally enables colors. # * "False" unconditionally disables colors. is_color=False, # <-- disable color entirely # Optionally enable developer-friendly debugging. is_debug=True, # Optionally enable PEP 484's implicit numeric tower by: # * Expanding all "float" type hints to "float | int". # * Expanding all "complex" type hints to "complex | float | int". is_pep484_tower=True, # Optionally switch to a different type-checking strategy: # * "BeartypeStrategy.O1" type-checks in O(1) constant time. [DEFAULT] # * "BeartypeStrategy.On" type-checks in O(n) linear time. # (Currently unimplemented but roadmapped for a future release.) # * "BeartypeStrategy.Ologn" type-checks in O(log n) logarithmic time. # (Currently unimplemented but roadmapped for a future release.) # * "strategy=BeartypeStrategy.O0" disables type-checking entirely. strategy=BeartypeStrategy.On, # <-- enable linear-time type-checking )) # Decorate with your decorator instead of the vanilla @beartype decorator. @bugbeartype def muh_configured_func(list_checked_in_On_time: list[float]) -> set[str]: return set(str(item) for item in list_checked_in_On_time) # ..................{ VALIDATORS }.................. # Import beartype's PEP 593 validator API to validate arbitrary constraints. # Note this requires either: # * Python ≥ 3.9.0. # * typing_extensions ≥ 3.9.0.0. from beartype.vale import Is, IsAttr, IsEqual from typing import Annotated # <--------------- if Python ≥ 3.9.0 #from typing_extensions import Annotated # <--- if Python < 3.9.0 # Import third-party packages to validate. import numpy as np # Validator matching only two-dimensional NumPy arrays of 64-bit floats, # specified with a single caller-defined lambda function. NumpyArray2DFloat = Annotated[np.ndarray, Is[ lambda arr: arr.ndim == 2 and arr.dtype == np.dtype(np.float64)]] # Validator matching only one-dimensional NumPy arrays of 64-bit floats, # specified with two declarative expressions. Although verbose, this # approach generates optimal reusable code that avoids function calls. IsNumpyArray1D = IsAttr['ndim', IsEqual[1]] IsNumpyArrayFloat = IsAttr['dtype', IsEqual[np.dtype(np.float64)]] NumpyArray1DFloat = Annotated[np.ndarray, IsNumpyArray1D, IsNumpyArrayFloat] # Validator matching only empty NumPy arrays, equivalent to but faster than: # NumpyArrayEmpty = Annotated[np.ndarray, Is[lambda arr: arr.size != 0]] IsNumpyArrayEmpty = IsAttr['size', IsEqual[0]] NumpyArrayEmpty = Annotated[np.ndarray, IsNumpyArrayEmpty] # Validator composed with standard operators from the above validators, # permissively matching all of the following: # * Empty NumPy arrays of any dtype *except* 64-bit floats. # * Non-empty one- and two-dimensional NumPy arrays of 64-bit floats. NumpyArrayEmptyNonFloatOrNonEmptyFloat1Or2D = Annotated[np.ndarray, # "&" creates a new validator matching when both operands match, while # "|" creates a new validator matching when one or both operands match; # "~" creates a new validator matching when its operand does not match. # Group operands to enforce semantic intent and avoid precedence woes. (IsNumpyArrayEmpty & ~IsNumpyArrayFloat) | ( ~IsNumpyArrayEmpty & IsNumpyArrayFloat ( IsNumpyArray1D | IsAttr['ndim', IsEqual[2]] ) ) ] # Decorate functions accepting validators like usual and... @beartype def my_validated_function( # Annotate validators just like standard type hints. param_must_satisfy_validator: NumpyArrayEmptyOrNonemptyFloat1Or2D, # Combine validators with standard type hints, too. ) -> list[NumpyArrayEmptyNonFloatOrNonEmptyFloat1Or2D]: return ( [param_must_satisfy_validator] * 0xFACEFEED if bool(param_must_satisfy_validator) else [np.array([i], np.dtype=np.float64) for i in range(0xFEEDFACE)] ) # ..................{ NUMPY }.................. # Import NumPy-specific type hints validating NumPy array constraints. Note: # * These hints currently only validate array dtypes. To validate additional # constraints like array shapes, prefer validators instead. See above. # * This requires NumPy ≥ 1.21.0 and either: # * Python ≥ 3.9.0. # * typing_extensions ≥ 3.9.0.0. from numpy.typing import NDArray # NumPy type hint matching all NumPy arrays of 64-bit floats. Internally, # beartype reduces this to the equivalent validator: # NumpyArrayFloat = Annotated[ # np.ndarray, IsAttr['dtype', IsEqual[np.dtype(np.float64)]]] NumpyArrayFloat = NDArray[np.float64] # Decorate functions accepting NumPy type hints like usual and... @beartype def my_numerical_function( # Annotate NumPy type hints just like standard type hints. param_must_satisfy_numpy: NumpyArrayFloat, # Combine NumPy type hints with standard type hints, too. ) -> tuple[NumpyArrayFloat, int]: return (param_must_satisfy_numpy, len(param_must_satisfy_numpy)) Beartype: *it just sorta works.* beartype-0.18.5/mypy000077500000000000000000000065321461113517100143520ustar00rootroot00000000000000#!/usr/bin/env bash # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. # # --------------------( SYNOPSIS )-------------------- # Bash shell script statically type-checking this project with mypy, passing # sane default options suitable for interactive terminal testing and otherwise # passing all passed arguments as is to the "tox" command. # # This script is defined as a Bash rather than Bourne script purely for the # canonical ${BASH_SOURCE} string global, reliably providing the absolute # pathnames of this script and hence this script's directory. # # --------------------( CAVEATS )-------------------- # *THIS SCRIPT ONLY STATICALLY TYPE-CHECKS THIS PROJECT'S MAIN CODEBASE.* # Namely, this script avoids statically type-checking this project's test suite # as well. Why? Because we only statically type-check this project's codebase # for PEP 561-compliance and downstream consumers themselves statically # type-checking codebases dependent on this project. No such concerns apply to # this test suite, which is clearly *NEVER* intended for external reuse. # # This test suite also conditionally statically type-checks this project's # codebase as a functional test when the "mypy" package is importable. While # useful as a sanity check, this script is typically preferable from the # human-readable persective as its output is significantly more readable than # that captured by that functional test. # ....................{ PREAMBLE }.................... # Enable strictness for sanity. set -e # ....................{ FUNCTIONS }.................... # str canonicalize_path(str pathname) # # Canonicalize the passed pathname. function canonicalize_path() { # Validate and localize all passed arguments. (( $# == 1 )) || { echo 'Expected exactly one argument.' 1>&2 return 1 } local pathname="${1}" # The "readlink" command's GNU-specific "-f" option would be preferable but # is unsupported by macOS's NetBSD-specific version of "readlink". Instead, # just defer to Python for portability. command python3 -c " import os, sys print(os.path.realpath(os.path.expanduser(sys.argv[1])))" "${pathname}" } # ....................{ PATHS }.................... # Absolute or relative filename of this script. script_filename="$(canonicalize_path "${BASH_SOURCE[0]}")" # Absolute or relative dirname of the directory directly containing this # script, equivalent to the top-level directory for this project. script_dirname="$(dirname "${script_filename}")" # ....................{ MAIN }.................... # Temporarily change the current working directory to that of this project. pushd "${script_dirname}" >/dev/null # Statically type-check this project's codebase with all passed arguments. command python3 -m mypy "${@}" # command python3.8 -m mypy "${@}" # command python3.9 -m mypy "${@}" # command python3.10 -m mypy "${@}" # command python3.11 -m mypy "${@}" # 0-based exit code reported by the prior command. exit_code=$? # Revert the current working directory to the prior such directory. popd >/dev/null # Report the same exit code from this script. exit ${exit_code} beartype-0.18.5/mypy.ini000066400000000000000000000046301461113517100151220ustar00rootroot00000000000000# --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. # # --------------------( SYNOPSIS )-------------------- # Project-wide mypy configuration, applied to all invocations of the mypy # static type-checker within this project. # # --------------------( SEE ALSO )-------------------- # * https://mypy.readthedocs.io/en/stable/config_file.html # Official documentation on this file format. # ....................{ GLOBAL }................... # The following mypy-specific section specifier is mandatory, despite this # file's unambiguous basename of ".mypy.ini". One is enraged by bureaucracy! [mypy] # Comma-separated string listing the pathnames of all project paths to be # checked by mypy by default if none are explicitly passed on the command line. files = beartype/ # To quote mypy's official CLI documentation: # "By default, imported values to a module are treated as exported and mypy # allows other modules to import them. This flag changes the behavior to # not re-export unless the item is imported using from-as or is included # in __all__. Note this is always treated as enabled for stub files." # We don't pretend to understand the low-level nuance between those two # behaviours, but now explicitly enable the latter behaviour to resolve #57. no_implicit_reexport = True # Display machine-readable "["- and "]"-bracketed error codes in *ALL* # mypy-specific error messages. This option is disabled by default, which is # awful, because these codes are the *ONLY* means of explicitly ignoring # specific mypy errors with "# type: ignore[{error_code}]" comments littered # throughout this project's codebase. Type-checked serenity now! show_error_codes = True # ....................{ LIB }................... # Implicitly ignore missing type hints in third-party optional dependencies, an # automated alternative to literring our codebase with "# type: ignore[import]" # pragmas on every import from these dependencies. See also: # https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-type-hints-for-third-party-library [mypy-importlib.metadata.*] ignore_missing_imports = True [mypy-numpy.*] ignore_missing_imports = True [mypy-pkg_resources.*] ignore_missing_imports = True beartype-0.18.5/pyproject.toml000066400000000000000000000063131461113517100163370ustar00rootroot00000000000000# --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. # # --------------------( SYNOPSIS )-------------------- # Project-wide packager-agnostic configuration. Unlike all other top-level # configuration files (e.g., "setup.py", "MANIFEST.in") specific to some # utility in Python's package management ecosystem (e.g., "pip", "setuptools"), # this file strictly conforms to a standards-compliant PEP and hence # generically applies to *ALL* such utilities. # # Welcome to project distribution hell, where only twenty distinct files in # twenty distinct formats suffice to distribute a single project. # # --------------------( MOTIVATION )-------------------- # This configuration is now required in various edge cases to avoid fatal errors # under Python's modern build toolchain. Notably, "pip" now refuses to install # this project under containerized environments (e.g., Docker, which the popular # documentation host ReadTheDocs (RTD) leverages) with extremely verbose and # thus mostly unreadable fatal errors resembling: # Processing /home/docs/checkouts/readthedocs.org/user_builds/beartype/checkouts/latest # Preparing metadata (setup.py): started # Preparing metadata (setup.py): finished with status 'error' # error: subprocess-exited-with-error # # × python setup.py egg_info did not run successfully. # │ exit code: 1 # ╰─> [1 lines of output] # ERROR: Can not execute `setup.py` since setuptools is not available in the build environment. # [end of output] # # note: This error originates from a subprocess, and is likely not a problem with pip. # error: metadata-generation-failed # # × Encountered error while generating package metadata. # ╰─> See above for output. # # note: This is an issue with the package mentioned above, not pip. # hint: See above for details. # # --------------------( SEE ALSO )-------------------- # * https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html # Official "setuptools" documentation on integrating "setuptools" with PEP 621 # (i.e., this file). # * https://snarky.ca/clarifying-pep-518 # "Clarifying PEP 518 (a.k.a. pyproject.toml)", a human-readable article from # one of the principal authors of the "pyproject.toml" standard. # ....................{ BUILDING }.................... [build-system] # List of all Python packages required to build (i.e., install) this project # from both codebase tarballs and binary wheels. # # Note that: # * Setuptools 50.0 is fundamentally broken in numerous ways (including ways # that both break and do not break installation of this project) and *MUST* # thus be blacklisted. See also: # https://github.com/pypa/setuptools/issues/2350 # https://github.com/pypa/setuptools/issues/2352 # https://github.com/pypa/setuptools/issues/2353 requires = ['setuptools !=50.0'] # Explicitly notify "pip" that we leverage the top-level "setuptools"-backed # "setup.py" script as our installation infrastructure. build-backend = 'setuptools.build_meta' beartype-0.18.5/pyright000077500000000000000000000071621461113517100150420ustar00rootroot00000000000000#!/usr/bin/env bash # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. # # --------------------( SYNOPSIS )-------------------- # Bash shell script statically type-checking this project with pyright, passing # sane default options suitable for interactive terminal testing and otherwise # passing all passed arguments as is to the "tox" command. # # This script is defined as a Bash rather than Bourne script purely for the # canonical ${BASH_SOURCE} string global, reliably providing the absolute # pathnames of this script and hence this script's directory. # # --------------------( CAVEATS )-------------------- # *tHIS SCRIPT ONLY STATICALLY TYPE-CHECKS THIS PROJECT'S CODEBASE.* Notably, # this script avoids statically type-checking this project's test suite as # well. Why? Because we only statically type-check this project's codebase for # PEP 561-compliance and downstream consumers themselves statically # type-checking beartype-dependent codebases. No such concerns apply to this # test suite, which is clearly *NEVER* intended for external reuse. # # This test suite also conditionally statically type-checks this project's # codebase as a functional test when the "pyright" command is in the current # ${PATH} shell environment variable. While useful as a sanity check, this # script is typically preferable from the human-readable persective as its # output is significantly more readable than that captured by that test. # ....................{ PREAMBLE }.................... # Enable strictness for sanity. set -e # ....................{ FUNCTIONS }.................... # str canonicalize_path(str pathname) # # Canonicalize the passed pathname. function canonicalize_path() { # Validate and localize all passed arguments. (( $# == 1 )) || { echo 'Expected exactly one argument.' 1>&2 return 1 } local pathname="${1}" # The "readlink" command's GNU-specific "-f" option would be preferable but # is unsupported by macOS's NetBSD-specific version of "readlink". Instead, # just defer to Python for portability. command python3 -c " import os, sys print(os.path.realpath(os.path.expanduser(sys.argv[1])))" "${pathname}" } # ....................{ PATHS }.................... # Absolute or relative filename of this script. script_filename="$(canonicalize_path "${BASH_SOURCE[0]}")" # Absolute or relative dirname of the directory directly containing this # script, equivalent to the top-level directory for this project. script_dirname="$(dirname "${script_filename}")" # ....................{ MAIN }.................... # Temporarily change the current working directory to that of this project. pushd "${script_dirname}" >/dev/null #FIXME: Note that we *COULD* additionally pass the "--verifytypes" option, which #exposes further "pyright" compliants. Let's avoid doing so until someone #explicitly requests we do so, please. This has dragged on long enough! *HURK* # Statically type-check this project's codebase with all passed arguments. command pyright beartype "${@}" # command pyright --pythonversion 3.8 beartype "${@}" # command pyright --pythonversion 3.10 beartype "${@}" # command pyright --pythonversion 3.11 beartype "${@}" # 0-based exit code reported by the prior command. exit_code=$? # Revert the current working directory to the prior such directory. popd >/dev/null # Report the same exit code from this script. exit ${exit_code} beartype-0.18.5/pyrightconfig.json000066400000000000000000000001211461113517100171610ustar00rootroot00000000000000{ "include": ["beartype"], "exclude": ["**/__pycache__"], "stubPath": "" } beartype-0.18.5/pytest000077500000000000000000000163601461113517100147040ustar00rootroot00000000000000#!/usr/bin/env bash # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. # # --------------------( SYNOPSIS )-------------------- # Bash shell script wrapping this project's pytest-based test suite, passing # sane default options suitable for interactive terminal testing and otherwise # passing all passed arguments as is to the "pytest" command. # # This script is defined as a Bash rather than Bourne script purely for the # canonical ${BASH_SOURCE} string global, reliably providing the absolute # pathnames of this script and hence this script's directory. # # --------------------( CAVEATS )-------------------- # *THE HIGHER-LEVEL "tox" SCRIPT SHOULD TYPICALLY BE RUN INSTEAD.* This # lower-level script only exercises this project against the single Python # interpreter associated with the "pytest" command and is thus suitable *ONLY* # as a rapid sanity check. Meanwhile, the higher-level "tox" command exercises # this project against all installed Python interpreters and is thus suitable # as a full-blown correctness check (e.g., before submitting pull requests). # ....................{ PREAMBLE }.................... # Enable strictness for sanity. set -e # ....................{ ARRAYS }.................... # Array of all shell words with which to invoke Python. Dismantled, this is: # * "-X dev", enabling the Python Development Mode (PDM). See also commentary # for the ${PYTHONDEVMODE} shell variable in the "tox.ini" file. PYTHON_ARGS=( command python3 -X dev ) # PYTHON_ARGS=( command python3.8 -X dev ) # PYTHON_ARGS=( command python3.9 -X dev ) # PYTHON_ARGS=( command python3.10 -X dev ) # PYTHON_ARGS=( command python3.11 -X dev ) # PYTHON_ARGS=( command python3.12 -X dev ) # PYTHON_ARGS=( command pypy3.7 -X dev ) # Array of all shell words to be passed to "python3" below. PYTEST_ARGS=( pytest # Enable colour output to ensure colour under piped pagers (e.g., "less"). '--color=yes' # Halt testing on the first failure for interactive tests. Permitting # multiple failures complicates failure output, especially when every # failure after the first is a result of the same underlying issue. When # testing non-interactively, testing is typically *NOT* halted on the first # failure. Hence, this option is confined to this script rather than added # to our general-purpose "pytest.ini" configuration. '--maxfail=1' # Pass all remaining arguments to "pytest" as is. "${@}" ) # echo "pytest args: ${PYTEST_ARGS[*]}" # ....................{ FUNCTIONS }.................... # is_package(module_name: str) -> bool # # Report success only if a package or module with the passed fully-qualified # name is importable and thus installed under the active Python interpreter. # This tester is strongly inspired by this StackOverflow post: # https://askubuntu.com/a/588392/415719 function is_package() { # Validate and localize all passed arguments. (( $# == 1 )) || { echo 'Expected exactly one argument.' 1>&2 return 1 } local package_name="${1}" # Report success only if this package or module exists. "${PYTHON_ARGS[@]}" -c "import ${package_name}" 2>/dev/null } # str canonicalize_path(str pathname) # # Canonicalize the passed pathname. function canonicalize_path() { # Validate and localize all passed arguments. (( $# == 1 )) || { echo 'Expected exactly one argument.' 1>&2 return 1 } local pathname="${1}" # The "readlink" command's GNU-specific "-f" option would be preferable but # is unsupported by macOS's NetBSD-specific version of "readlink". Instead, # just defer to Python for portability. command python3 -c " import os, sys print(os.path.realpath(os.path.expanduser(sys.argv[1])))" "${pathname}" } # ....................{ PATHS }.................... # Absolute or relative filename of this script. SCRIPT_FILENAME="$(canonicalize_path "${BASH_SOURCE[0]}")" # Absolute or relative dirname of the directory directly containing this # script, equivalent to the top-level directory for this project. SCRIPT_DIRNAME="$(dirname "${SCRIPT_FILENAME}")" # ....................{ MAIN }.................... # Temporarily change the current working directory to that of this project. pushd "${SCRIPT_DIRNAME}" >/dev/null # If the third-party "coverage" package is installed under the desired Python # interpreter *AND* the "-k" option was *NOT* passed, then measure coverage # while running tests. # # If the "-k" option was passed, we avoid measuring coverage. Why? Because that # option restricts testing to a subset of tests, guaranteeing that coverage # measurements will be misleading at best and trigger test failure at worst # (e.g., if the "fail_under" option is enabled in ".coveragerc"). if is_package coverage && [[ ! " ${PYTEST_ARGS[*]} " =~ " -k " ]]; then # If run this project's pytest-based test suite with all passed arguments # (while measuring coverage) succeeds, generate a terminal coverage report. # # Note that the last argument passed to "pytest" *MUST* be ".". Why? # Because "." notifies pytest of the relative dirname of the root directory # for this project. On startup, pytest internally: # * Sets its "rootdir" property to this dirname in absolute form. # * Sets its "inifile" property to the concatenation of this dirname # with the basename "pytest.ini" if that top-level configuration file # exists. # * Prints the initial values of these properties to stdout. # # *THIS IS ESSENTIAL.* If *NOT* explicitly passed this dirname as an # argument, pytest may fail to set these properties to the expected # pathnames. For unknown reasons (presumably unresolved pytest issues), # pytest instead sets "rootdir" to the absolute dirname of the current # user's home directory and "inifile" to "None". Since no user's home # directory contains a "pytest.ini" file, pytest then prints errors ala: # $ ./pytest -k test_sim_export --export-sim-conf-dir ~/tmp/yolo # running test # Running py.test with arguments: ['--capture=no', '--maxfail=1', '-k', 'test_sim_export', '--export-sim-conf-dir', '/home/leycec/tmp/yolo'] # usage: setup.py [options] [file_or_dir] [file_or_dir] [...] # setup.py: error: unrecognized arguments: --export-sim-conf-dir # inifile: None # rootdir: /home/leycec # # See the following official documentation for further details, entitled # "Initialization: determining rootdir and inifile": # https://docs.pytest.org/en/latest/customize.html "${PYTHON_ARGS[@]}" -m coverage run -m "${PYTEST_ARGS[@]}" . && "${PYTHON_ARGS[@]}" -m coverage report # Else, run this project's pytest-based test suite with all passed arguments # *WITHOUT* measuring coverage. else "${PYTHON_ARGS[@]}" -m "${PYTEST_ARGS[@]}" . fi # 0-based exit code reported by the prior command. exit_code=$? # Revert the current working directory to the prior such directory. popd >/dev/null # Report the same exit code from this script. exit ${exit_code} beartype-0.18.5/pytest.ini000066400000000000000000000241611461113517100154550ustar00rootroot00000000000000# --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. # # --------------------( SYNOPSIS )-------------------- # Project-wide pytest configuration, applied to all invocations of the pytest # test runner within this project. # # --------------------( DETAILS )-------------------- # To permit tests to transparently import from the main non-test codebase, this # file resides in the root project directory. pytest then: # # 1. Recursively finds this file. # 2. Sets "config.inifile" to the absolute path of this file. # 3. Sets "config.rootdir" to the absolute path of this file's directory. # # See https://pytest.org/latest/customize.html for details. # ....................{ BOILERPLATE }................... # The following pytest-specific section specifier is mandatory, despite this # file's unambiguous basename of "pytest.ini". One is enraged by bureaucracy! [pytest] # Newline-delimited list of all custom warning filters applied by this test # suite. Recognized strings include: # * "default", printing the first occurrence of matching warnings for each # location (module + line number) where the warning is issued. # Unsurprisingly, this is pytest's default. Surprisingly, the resulting # output is overly fine-grained to the point of stripping all caller context # and thus being mostly useless: e.g., # betse_test/func/sim/solve/test_sim_fast.py::test_cli_sim_fast # /usr/lib/python3.7/site-packages/numpy/core/_asarray.py:83: VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated. If you meant to do this, you must specify 'dtype=object' when creating the ndarray # return array(a, dtype, copy=False, order=order) # Note that pytest reports the warning as originating from within a private # NumPy submodule, which it technically does but which communicates no # practical meaning with respect to our codebase. # * "error", turning matching warnings into exceptions. Ideally, pytest would # support a filter printing tracebacks on warnings. Since it fails to do so, # implicitly printing tracebacks by coercing non-fatal warnings into fatal # exceptions is our next best least worst solution. filterwarnings = # Implicitly coerce all non-fatal warnings into fatal exceptions. error # Avoid coercing all non-fatal warnings matching one or more of the # following patterns formatted as: # ignore:{warning_message}:{warning_classname}:{module_name} # # ...where: # * "{warning_message}" is a regular expression matching the plaintext of # the warning message to be ignored. # * "{warning_classname}" is the fully-qualified classname of the warning # to be ignored. # * "{module_name}" is the fully-qualified name of the module emitting the # warning to be ignored. # # For example: # ignore:^Use of \.\. or absolute path in a resource path.*:DeprecationWarning:pkg_resources # # See also: # * https://docs.python.org/3/library/warnings.html and # * https://docs.pytest.org/en/latest/warnings.html ignore:^'cgi' is deprecated and slated for removal in Python 3\.13$:DeprecationWarning:babel.messages.catalog # Newline-delimited list of all custom marks applied by this test suite. # Failing to explicitly list such marks here induces non-fatal warnings: e.g., # /usr/lib64/python3.6/site-packages/_pytest/mark/structures.py:335 # /usr/lib64/python3.6/site-packages/_pytest/mark/structures.py:335: # PytestUnknownMarkWarning: Unknown pytest.mark.noop - is this a typo? # You can register custom marks to avoid this warning - for details, see # https://docs.pytest.org/en/latest/mark.html # PytestUnknownMarkWarning, markers = noop: meaningless placeholder mark required to conditionally skip tests # ....................{ LOGGING }................... #FIXME: Uncomment to print logging messages. ; # If true, captures and prints logging messages whose level is at least ; # {log_cli_level} or higher. (Defaults to false.) ; log_cli = True ; ; # Minimum level of logging messages to be captured and printed if {log_cli} is ; # true. (Defaults to "INFO".) ; log_cli_level = DEBUG # ....................{ OPTIONS }................... #FIXME: Conditionally support the following plugin-based options in an #appropriate setuptools command when the requisite plugin is importable: # #* "--instafail", immediately printing test output rather than delaying such # output until after all tests complete. This requires the "pytest-instafail" # plugin. Note that this may not necessarily play nicely with the # "--capture=no" option leveraged below. Consider further testing. #FIXME: Pass "--ff" and "--tb=auto" when all test machines have a sufficiently #new version of pytest installed. # Unconditionally pass the following command-line options to all invocations of # the "pytest" command. Dismantled, this is: # # * "-v", increasing verbosity. # * "--full-trace", printing a full traceback on keyboard interrupts (e.g., # hitting during testing at the command line). # * "-p no:asyncio", disabling the "pytest-asyncio" plugin for this CLI-only # This plugin is *ABSOLUTELY* mad, doing horrifying things with unexpected # side effects like: # * Unconditionally importing *EVERYTHING* in our friggin' test suite, which # then promptly raises non-human-readable exceptions during early test # collection time. Like, "Just no, you imbecilic plugin!" Many of the # submodules in our test suite are only safely importable in a conditional # test-specific context. # * Emitting senseless deprecation warnings on "pytest" startup resembling: # INTERNALERROR> Traceback (most recent call last): # ... # INTERNALERROR> File "/usr/lib/python3.8/site-packages/pytest_asyncio/plugin.py", line 186, in pytest_configure # INTERNALERROR> config.issue_config_time_warning(LEGACY_MODE, stacklevel=2) # INTERNALERROR> File "/usr/lib/python3.8/site-packages/_pytest/config/__init__.py", line 1321, in issue_config_time_warning # INTERNALERROR> warnings.warn(warning, stacklevel=stacklevel) # INTERNALERROR> DeprecationWarning: The 'asyncio_mode' default value will change to 'strict' in future, please explicitly use 'asyncio_mode=strict' or 'asyncio_mode=auto' in pytest configuration file. # # This is *ABSOLUTELY* senseless, because this project intentionally does *NOT* # require, reference, or otherwise leverage "pytest-asyncio" anywhere. However, # many other third-party packages you may have installed do. Thanks to them, # *ALL* "pytest" invocations must now pass this vapid setting to avoid spewing # trash across *ALL* "pytest"-driven test sessions. *double facepalm* # * "-p no:xvfb", disabling the "pytest-xvfb" plugin for this CLI-only project. # Although technically harmless, this plugin unconditionally logs extraneous # messages that hamper readability of pytest output. Ergo, it goes. # * "-r a", increasing verbosity of (a)ll types of test summaries. # * "-s", disable all stdout and stderr capturing. # * "--doctest-glob=", disabling implicit detection of doctests (i.e., tests # embedded in docstrings that double as human-readable examples). By default, # pytest runs all files matching the recursive glob "**/test*.txt" through # the standard "doctest" module. Since this project employs explicit tests # rather than implicit doctests, this detection is a non-fatal noop in the # best case and a fatal conflict in the worst case. For collective sanity, # this detection *MUST* be disabled. # * "--failed-first", prioritizing tests that failed ahead of tests that # succeeded on the most recent test run. Actually, this option has been # temporarily omitted. Why? Because serial tests currently fail to implicitly # require prerequisite tests (e.g., "test_cli_sim_default[sim]" fails to # require "test_cli_sim_default[seed]"), thus requiring that tests be run # *ONLY* in the default ordering. # * "--showlocals", printing local variable values in tracebacks. # * "--tb=native", printing tracebacks in the same manner as tracebacks printed # by Python itself for uncaught exceptions. By default, pytest prints # tracebacks in an extremely colourful (which is nice) but unreadable (which # is *NOT* nice) manner. # # See "pytest --help | less" for further details on available options. addopts = -v --showlocals -p no:asyncio -p no:xvfb -r a --doctest-glob= ; addopts = -v --showlocals -p no:asyncio -p no:xvfb -r a --doctest-glob= -s ; addopts = -v -p no:asyncio -p no:xvfb -r a --doctest-glob= --showlocals --tb=native ; addopts = -vvvv --showlocals -p no:asyncio -p no:xvfb -r a --doctest-glob= ; addopts = -vvvv --showlocals -p no:asyncio -p no:xvfb -r a --doctest-glob= -s #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: The "--full-trace" option now inadvertently produces absurdly # verbose, largely unreadable tracebacks. (You have been warned.) #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ; addopts = -v --full-trace --showlocals -p no:xvfb -r a --doctest-glob= # Minimum version of pytest required by: # # * The "--failed-first" option enabled by default above. # minversion = 2.8.0 # Whitespace-delimited list of the relative paths of all top-level directories # containing tests. All Python scripts with basenames prefixed by "test_" in # all subdirectories of these directories including these directories # themselves will be parsed for: # # * Functions whose names are prefixed by "test_". # * Classes whose names are prefixed by "Test". testpaths = beartype_test # ....................{ OPTIONS ~ plugin }................... # Options specific to third-party pytest plugins. # # Command-line options pertaining to plugins include: # $ pytest --trace-config # List all active and available plugins. beartype-0.18.5/setup.cfg000066400000000000000000000021011461113517100152330ustar00rootroot00000000000000# --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. # # --------------------( SYNOPSIS )-------------------- # Project-wide pip and setuptools configuration, containing miscellaneous # project metadata *NOT* already provided by a more appropriate top-level file # (typically either "setup.py" or "MANIFEST.in"). # # Welcome to setuptools hell, where only twenty distinct files in twenty # distinct formats suffice to distribute a single project. # ....................{ WHEEL }.................... # Metadata specific to the third-party "wheel" package used to produce # platform-specific binary distributions of this project. [metadata] # List of one or more comma-delimited relative filenames of the files defining # this project's license(s). license_files = LICENSE # Relative filename of the file describing this project. long_description = file: README.rst long_description_content_type = text/x-rst; charset=UTF-8 beartype-0.18.5/setup.py000077500000000000000000000427171461113517100151500ustar00rootroot00000000000000#!/usr/bin/env python3 # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. ''' **Beartype installer.** This submodule conforms to the standard :mod:`setuptools`-based "makefile" format, instrumenting most high-level installation tasks for this package. ''' # ....................{ TODO }.................... #FIXME: Strongly consider migrating to Hatch: # https://hatch.pypa.io #We can't *STAND* poetry, but Hatch looks to be another breed entirely. #Crucially, PyPA itself has officially adopted Hatch (which is a huge boost), #Hatch supports dynamic retrieval of version specifiers from Python modules, #support for PEPs denigrated by poetry authors, and probably much more. #Basically, Hatch looks like everything we wish poetry was. # ....................{ KLUDGES ~ path }.................... # Explicitly register all files and subdirectories of the root directory # containing this top-level "setup.py" script to be importable modules and # packages (respectively) for the remainder of this Python process if this # directory has yet to be registered. # # Technically, this should *NOT* be required. The current build framework # (e.g., "pip", "setuptools") should implicitly guarantee this to be the case. # Indeed, the "setuptools"-based "easy_install" script does just that. # Unfortunately, "pip" >= 19.0.0 does *NOT* guarantee this to be the case for # projects defining a "pyproject.toml" file -- which, increasingly, is all of # them. Although "pip" purports to have resolved this upstream, current stable # releases appear to suffer the same deficiencies. See also: # https://github.com/pypa/pip/issues/6163 # Isolate this kludge to a private function for safety. def _register_dir() -> None: # Avert thy eyes, purist Pythonistas! import os, sys # Absolute dirname of this directory inspired by this StackOverflow answer: # https://stackoverflow.com/a/8663557/2809027 setup_dirname = os.path.dirname(os.path.realpath(__file__)) # If the current PYTHONPATH does *NOT* already contain this directory... if setup_dirname not in sys.path: # Print this registration. print( 'WARNING: Registering "setup.py" directory for importation under ' 'broken installer (e.g., pip >= 19.0.0)...', file=sys.stderr) # print('setup_dirname: {}\nsys.path: {!r}'.format(setup_dirname, sys.path)) # Append this directory to the current PYTHONPATH. sys.path.append(setup_dirname) # Kludge us up the bomb. _register_dir() # ....................{ KLUDGES ~ init }.................... # Explicitly notify the "beartype.__init__" submodule that it is being imported # at install time from this script. Doing so prevents that submodule from # implicitly importing from *ANY* "beartype" submodule other than the # "beartype.meta" submodule, which is the *ONLY* "beartype" submodule guaranteed # by sheer force of will to be safely importable at install time. All other # "beartype" submodules should be assumed to be unsafe to import below due to # potentially importing one or more optional runtime dependencies yet to be # installed (e.g., the third-party "typing_extensions" package). # # Naturally, there exist a countably infinite number of ways to notify the # "beartype.__init__" submodule that it is being imported at install time. To # minimize the likelihood of a conflict with other Python subsystems or # interpreters, we intentionally do *NOT*: # * Monkey-patch a module or package in the standard library. # * Detect this script in a stack frame on the call stack. # # Instead, we dynamically populate the standard "sys.modules" list of all # previously imported modules with a fake beartype-specific module. The # "beartype.__init__" submodule then decides whether to implicitly import from # potentially unsafe "beartype" submodules by detecting that fake module. # Isolate this kludge to a private function for safety. def _notify_beartype() -> None: # The acid: it burns more than I expected. from sys import modules from types import ModuleType # Dynamically create a new fake module. Look. Just do it. fake_module = ModuleType('beartype.__is_installing__', 'This is horrible.') # Dynamically register the fake module. Don't look like that. modules['beartype.__is_installing__'] = fake_module # Setuptools made us do it. _notify_beartype() # ....................{ IMPORTS }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: To avoid race conditions during setuptools-based installation, this # module may import *ONLY* from packages guaranteed to exist at the start of # installation. This includes all standard Python and package submodules but # *NOT* third-party dependencies, which if currently uninstalled will only be # installed at some later time in the installation. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! import setuptools from beartype import meta # ....................{ METADATA ~ seo }.................... _KEYWORDS = [ 'type checking', 'type hints', 'PEP 483', 'PEP 484', 'PEP 544', 'PEP 563', 'PEP 585', 'PEP 586', 'PEP 589', 'PEP 593', 'PEP 604', 'PEP 3141', ] ''' List of all lowercase alphabetic keywords synopsising this package. These keywords may be arbitrarily selected so as to pretend to improve search engine optimization (SEO). In actuality, they do absolutely nothing. ''' # ....................{ METADATA ~ seo : classifiers }.................... # To minimize desynchronization woes, all # "Programming Language :: Python :: "-prefixed strings are dynamically # appended to this list by the init() function below. _CLASSIFIERS = [ # PyPI-specific version type. The number specified here is a magic constant # with no relation to this package's version numbering scheme. *sigh* 'Development Status :: 5 - Production/Stable', # Miscellaneous metadata. 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python :: 3 :: Only', 'Topic :: Software Development :: Code Generators', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Quality Assurance', 'Typing :: Typed', ] ''' List of all PyPI-specific trove classifier strings synopsizing this project. Each such string *must* contain either two or three ``" :: "`` substrings delimiting human-readable capitalized English words formally recognized by the :mod:`distutils`-specific ``register`` command. See Also ---------- https://pypi.org/classifiers Plaintext list of all trove classifier strings recognized by PyPI. ''' def _sanitize_classifiers( python_version_min_parts: tuple, python_version_minor_max: int, ) -> list: ''' List of all PyPI-specific trove classifier strings synopsizing this package, manufactured by appending classifiers synopsizing this package's support for Python major versions (e.g., ``Programming Language :: Python :: 3.6``, a classifier implying this package to successfully run under Python 3.6) to the global list :data:`_CLASSIFIERS` of static classifiers. Parameters ---------- python_version_min_parts : tuple Minimum fully-specified version of Python required by this package as a tuple of integers (e.g., ``(3, 5, 0)`` if this package requires at least Python 3.5.0). python_version_minor_max : int Maximum minor stable version of the current Python 3.x mainline (e.g., ``9`` if Python 3.9 is the most recent stable version of Python 3.x). Returns ---------- list List of all sanitized PyPI-specific trove classifier strings. ''' assert isinstance(python_version_min_parts, tuple), ( f'"{python_version_min_parts}" not tuple.') assert isinstance(python_version_minor_max, int), ( f'"{python_version_minor_max}" not integer.') # Major version of Python required by this package. PYTHON_VERSION_MAJOR = python_version_min_parts[0] # List of classifiers to return, copied from the global list for safety. classifiers_sane = _CLASSIFIERS[:] # For each minor version of Python 3.x supported by this package, # formally classify this version as such. for python_version_minor in range( python_version_min_parts[1], python_version_minor_max + 1): classifiers_sane.append( f'Programming Language :: Python :: ' f'{PYTHON_VERSION_MAJOR}.{python_version_minor}' ) # print('classifiers: {}'.format(_CLASSIFIERS)) # Return this sanitized list of classifiers. return classifiers_sane # ....................{ OPTIONS }.................... # Setuptools-specific options. Keywords not explicitly recognized by either # setuptools or distutils must be added to the above dictionary instead. _SETUP_OPTIONS = { # ..................{ CORE }.................. # Self-explanatory metadata. Note that the following metadata keys are # instead specified by the "setup.cfg" file: # # * "license_file", for unknown reasons. We should probably reconsider. # * "long_description", since "setup.cfg" supports convenient # "file: ${relative_filename}" syntax for transcluding the contents of # arbitrary project-relative files into metadata values. Attempting to do # so here would require safely opening this file with a context manager, # reading the contents of this file into a local variable, and passing # that variable's value as this metadata outside of that context. (Ugh.) 'name': meta.PACKAGE_NAME, 'version': meta.VERSION, 'description': meta.SYNOPSIS, # ..................{ AUTHORS }.................. 'author': meta.AUTHORS, 'author_email': meta.AUTHOR_EMAIL, 'maintainer': meta.AUTHORS, 'maintainer_email': meta.AUTHOR_EMAIL, # ..................{ URLS }.................. 'url': meta.URL_HOMEPAGE, 'download_url': meta.URL_DOWNLOAD, # Dictionary mapping from arbitrary human-readable terse names describing # various package-related URLs to those URLs. 'project_urls': { 'Documentation': meta.URL_HOMEPAGE, 'Source': meta.URL_REPO, 'Issues': meta.URL_ISSUES, 'Forums': meta.URL_FORUMS, 'Releases': meta.URL_RELEASES, }, # ..................{ PYPI }.................. # PyPi-specific meta. 'classifiers': _sanitize_classifiers( python_version_min_parts=meta.PYTHON_VERSION_MIN_PARTS, python_version_minor_max=meta.PYTHON_VERSION_MINOR_MAX, ), 'keywords': _KEYWORDS, 'license': meta.LICENSE, # ..................{ DEPENDENCIES }.................. # Python dependency. 'python_requires': f'>={meta.PYTHON_VERSION_MIN}', # Mandatory runtime dependencies. This package intentionally requires no # such dependencies and hopefully never will. 'install_requires': (), # Optional runtime dependencies. Whereas mandatory dependencies are defined # as sequences, optional dependencies are defined as a dictionary mapping # from an arbitrary alphanumeric word to a sequence containing one or more # such dependencies. Such dependencies are then installable via "pip" by # suffixing the name of this project by the "["- and "]"-delimited key # defined below whose value lists the dependencies to be installed (e.g., # "sudo pip3 install betse[all]", installing both the package and all # mandatory and optional dependencies required by the package). 'extras_require': { # All optional runtime dependencies. 'all': meta.LIBS_RUNTIME_OPTIONAL, # All mandatory developer dependencies (including all mandatory test- # and documentation build-time dependencies) as referenced from # external project documentation for developers. 'dev': meta.LIBS_DEVELOPER_MANDATORY, # All mandatory Read The Docs (RTD)-specific documentation build-time # dependencies an arbitrarily named extra. This is required *ONLY* # for integration with the top-level ".readthedocs.yml" file. See the # "python" key in that file for further details. 'doc-rtd': meta.LIBS_DOCTIME_MANDATORY_RTD, # All mandatory tox-specific testing dependencies, copied from the # "tests_require" key below into an arbitrarily named extra. This is # required *ONLY* for integration with the top-level "tox.ini" file. # See the "extras" key in that file for further details. 'test-tox': meta.LIBS_TESTTIME_MANDATORY_TOX, # All mandatory coverage-specific testing dependencies as an # arbitrarily named extra, required *ONLY* for integration with the # top-level "tox.ini" file. See the "extras" key in that file. 'test-tox-coverage': meta.LIBS_TESTTIME_MANDATORY_COVERAGE, }, # Mandatory testing dependencies. 'tests_require': meta.LIBS_TESTTIME_MANDATORY_TOX, # ..................{ PACKAGES }.................. # List of the fully-qualified names of all Python packages (i.e., # directories containing zero or more Python modules) to be installed, # including the top-level package and all subpackages of that package. This # thus excludes: # # * The top-level test package and all subpackages of that package, # defining only test functionality *NOT* intended to be installed with # this package. # * "build", caching both setuptools metadata and a complete copy of this # package, required only by a prior package installation. # #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: This inspection intentionally omits subdirectories containing no # "__init__.py" file, despite the remainder of the Python ecosystem # commonly accepting such subdirectories as subpackages. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 'packages': setuptools.find_packages(exclude=( f'{meta.PACKAGE_NAME}_test', f'{meta.PACKAGE_NAME}_test.*', 'build', )), # ..................{ PACKAGES ~ data }.................. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: *ALL DATA FILES TO BE INSTALLED MUST BE EXPLICITLY MATCHED IN # THE TOP-LEVEL "MANIFEST.in" FILE.* #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # # Install all data files (i.e., non-Python files) embedded in the Python # package tree for this project which are also explicitly matched by the # top-level "MANIFEST.in" file. # # Unlike Python packages, undesirable data files are includable and # excludable from installation *ONLY* via the external "MANIFEST.in" file. # This is terrible, of course. (Did you expect otherwise?) # # Data files are *NOT* Python modules and hence should *NOT* be embedded in # the Python package tree. Sadly, the "data_files" key supported by # setuptools for this purpose is *NOT* cross-platform-portable and is thus # inherently broken. Why? Because that key either requires usage of # absolute paths *OR* relative paths relative to absolute paths defined by # "setup.cfg"; in either case, those paths are absolute. While the current # platform could be detected and the corresponding absolute path embedded # in 'data_files', that implementation would be inherently fragile. (That's # bad.) In lieu of sane setuptools support, we defer to the methodology # employed by everyone. Setuptools, your death is coming. # # See also: # * "Data Files Support", official documentation for this abomination at: # https://setuptools.readthedocs.io/en/latest/userguide/datafiles.html 'include_package_data': True, # Install to an uncompressed directory rather than a compressed archive. # # While nothing technically precludes the latter, doing so substantially # complicates runtime access of data files compressed into this archive # (e.g., with the pkg_resources.resource_filename() function). How so? By # decompressing this archive's contents into a temporary directory on # program startup and removing these contents on program shutdown. Since # there exists no guarantee this removal will actually be performed (e.g., # due to preemptive SIGKILLs), compressed archives are inherently fragile. # # Note that MyPy requires upstream PEP 561-compliant dependencies (like # this project) to explicitly prohibit archival. See also: # https://mypy.readthedocs.io/en/stable/installed_packages.html 'zip_safe': False, } ''' Dictionary unpacked as keyword arguments into the subsequent call of the :func:`setuptools.setup` function, signifying the set of all package-specific :mod:`setuptools` options. ''' # print('extras: {}'.format(setup_options['extras_require'])) # ....................{ SETUP }.................... setuptools.setup(**_SETUP_OPTIONS) beartype-0.18.5/sphinx000077500000000000000000000145451461113517100146700ustar00rootroot00000000000000#!/usr/bin/env bash # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. # # --------------------( SYNOPSIS )-------------------- # Bash shell script wrapping this project's Sphinx-based documentation build # system, passing sane default options suitable for interactive terminal # building and otherwise passing all passed arguments as is to the # "sphinx-build" command via the "SPHINXOPTS" environment variable. # # This script is defined as a Bash rather than Bourne script purely for the # canonical ${BASH_SOURCE} string global, reliably providing the absolute # pathnames of this script and hence this script's directory. # ....................{ PREAMBLE }.................... # Enable strictness for sanity. set -e # ....................{ ARRAYS }.................... # Array of all shell words with which to invoke "sphinx-build" below. # # Bizarrely, note that these Sphinx options are *ONLY* available in the short # and long forms listed here. (This is us collectively shrugging.) SPHINX_BUILD_ARGS=( command sphinx-build # ..................{ SPHINX ~ mode }.................. # Generate HTML documentation. # # Note that we intentionally prefer the "html" to "dirhtml" build type when # locally revising documentation, despite ReadTheDocs (RTD) preferring the # latter. Why? Because the latter incorrectly links to directories rather # than HTML files in directories when generating local documentation. -M html # -b dirhtml # ..................{ SPHINX ~ paths }.................. # Relative dirname of the directory containing source documentation in # reStructuredText (reST) format. doc/src/ # Relative dirname of the directory containing target documentation in the # format corresponding to the "-M" option above (typically, HTML). doc/trg/ # Pass all remaining non-option arguments to "sphinx-build" as is, # comprising the set of all relative or absolute filenames to be built. If # unpassed, "sphinx-build" rebuilds all outdated files by default. "${@}" # ..................{ SPHINX ~ options }.................. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # CAUTION: In flagrant violation of POSIX standards, sensible norms, and its # own bloody documentation in both "--help" text and error messages, # "sphinx-build" requires that options be passed *AFTER* pathnames. By # inspection, the autogenerated "doc/Makefile" does so. If options are # erroneously passed *BEFORE* pathnames, "sphinx-build" fails with this # non-human-readable fatal error: # $ ./sphinx # usage: sphinx-build [OPTIONS] SOURCEDIR OUTPUTDIR [FILENAMES...] # sphinx-build: error: argument -d: expected one argument #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Suffix raised exceptions with tracebacks. This is *CRITICAL.* By default, # Sphinx insanely emits *ONLY* useless exception messages. -T # Treat non-fatal warnings as fatal errors. This is *CRITICAL.* By default, # Sphinx insanely emits non-fatal warnings for fatal errors... Not kidding. -W #FIXME: Uncomment this when caching issues inevitably arise. # Unconditionally rebuild *EVERY* target output documentation regardless of # whether the source input files providing that documentation have been # modified or not. By default, Sphinx only conditionally rebuilds target # documentation whose underlying source files have since been modified. # -a #FIXME: This appears to redundantly do the same thing as "-a" and is thus #currently disabled. That said, if and when caching issues arise, consider #uncommenting this as well just to be sure. *shrug* # -E # Parallelize the build process across *ALL* available CPU cores. -j auto # Collect *ALL* warnings before failing rather than immediately failing on # the first warning. --keep-going #FIXME: Temporarily disabled until we either resolve exactly why the Sphinx #"autodoc" extension is failing to generate *ANY* working references or #(more likely) ditch "autodoc" for something sane based on AST parsing. #Currently, "autodoc" fails to generate working references for even #standard pure-Python modules guaranteed to exist (e.g., "typing"). # Enable "nit-picky mode," generating one warning for each broken reference # (e.g., interdocument or intrasection link). -n #FIXME: Uncomment to assist in debugging, please. # Increase verbosity. #-vv ) echo "Running: ${SPHINX_BUILD_ARGS[*]}" # ....................{ FUNCTIONS }.................... # str canonicalize_path(str pathname) # # Canonicalize the passed pathname. function canonicalize_path() { # Validate and localize all passed arguments. (( $# == 1 )) || { echo 'Expected exactly one argument.' 1>&2 return 1 } local pathname="${1}" # The "readlink" command's GNU-specific "-f" option would be preferable but # is unsupported by macOS's NetBSD-specific version of "readlink". Instead, # just defer to Python for portability. command python3 -c " import os, sys print(os.path.realpath(os.path.expanduser(sys.argv[1])))" "${pathname}" } # ....................{ PATHS }.................... # Absolute or relative filename of this script. SCRIPT_FILENAME="$(canonicalize_path "${BASH_SOURCE[0]}")" # Absolute or relative dirname of the directory directly containing this # script, equivalent to the top-level directory for this project. SCRIPT_DIRNAME="$(dirname "${SCRIPT_FILENAME}")" # ....................{ MAIN }.................... # Temporarily change the current working directory to that of this project. # pushd "${SCRIPT_DIRNAME}/doc" >/dev/null pushd "${SCRIPT_DIRNAME}" >/dev/null # Build this project's documentation with all passed arguments. "${SPHINX_BUILD_ARGS[@]}" # 0-based exit code reported by the prior command. exit_code=$? # Revert the current working directory to the prior such directory. popd >/dev/null # Report the same exit code from this script. exit ${exit_code} beartype-0.18.5/tox000077500000000000000000000115271461113517100141660ustar00rootroot00000000000000#!/usr/bin/env bash # --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. # # --------------------( SYNOPSIS )-------------------- # Bash shell script wrapping this project's tox-based test suite, passing # sane default options suitable for interactive terminal testing and otherwise # passing all passed arguments as is to the "tox" command. # # This script is defined as a Bash rather than Bourne script purely for the # canonical ${BASH_SOURCE} string global, reliably providing the absolute # pathnames of this script and hence this script's directory. # # --------------------( CAVEATS )-------------------- # *THE HIGHER-LEVEL "tox" SCRIPT SHOULD TYPICALLY BE RUN INSTEAD.* This # lower-level script only exercises this project against the single Python # interpreter associated with the "pytest" command and is thus suitable *ONLY* # as a rapid sanity check. Meanwhile, the higher-level "tox" command exercises # this project against all installed Python interpreters and is thus suitable # as a full-blown correctness check (e.g., before submitting pull requests). # ....................{ PREAMBLE }.................... # Enable strictness for sanity. set -e # ....................{ ARRAYS }.................... # Array of all arguments with which to invoke Python. # # Note that the "-X dev" option enabling the Python Development Mode (PDM) need # *NOT* be explicitly passed to "tox", as the ${PYTHONDEVMODE} shell variable # in the "tox.ini" file already enables the PDM. # PYTHON_ARGS=( command python3 ) # PYTHON_ARGS=( command python3.8 ) # PYTHON_ARGS=( command python3.9 ) # PYTHON_ARGS=( command python3.10 ) PYTHON_ARGS=( command python3.11 ) # PYTHON_ARGS=( command pypy3.7 ) # ....................{ FUNCTIONS }.................... # str canonicalize_path(str pathname) # # Canonicalize the passed pathname. function canonicalize_path() { # Validate and localize all passed arguments. (( $# == 1 )) || { echo 'Expected exactly one argument.' 1>&2 return 1 } local pathname="${1}" # The "readlink" command's GNU-specific "-f" option would be preferable but # is unsupported by macOS's NetBSD-specific version of "readlink". Instead, # just defer to Python for portability. command python3 -c " import os, sys print(os.path.realpath(os.path.expanduser(sys.argv[1])))" "${pathname}" } # ....................{ PATHS }.................... # Absolute or relative filename of this script. script_filename="$(canonicalize_path "${BASH_SOURCE[0]}")" # Absolute or relative dirname of the directory directly containing this # script, equivalent to the top-level directory for this project. script_dirname="$(dirname "${script_filename}")" # ....................{ MAIN }.................... # Temporarily change the current working directory to that of this project. pushd "${script_dirname}" >/dev/null #FIXME: Excise us up, please. This no longer appears to be working as intended #as has, in fact, obstructed sanity on multiple occasions. # # Comma-delimited list of all tox environments. Dismantled, this is: # # # # * "tox -l", listing all environments defined by the "envlist" default. # # * "grep linux", removing all duplicate platform-specific environments from # # that list. # # * "paste ...", converting all newlines in that "grep" output into commas # # expected by the parent "tox -e" command. # # # # This pipeline is gratefully inspired by this StackOverflow answer: # # https://stackoverflow.com/a/56387013/2809027 # tox_envs="$(command tox -l | command paste -sd "," -)" # # # Strip the optional "-coverage" suffix from these environments, preventing # # tox from measuring coverage. While tox can be made to measure coverage at the # # command line, coverage is best measured either: # # * Directly by "pytest" from the command line. # # * Indirectly by "tox" from continuous integration (CI). # tox_envs="${tox_envs//-coverage}" # # # Print this munged list for debuggability. # echo "Exercising tox environments \"${tox_envs}\"..." # # # Run this project's tox-based test suite with all passed arguments. # # Dismantled, this is: # # # # * "tox -e", exercising only the passed comma-delimited list of environments. # # Note this implicitly disables the "envlist" default in "tox.ini". # "${PYTHON_ARGS[@]}" -m tox -e "${tox_envs}" "${@}" # Run this project's tox-based test suite with all passed arguments. "${PYTHON_ARGS[@]}" -m tox "${@}" # 0-based exit code reported by the prior command. exit_code=$? # Revert the current working directory to the prior such directory. popd >/dev/null # Report the same exit code from this script. exit ${exit_code} beartype-0.18.5/tox.ini000066400000000000000000000423271461113517100147430ustar00rootroot00000000000000# --------------------( LICENSE )-------------------- # Copyright (c) 2014-2024 Beartype authors. # See "LICENSE" for further details. # # --------------------( SYNOPSIS )-------------------- # Project-wide tox configuration, applied to all invocations of the tox test # harness within this project. # # tox is a high-level Python-specific testing utility wrapping comparatively # lower-level Python-specific testing frameworks (e.g., py.test, unittest2). # Whereas the latter only exercise this project's codebase from the current # working directory (CWD) without installing this project and hence exercising # this project's installation, tox exercises both. # # Specifically, tox iteratively: # 1. Creates a source-based tarball distribution of this project (e.g., via # "python setup.py sdist"). # 2. Installs this tarball *AND* a system-agnostic Python interpreter into one # isolated virtual environment for each testing configuration. # 3. Tests this installation with the specified testing framework. # # --------------------( CAVEATS )-------------------- # *THIS CONFIGURATION IS INTOLERANT OF UNICODE CHARACTERS.* Note that setting # "PYIOENCODING = UTF-8" under the "setenv" section below has no meaningful # effect. For unknown reasons, "tox" is incapable of processing UTF-8 here. # This is why nobody gets good things. If this file contains one or more # UTF-8-encoded characters, "tox" fails with a non-human-readable traceback: # Traceback (most recent call last): # File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/runpy.py", line 194, in _run_module_as_main # return _run_code(code, main_globals, None, # File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/runpy.py", line 87, in _run_code # exec(code, run_globals) # File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/tox/__main__.py", line 4, in # tox.cmdline() # File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/tox/session/__init__.py", line 44, in cmdline # main(args) # File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/tox/session/__init__.py", line 65, in main # config = load_config(args) # File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/tox/session/__init__.py", line 81, in load_config # config = parseconfig(args) # File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/tox/config/__init__.py", line 282, in parseconfig # ParseIni(config, config_file, content) # File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/tox/config/__init__.py", line 1145, in __init__ # self._cfg = py.iniconfig.IniConfig(config.toxinipath, ini_data) # File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/py/_vendored_packages/iniconfig/__init__.py", line 54, in __init__ # tokens = self._parse(iter(f)) # File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/py/_vendored_packages/iniconfig/__init__.py", line 82, in _parse # for lineno, line in enumerate(line_iter): # File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/codecs.py", line 322, in decode # (result, consumed) = self._buffer_decode(data, self.errors, final) # UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd7 in position 3335: invalid continuation byte # Error: Process completed with exit code 1. # # --------------------( VARIABLES )-------------------- # tox dynamically substitutes "{"- and "}"-delimited variable names with the # strings to which those variables expand. Supported variable names include: # * "{envtmpdir}", the absolute dirname of a temporary directory specific to # the current virtual environment to which this project has been installed. # * "{posargs}", the whitespace-delimited list of all command-line arguments # passed to the current invocation of the "tox" command. # * "{toxinidir}", the absolute dirname of the directory containing this file # (e.g., the project root). # ....................{ TOX }.................... # Metadata specific to tox itself. [tox] # Comma- and newline-delimited string listing the names and optional versions # of all mandatory core dependencies required to merely create a new venv. # # Note that project dependencies should *NOT* be listed here. #requires = # pip >= 20.0.0 # setuptools < 50.0.0 # ....................{ TOX ~ py }.................... #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # WARNING: Changes to this setting *MUST* be manually synchronized with: # * The "tox-env" setting in ".github/workflows/python_test.yml". #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Test matrix defined as a bash-interpolated string, where tox implicitly: # * Expands "py" to "python". # * Delimits the subsequent two digits with a dot to associate each resulting # test configuration with the basename of an external command running an # externally installed Python interpreter. # * Expands "-"-delimited lists via the Cartesian set product A x B, # effectively "multiplying" each environment on the left of each "-" against # each environment on the right of that "-". Moreover, each such environment # remains preserved and thus distinctly testable as that environment. # # For example, setting "envlist = py27,py38" produces a test matrix exercising # the externally installed "python2.7" and "python3.8" commands. See also: # https://tox.readthedocs.io/en/latest/config.html#generating-environments-conditional-settings envlist = py{38,39,310,311,312}-coverage #FIXME: Disabled until required. Since "tox" currently behaves as expected, #there's no incentive to break what's worky. # # Comma- and newline-delimited string listing the names of all PyPI-hosted # # projects required as mandatory dependencies to bootstrap "tox" -- typically # # including some combination of "tox" itself, "virtualenv", and/or "pip". # # # # Note that all application-specific dependencies (e.g., "numpy", "scipy") # # should be listed in the "deps" and/or "extras" settings under each # # "[testenv]" section below. # requires = # # This "setuptools" dependency *MUST* be manially synchronized with the # # "betse.metadeps" submodule here. "tox" requires this minimum version of # # "setuptools" to create an sdist for this project *BEFORE* instantiating # # the first venv. Ergo, the venv-specific "[testenv]" section below is of # # no use to sdist generation. If omitted, "tox" fails at sdist generation # # time with an exception resembling: # # # # GLOB sdist-make: /home/leycec/py/betse/setup.py # # py36 inst-nodeps: /home/leycec/py/betse/.tox/.tmp/package/1/betse-1.1.1.zip # # ERROR: invocation failed (exit code 1), logfile: /home/leycec/py/betse/.tox/py36/log/py36-3.log # # =================================================== log start =================================================== # # Processing ./.tox/.tmp/package/1/betse-1.1.1.zip # # Complete output from command python setup.py egg_info: # # Traceback (most recent call last): # # File "", line 1, in # # File "/tmp/pip-0j3y5x58-build/setup.py", line 158, in # # buputil.die_unless_setuptools_version_at_least(metadeps.SETUPTOOLS_VERSION_MIN) # # File "/tmp/pip-0j3y5x58-build/betse_setup/buputil.py", line 74, in die_unless_setuptools_version_at_least # # setuptools_version_min, setuptools.__version__)) # # Exception: setuptools >= 38.2.0 required by this application, but only setuptools 28.8.0 found. # # # # ---------------------------------------- # # Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-0j3y5x58-build/ # # You are using pip version 9.0.1, however version 19.3.1 is available. # # You should consider upgrading via the 'pip install --upgrade pip' command. # # # # ==================================================== log end ==================================================== # setuptools >= 38.2.0 # # # Install the most recent stable release of "virtualenv" as of this # # writing. Note that this version dictates which versions of implicit # # packages (e.g., "pip", "pkg_resources", "setuptools") come pre-installed # # into each venv. If omitted, the obsolete version of "virtualenv" bundled # # with "tox" is defaulted to; this induces non-trivial issues throughout # # the installation toolchain, exemplified by the following "tox" warning: # # py36 installed: You are using pip version 9.0.1, however version # # 19.3.1 is available. You should consider upgrading via the 'pip # # install --upgrade pip' command. # # See also: https://github.com/tox-dev/tox/issues/765 # virtualenv >= 16.7.7 # Ignore Python environments unavailable on the current system. By default, # "tox" fails on the first unavailable Python environment. While sensible for # continuous integration (CI), this default fails to generalize for local # developers lacking one or more Python environments. # # Note that our CI configuration explicitly falsifies this setting back to its # CI-friendly default via the "--skip-missing-interpreters=false" CLI option, # forcing CI failures for unavailable Python environments. See also: # https://github.com/tox-dev/tox/issues/903 skip_missing_interpreters = true # ....................{ ENV }.................... # Job run for each test environment, exercising this project under that # environment. [testenv] # Human-readable string synopsizing the current test configuration. description = Exercise "{toxinidir}" with "{basepython} -m pytest". # ....................{ ENV ~ shell }.................... # Absolute dirname of the directory to change to for the current test # configuration, required to avoid accidental import collisions with # uninstalled packages of the same name residing in "{toxinidir}". See also the # following pertinent blog post, "Testing your python package as installed": # https://blog.ganssle.io/articles/2019/08/test-as-installed.html changedir = {envtmpdir} # Newline-delimited string listing all environment variables to be temporarily # set in each shell subprocess running tests. setenv = # Permit the "pip" installation commands internally invoked under each # "tox" venv to optionally install wheels from an external third-party PyPI # repository explicitly supporting PyPy. If this is *NOT* done, # PyPy-specific "tox" venvs typically fail to install one or more Python # packages in the standard scientific stack. # # Note that this repository resides at: # https://github.com/antocuni/pypy-wheels PIP_EXTRA_INDEX_URL=https://antocuni.github.io/pypy-wheels/manylinux2010 # Enable the Python Development Mode (PDM), which: # "Introduces additional runtime checks that are too expensive to be # enabled by default. It should not be more verbose than the default if # the code is correct; new warnings are only emitted when an issue is # detected." # Specifically, the PDM enables: # * "-W default", emitting warnings ignored by default. Yes, Python # insanely ignores various categories of warnings by default -- including # deprecating warnings, which *ABSOLUTELY* should be emitted by default, # but aren't. We can't resolve that for end users but we can resolve that # for ourselves. # * "PYTHONMALLOC=debug", registering memory allocators hooks detecting # unsafe call stack, memory, and GIL violations. # * "PYTHONFAULTHANDLER=1", registering fault handlers emitting Python # tracebacks on segmentation faults. # * "PYTHONASYNCIODEBUG=1", enabling asyncio debug mode logging unawaited # coroutines. # * Detections for unsafe string encoding and decoding operations. # * Logging io.IOBase.close() exceptions on object finalization. # * Enabling the "dev_mode" attribute of "sys.flags". # See also: # https://docs.python.org/3/library/devmode.html #PYTHONDEVMODE = 1 # Prevent Python from buffering and hence failing to log output in the # unlikely (but feasible) event of catastrophic failure from either the # active Python process or OS kernel. PYTHONUNBUFFERED = 1 # Command fragment measuring coverage while running tests, defined *ONLY* # when the caller explicitly concatenated the current environment name by # "-coverage" (e.g., "py310-coverage"). # # Note that we intentionally do *NOT* leverage the "pytest-cov" plugin, # which lacks sufficient configurability and friendly maintainership to # warrant yet another fragile dependency. coverage: _COVERAGE_COMMAND = coverage run -m # Newline-delimited string listing all environment variables to be passed from # the current shell process to each shell subprocess running tests. # Dismantled, this is: # * "CI" and "GITHUB_ACTIONS", enabling our test suite to programatically # detect execution by a remote continuous integration (CI) host. passenv = CI GITHUB_ACTIONS PIP_CACHE_DIR # ....................{ ENV ~ dependencies }.................... # Comma- and newline-delimited string listing the names of all "setup.py"-based # "extras" required as mandatory or optional dependencies when testing this # project. extras = # Install all mandatory test-specific dependencies. This is the official # solution supported by "tox" developers for eliminating redundancy between # testing dependencies listed within this file and the top-level "setup.py" # script. While non-intuitive, we have little recourse. See also: # https://stackoverflow.com/questions/29870629/pip-install-test-dependencies-for-tox-from-setup-py # https://stackoverflow.com/questions/39922650/tox-tests-use-setup-py-extra-require-as-tox-deps-source # https://github.com/tox-dev/tox/issues/13#issuecomment-247788280 # Note that this also requires ".[test-tox]" to be listed as a dependency. test-tox # If the caller explicitly concatenated the current environment name by # "-coverage" (e.g., "py310-coverage"), install all mandatory # coverage-specific dependencies as well. coverage: test-tox-coverage # Comma- and newline-delimited string listing the names of all mandatory # dependencies (i.e., third-party packages) required to test this project. # # Note that this also requires "test-tox" to be listed as an extra above. deps = .[test-tox] # ....................{ ENV ~ commands }.................... # Shell command with which to install project dependencies. # # This command extends the default "install_command" with support for an # optional "${_TOX_PIP_INSTALL_OPTIONS}" environment variable defaulting to the # empty string. This variable is typically defined by the higher-level # ".github/workflows/python_test.yml" continuous integration (CI) configuration # file on a platform-specific basis (e.g., "--force-reinstall" under macOS). install_command = python -m pip install {env:_TOX_PIP_INSTALL_OPTIONS:} {opts} {packages} # Newline-delimited string listing all shell commands required to test this # project under this environment. # # Note that: # * For disambiguity, avoid running any Python-based commands *EXCEPT* those # explicitly prefixed by "{envpython}" (i.e., the absolute filename of the # venv-specific Python interpreter). # * For portability between POSIX-compliant platforms (e.g., Linux, macOS) and # POSIX-noncompliant platforms (e.g., Windows), the current platform and # shell should *NOT* assumed. Ergo, commands should be confined to those # explicitly prefixed by "{envpython}". commands = # Print metadata on the current versions of Python and pytest (in order). {envpython} --version {envpython} -m pytest --version # Run our entire pytest-based test suite. Dismantled, this is: # * "{env:_COVERAGE_COMMAND:}", expanding to either: # * If measuring coverage, the value of the "${_COVERAGE_COMMAND}" # environment variable defined above. # * Else, the empty string. # * "--maxfail={n}", halting testing on the {n}th failure. # * "-p no:*", disabling various pytest plugins known to be harmful. See our # "pytest.ini" file for further commentary on the hideous state of pytest # plugins and why they are True Evil Personified (TEP). {envpython} \ -m {env:_COVERAGE_COMMAND:} \ pytest --maxfail=1 -p no:asyncio -p no:xvfb {posargs} "{toxinidir}" # {envpython} -m {env:_COVERAGE_COMMAND:} pytest --maxfail=1 -vvvv {posargs} "{toxinidir}" # {envpython} -m {env:_COVERAGE_COMMAND:} pytest --maxfail=1 -k test_pep563_closure_nested {posargs} "{toxinidir}" # If measuring coverage, additionally generate a coverage report in the # specific format expected by Codecov *AFTER* running our test suite and # thus collecting coverage statistics. coverage: {envpython} -m coverage xml -o "{toxinidir}/coverage.xml"