├── .editorconfig ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── dependabot.yml ├── labeler.yml ├── release.yml └── workflows │ ├── ci.yml │ ├── labeler-pr.yml │ └── publish-pypi.yml ├── .gitignore ├── .markdownlint.yaml ├── .vscode ├── extensions.json └── settings.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── examples ├── README.md ├── functor.py └── twice.py ├── optype ├── __init__.py ├── __init__.pyi ├── _core │ ├── __init__.py │ ├── _can.py │ ├── _do.py │ ├── _does.py │ ├── _has.py │ └── _just.py ├── _utils.py ├── copy.py ├── dataclasses.py ├── dlpack.py ├── inspect.py ├── io.py ├── json.py ├── numpy │ ├── __init__.py │ ├── _any_array.py │ ├── _any_dtype.py │ ├── _array.py │ ├── _compat.py │ ├── _ctypeslib.py │ ├── _ctypeslib.pyi │ ├── _dtype.py │ ├── _dtype_attr.py │ ├── _is.py │ ├── _literals.py │ ├── _scalar.py │ ├── _sequence_nd.py │ ├── _shape.py │ ├── _to.py │ ├── _ufunc.py │ ├── compat.py │ ├── ctypeslib.py │ └── random.py ├── pickle.py ├── py.typed ├── string.py ├── types │ ├── __init__.py │ ├── _typeforms.py │ └── _typeforms.pyi └── typing.py ├── pyproject.toml ├── scripts ├── config │ ├── bpr-np-1.25.json │ ├── bpr-np-1.26.json │ ├── bpr-np-2.0.json │ ├── bpr-np-2.1.json │ ├── bpr-np-2.2.json │ └── bpr-np-2.3.json └── my.py ├── tests ├── __init__.py ├── core │ ├── __init__.py │ ├── test_can.py │ ├── test_do.py │ ├── test_does.py │ ├── test_has_types.pyi │ ├── test_just.py │ └── test_protocols.py ├── numpy │ ├── __init__.py │ ├── test_any_array.py │ ├── test_any_dtype.py │ ├── test_array.py │ ├── test_is.py │ ├── test_scalar.py │ ├── test_shape.py │ ├── test_to.pyi │ └── test_ufunc.py ├── test_beartype.py ├── test_copy.py ├── test_dlpack.py ├── test_inspect.py ├── test_io.py ├── test_json.pyi ├── test_pickle.py ├── test_string.py ├── test_types.py ├── test_typing.py └── test_version.py └── uv.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org/ 2 | 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | charset = utf-8 8 | indent_style = space 9 | indent_size = 4 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | max_line_length = 88 13 | 14 | # 2 space indentation 15 | [{*.json,*.jsonc,*.yml,*.yaml}] 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Triggers (optional) review requests. 2 | 3 | * @jorenham 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: jorenham 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | target-branch: "master" 6 | schedule: 7 | interval: weekly 8 | labels: 9 | - "is: chore" 10 | - "topic: github actions" 11 | groups: 12 | actions: 13 | patterns: 14 | - "*" 15 | - package-ecosystem: uv 16 | directory: / 17 | target-branch: master 18 | schedule: 19 | interval: weekly 20 | labels: 21 | - "is: chorew" 22 | - "topic: dependencies" 23 | groups: 24 | actions: 25 | patterns: 26 | - "*" 27 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | # This file contains globs for automatically adding labels based on changed files, 2 | # for use with https://github.com/actions/labeler. 3 | 4 | # _core 5 | 6 | optype.Just*: 7 | - changed-files: 8 | - any-glob-to-any-file: 9 | - optype/_core/_just.py 10 | 11 | optype.Can*: 12 | - changed-files: 13 | - any-glob-to-any-file: 14 | - optype/_core/_can.py 15 | 16 | optype.Has*: 17 | - changed-files: 18 | - any-glob-to-any-file: 19 | - optype/_core/_has.py 20 | 21 | optype.Does*: 22 | - changed-files: 23 | - any-glob-to-any-file: 24 | - optype/_core/_does.py 25 | 26 | optype.do_*: 27 | - changed-files: 28 | - any-glob-to-any-file: 29 | - optype/_core/_do.py 30 | 31 | # subpackages 32 | 33 | optype.numpy: 34 | - changed-files: 35 | - any-glob-to-any-file: 36 | - optype/numpy/** 37 | 38 | optype.types: 39 | - changed-files: 40 | - any-glob-to-any-file: 41 | - optype/types/** 42 | 43 | # submodules 44 | 45 | optype.copy: 46 | - changed-files: 47 | - any-glob-to-any-file: 48 | - optype/copy.py 49 | 50 | optype.dataclasses: 51 | - changed-files: 52 | - any-glob-to-any-file: 53 | - optype/dataclasses.py 54 | 55 | optype.dlpack: 56 | - changed-files: 57 | - any-glob-to-any-file: 58 | - optype/dlpack.py 59 | 60 | optype.inspect: 61 | - changed-files: 62 | - any-glob-to-any-file: 63 | - optype/inspect.py 64 | 65 | optype.io: 66 | - changed-files: 67 | - any-glob-to-any-file: 68 | - optype/io.py 69 | 70 | optype.json: 71 | - changed-files: 72 | - any-glob-to-any-file: 73 | - optype/json.py 74 | 75 | optype.pickle: 76 | - changed-files: 77 | - any-glob-to-any-file: 78 | - optype/pickle.py 79 | 80 | optype.string: 81 | - changed-files: 82 | - any-glob-to-any-file: 83 | - optype/string.py 84 | 85 | optype.typing: 86 | - changed-files: 87 | - any-glob-to-any-file: 88 | - optype/typing.py 89 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | categories: 3 | - title: "`optype`" 4 | labels: 5 | - "optype.Can*" 6 | - "optype.Has*" 7 | - "optype.Does*" 8 | - "optype.Just*" 9 | - "optype.do_*" 10 | - title: "`optype.numpy`" 11 | labels: 12 | - "optype.numpy" 13 | - title: "`optype.copy`" 14 | labels: 15 | - "optype.copy" 16 | - title: "`optype.dataclasses`" 17 | labels: 18 | - "optype.dataclasses" 19 | - title: "`optype.dlpack`" 20 | labels: 21 | - "optype.dlpack" 22 | - title: "`optype.inspect`" 23 | labels: 24 | - "optype.inspect" 25 | - title: "`optype.pickle`" 26 | labels: 27 | - "optype.pickle" 28 | - title: "`optype.string`" 29 | labels: 30 | - "optype.string" 31 | - title: "`optype.types`" 32 | labels: 33 | - "optype.types" 34 | - title: "`optype.typing`" 35 | labels: 36 | - "optype.typing" 37 | - title: Documentation 38 | labels: 39 | - "topic: documentation" 40 | - title: Dependencies 41 | labels: 42 | - "topic: dependencies" 43 | - title: Testing 44 | labels: 45 | - "topic: testing" 46 | - title: Continuous integration 47 | labels: 48 | - "topic: github actions" 49 | - title: Other Changes 50 | labels: 51 | - "*" 52 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | permissions: read-all 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | pull_request: 9 | workflow_dispatch: 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} 13 | cancel-in-progress: true 14 | 15 | env: 16 | UV_LOCKED: 1 17 | 18 | jobs: 19 | lint: 20 | timeout-minutes: 5 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | 26 | - uses: scientific-python/repo-review@v0.12.2 27 | with: 28 | plugins: sp-repo-review 29 | 30 | - name: markdownlint 31 | uses: DavidAnson/markdownlint-cli2-action@v20 32 | with: 33 | config: ".markdownlint.yaml" 34 | globs: "**/*.md" 35 | 36 | - name: typos 37 | uses: crate-ci/typos@master 38 | 39 | - name: install uv 40 | uses: astral-sh/setup-uv@v6 41 | with: 42 | python-version: "3.13" 43 | 44 | - name: ruff check 45 | run: uv run ruff check --output-format=github 46 | 47 | - name: ruff format 48 | run: uv run ruff format --check 49 | 50 | - name: basedpyright --verifytypes 51 | run: uv run basedpyright --ignoreexternal --verifytypes optype 52 | 53 | typecheck: 54 | timeout-minutes: 5 55 | runs-on: ubuntu-latest 56 | 57 | strategy: 58 | fail-fast: false 59 | matrix: 60 | np: ["1.25", "1.26", "2.0", "2.1", "2.2"] 61 | 62 | steps: 63 | - uses: actions/checkout@v4 64 | 65 | - name: setup uv 66 | uses: astral-sh/setup-uv@v6 67 | with: 68 | activate-environment: true 69 | python-version: "3.11" 70 | - run: uv sync 71 | 72 | - name: Install numpy ${{ matrix.np }} 73 | run: uv pip install "numpy==${{ matrix.np }}.*" 74 | 75 | - name: basedpyright 76 | run: > 77 | uv run --no-sync 78 | basedpyright -p scripts/config/bpr-np-${{ matrix.np }}.json 79 | 80 | - name: mypy 81 | run: uv run --no-sync scripts/my.py 82 | 83 | test: 84 | timeout-minutes: 5 85 | runs-on: ${{ matrix.os }} 86 | 87 | strategy: 88 | fail-fast: false 89 | matrix: 90 | os: [ubuntu-latest, windows-latest] 91 | py: ["3.11", "3.12", "3.13"] 92 | include: 93 | - os: ubuntu-latest 94 | py: "3.11" 95 | np: "1.25" 96 | 97 | steps: 98 | - uses: actions/checkout@v4 99 | 100 | - name: setup uv 101 | uses: astral-sh/setup-uv@v6 102 | with: 103 | activate-environment: true 104 | python-version: ${{ matrix.py }} 105 | 106 | - name: uv sync 107 | run: uv sync 108 | 109 | - name: Install old numpy 110 | if: ${{ matrix.np == '1.25' }} 111 | run: uv pip install "numpy==${{ matrix.np }}.*" 112 | 113 | - name: pytest 114 | run: uv run --no-sync pytest 115 | -------------------------------------------------------------------------------- /.github/workflows/labeler-pr.yml: -------------------------------------------------------------------------------- 1 | # label PR's based on changed files 2 | name: Pull Request Labeler 3 | on: 4 | pull_request_target: 5 | types: [opened] 6 | 7 | jobs: 8 | label_pull_request: 9 | permissions: 10 | contents: read 11 | pull-requests: write 12 | 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/labeler@v5 16 | if: github.repository == 'jorenham/optype' 17 | -------------------------------------------------------------------------------- /.github/workflows/publish-pypi.yml: -------------------------------------------------------------------------------- 1 | name: build and publish to PyPI 2 | 3 | on: 4 | workflow_dispatch: 5 | release: 6 | types: [prereleased, released] 7 | 8 | env: 9 | UV_LOCKED: 1 10 | 11 | jobs: 12 | pypi-publish: 13 | name: Publish release to PyPI 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 5 16 | environment: 17 | name: pypi 18 | url: https://pypi.org/p/optype 19 | permissions: 20 | id-token: write 21 | steps: 22 | - uses: actions/checkout@v4 23 | - uses: astral-sh/setup-uv@v6 24 | with: 25 | python-version: "3.13" 26 | 27 | - name: uv build 28 | run: uv build 29 | 30 | - name: publish to PyPI 31 | uses: pypa/gh-action-pypi-publish@release/v1 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # Distribution / packaging 7 | build/ 8 | dist/ 9 | site/ 10 | 11 | # Cache 12 | .cache/ 13 | .mypy_cache/ 14 | .pytest_cache/ 15 | .ruff_cache/ 16 | .tox/ 17 | 18 | # Environments 19 | .env 20 | .venv/ 21 | venv/ 22 | 23 | # IntelliJ 24 | .idea/ 25 | .run/ 26 | -------------------------------------------------------------------------------- /.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | default: true 2 | no-hard-tabs: true 3 | 4 | MD007: 5 | indent: 4 6 | MD013: 7 | line_length: 88 8 | MD031: 9 | list_items: false 10 | MD033: 11 | allowed_elements: 12 | - h1 13 | - p 14 | - br 15 | - a 16 | - i 17 | - code 18 | - sup 19 | - sub 20 | - img 21 | - table 22 | - tr 23 | - th 24 | - td 25 | - dl 26 | - dt 27 | - dd 28 | - details 29 | - summary 30 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "charliermarsh.ruff", 4 | "davidanson.vscode-markdownlint", 5 | "detachhead.basedpyright", 6 | "ms-python.mypy-type-checker", 7 | "ms-python.python", 8 | "tekumara.typos-vscode" 9 | ], 10 | "unwantedRecommendations": ["ms-pyright.pyright", "ms-python.vscode-pylance"] 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[markdown]": { 3 | "editor.rulers": [88] 4 | }, 5 | "[python]": { 6 | "editor.defaultFormatter": "charliermarsh.ruff", 7 | "editor.formatOnPaste": true, 8 | "editor.formatOnSave": true, 9 | "editor.formatOnSaveMode": "modificationsIfAvailable", 10 | "editor.formatOnType": true 11 | }, 12 | "editor.rulers": [88], 13 | "git.branchProtection": ["master"], 14 | "mypy-type-checker.args": [ 15 | "--config-file=${workspaceFolder}/pyproject.toml" 16 | ], 17 | "mypy-type-checker.cwd": ".", 18 | "mypy-type-checker.path": [ 19 | "${workspaceFolder}/scripts/my.py" 20 | ], 21 | "python.testing.pytestEnabled": true 22 | } 23 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct - `optype` 2 | 3 | - Don't be a c*nt 4 | - Critize ideas, not people 5 | - Embrace axioms, abolish dogma 6 | - Objectivity beats subjectivity 7 | - Practicality beats purity 8 | - Consistency beats practicality 9 | - Validity beats consistency 10 | - You have the right to be wrong 11 | - When in doubt, look at rule #1 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributing to optype 3 | 4 | First off, thanks for taking the time to contribute! ❤️ 5 | 6 | All types of contributions are encouraged and valued. 7 | See the [Table of Contents](#table-of-contents) for different ways to help and 8 | details about how this project handles them. 9 | Please make sure to read the relevant section before making your contribution. 10 | It will make it a lot easier for us maintainers and smooth out the experience 11 | for all involved. 12 | The community looks forward to your contributions. 🎉 13 | 14 | > [!NOTE] 15 | > And if you like optype, but just don't have time to contribute, that's fine. 16 | > There are other easy ways to support the project and show your appreciation, 17 | > which we would also be very happy about: 18 | > 19 | > - Star the project 20 | > - Tweet about it 21 | > - Refer this project in your project's readme 22 | > - Mention the project at local meetups and tell your friends/colleagues 23 | 24 | 25 | ## Table of Contents 26 | 27 | - [Code of Conduct](#code-of-conduct) 28 | - [I Have a Question](#i-have-a-question) 29 | - [I Want To Contribute](#i-want-to-contribute) 30 | - [Reporting Bugs](#reporting-bugs) 31 | - [Suggesting Enhancements](#suggesting-enhancements) 32 | - [Your First Code Contribution](#your-first-code-contribution) 33 | - [Improving The Documentation](#improving-the-documentation) 34 | 35 | ## Code of Conduct 36 | 37 | This project and everyone participating in it is governed by the 38 | [optype Code of Conduct][COC]. 39 | By participating, you are expected to uphold this code. 40 | Please report unacceptable behavior to `jhammudoglugmailcom`. 41 | 42 | ## I Have a Question 43 | 44 | > [!NOTE] 45 | > If you want to ask a question, we assume that you have read the 46 | > available [Documentation][DOC]. 47 | 48 | Before you ask a question, it is best to search for existing [Issues][BUG] 49 | that might help you. 50 | In case you have found a suitable issue and still need clarification, 51 | you can write your question in this issue. 52 | It is also advisable to search the internet for answers first. 53 | 54 | If you then still feel the need to ask a question and need clarification, we 55 | recommend the following: 56 | 57 | - Open an [Issue][BUG]. 58 | - Provide as much context as you can about what you're running into. 59 | - Provide project and platform versions (Python, mypy, pyright, ruff, etc), 60 | depending on what seems relevant. 61 | 62 | We will then take care of the issue as soon as possible. 63 | 64 | ## I Want To Contribute 65 | 66 | > ### Legal Notice 67 | > 68 | > When contributing to this project, 69 | > you must agree that you have authored 100% of the content, 70 | > that you have the necessary rights to the content and that the content you 71 | > contribute may be provided under the project license. 72 | 73 | ### Reporting Bugs 74 | 75 | 76 | #### Before Submitting a Bug Report 77 | 78 | A good bug report shouldn't leave others needing to chase you up for more 79 | information. 80 | Therefore, we ask you to investigate carefully, collect information and 81 | describe the issue in detail in your report. 82 | Please complete the following steps in advance to help us fix any potential 83 | bug as fast as possible. 84 | 85 | - Make sure that you are using the latest version. 86 | - Determine if your bug is really a bug and not an error on your side e.g. 87 | using incompatible environment components/versions 88 | (Make sure that you have read the [documentation][DOC]. 89 | If you are looking for support, you might want to check 90 | [this section](#i-have-a-question)). 91 | - To see if other users have experienced (and potentially already solved) 92 | the same issue you are having, check if there is not already a bug report 93 | existing for your bug or error in the [bug tracker][BUG]. 94 | - Also make sure to search the internet (including Stack Overflow) to see if 95 | users outside of the GitHub community have discussed the issue. 96 | - Collect information about the bug: 97 | - Stack trace (Traceback) 98 | - OS, Platform and Version (Windows, Linux, macOS, x86, ARM) 99 | - Version of the interpreter, compiler, SDK, runtime environment, 100 | package manager, depending on what seems relevant. 101 | - Possibly your input and the output 102 | - Can you reliably reproduce the issue? 103 | And can you also reproduce it with older versions? 104 | 105 | 106 | #### How Do I Submit a Good Bug Report? 107 | 108 | > You must never report security related issues, vulnerabilities or bugs 109 | including sensitive information to the issue tracker, or elsewhere in public. 110 | Instead sensitive bugs must be sent by email to `jhammudoglugmailcom`. 111 | 112 | We use GitHub issues to track bugs and errors. 113 | If you run into an issue with the project: 114 | 115 | - Open an [Issue][BUG]. 116 | (Since we can't be sure at this point whether it is a bug or not, 117 | we ask you not to talk about a bug yet and not to label the issue.) 118 | - Explain the behavior you would expect and the actual behavior. 119 | - Please provide as much context as possible and describe the 120 | *reproduction steps* that someone else can follow to recreate the issue on 121 | their own. 122 | This usually includes your code. 123 | For good bug reports you should isolate the problem and create a reduced test 124 | case. 125 | - Provide the information you collected in the previous section. 126 | 127 | Once it's filed: 128 | 129 | - The project team will label the issue accordingly. 130 | - A team member will try to reproduce the issue with your provided steps. 131 | If there are no reproduction steps or no obvious way to reproduce the issue, 132 | the team will ask you for those steps and mark the issue as `needs-repro`. 133 | Bugs with the `needs-repro` tag will not be addressed until they are 134 | reproduced. 135 | - If the team is able to reproduce the issue, it will be marked `needs-fix`, 136 | as well as possibly other tags (such as `critical`), and the issue will be 137 | left to be [implemented by someone](#your-first-code-contribution). 138 | 139 | ### Suggesting Enhancements 140 | 141 | This section guides you through submitting an enhancement suggestion for 142 | optype, **including completely new features and minor improvements to existing 143 | functionality**. 144 | Following these guidelines will help maintainers and the community to 145 | understand your suggestion and find related suggestions. 146 | 147 | 148 | #### Before Submitting an Enhancement 149 | 150 | - Make sure that you are using the latest version. 151 | - Read the [documentation][DOC] carefully and find out if the functionality is 152 | already covered, maybe by an individual configuration. 153 | - Perform a [search][BUG] to see if the enhancement has already been suggested. 154 | If it has, add a comment to the existing issue instead of opening a new one. 155 | - Find out whether your idea fits with the scope and aims of the project. 156 | It's up to you to make a strong case to convince the project's developers of 157 | the merits of this feature. Keep in mind that we want features that will be 158 | useful to the majority of our users and not just a small subset. If you're 159 | just targeting a minority of users, consider writing an add-on/plugin library. 160 | 161 | 162 | #### How Do I Submit a Good Enhancement Suggestion? 163 | 164 | Enhancement suggestions are tracked as [GitHub issues][BUG]. 165 | 166 | - Use a **clear and descriptive title** for the issue to identify the 167 | suggestion. 168 | - Provide a **step-by-step description of the suggested enhancement** in as 169 | many details as possible. 170 | - **Describe the current behavior** and **explain which behavior you expected 171 | to see instead** and why. At this point you can also tell which alternatives 172 | do not work for you. 173 | - **Explain why this enhancement would be useful** to most optype users. 174 | You may also want to point out the other projects that solved it better and 175 | which could serve as inspiration. 176 | 177 | ### Your First Code Contribution 178 | 179 | Ensure you have [uv](https://github.com/astral-sh/uv) installed. 180 | Now you can install the dev dependencies: 181 | 182 | ```bash 183 | uv sync 184 | ``` 185 | 186 | ### Tox 187 | 188 | The linters and tests can easily be run with [tox](https://github.com/tox-dev/tox): 189 | 190 | ```bash 191 | uvx tox p 192 | ``` 193 | 194 | This will run the tests in parallel on all supported Python versions. 195 | 196 | ### Improving The Documentation 197 | 198 | All [documentation] lives in the `README.md`. Please read it carefully before 199 | proposing any changes. Ensure that the markdown is formatted correctly with 200 | [markdownlint](https://github.com/DavidAnson/markdownlint/tree/main). 201 | 202 | 203 | ## Attribution 204 | 205 | This guide is based on the **contributing-gen**. 206 | [Make your own](https://github.com/bttger/contributing-gen)! 207 | 208 | [BUG]: https://github.com/jorenham/optype/issues 209 | [COC]: https://github.com/jorenham/optype/blob/master/CODE_OF_CONDUCT.md 210 | [DOC]: https://github.com/jorenham/optype?tab=readme-ov-file#optype 211 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024, Joren Hammudoglu (jorenham). 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Only the latest release is supported. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | To report a security vulnerability, please use the 10 | [Tidelift security contact](https://tidelift.com/security). 11 | Tidelift will coordinate the fix and disclosure. 12 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # optype examples 2 | 3 | Usage examples of `optype`, which are type-checked by pyright (strict mode). 4 | These examples also function as integration tests. 5 | -------------------------------------------------------------------------------- /examples/functor.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import sys 4 | import optype as op 5 | 6 | from typing import Generic, final, overload 7 | 8 | if sys.version_info >= (3, 13): 9 | from typing import override, TypeVar 10 | else: 11 | from typing_extensions import override, TypeVar 12 | 13 | from collections.abc import Callable as CanCall 14 | from types import NotImplementedType 15 | 16 | 17 | _T_co = TypeVar("_T_co", covariant=True) 18 | _T = TypeVar("_T") 19 | _X = TypeVar("_X") 20 | _Y = TypeVar("_Y") 21 | 22 | 23 | @final 24 | class Functor(Generic[_T_co]): 25 | __match_args__ = __slots__ = ("value",) 26 | 27 | def __init__(self, value: _T_co, /) -> None: 28 | self.value = value 29 | 30 | def map1(self: Functor[_T], f: CanCall[[_T], _Y], /) -> Functor[_Y]: 31 | """ 32 | Applies a unary operator `f` over the value of `self`, 33 | and return a new `Functor`. 34 | """ 35 | return Functor(f(self.value)) 36 | 37 | @overload 38 | def map2( 39 | self: Functor[_T], 40 | f: CanCall[[_T, _X], NotImplementedType], 41 | x: Functor[_X], 42 | /, 43 | ) -> NotImplementedType: ... 44 | @overload 45 | def map2( 46 | self: Functor[_T], 47 | f: CanCall[[_T, _X], _Y], 48 | x: Functor[_X], 49 | /, 50 | ) -> Functor[_Y]: ... 51 | def map2( 52 | self: Functor[_T], 53 | f: CanCall[[_T, _X], _Y], 54 | x: Functor[_X], 55 | /, 56 | ) -> Functor[_Y] | NotImplementedType: 57 | """ 58 | Maps the binary operator `f: (T, X) -> Y` over `self: Functor[T]` and 59 | `other: Functor[X]`, and returns `Functor[Y]`. A `NotImplemented` 60 | is returned if `f` is not supported for the types, or if other is not 61 | a `Functor`. 62 | """ 63 | y = f(self.value, x.value) 64 | return NotImplemented if y is NotImplemented else Functor(y) 65 | 66 | @override 67 | def __repr__(self, /) -> str: 68 | return f"{type(self).__name__}({self.value!r})" 69 | 70 | @override 71 | def __hash__(self: Functor[op.CanHash], /) -> int: 72 | return op.do_hash(self.value) 73 | 74 | # unary prefix ops 75 | 76 | def __neg__(self: Functor[op.CanNeg[_Y]], /) -> Functor[_Y]: 77 | """ 78 | >>> -Functor(3.14) 79 | Functor(-3.14) 80 | """ 81 | return self.map1(op.do_neg) 82 | 83 | def __pos__(self: Functor[op.CanPos[_Y]], /) -> Functor[_Y]: 84 | """ 85 | >>> +Functor(True) 86 | Functor(1) 87 | """ 88 | return self.map1(op.do_pos) 89 | 90 | def __invert__(self: Functor[op.CanInvert[_Y]], /) -> Functor[_Y]: 91 | """ 92 | >>> ~Functor(0) 93 | Functor(-1) 94 | """ 95 | return self.map1(op.do_invert) 96 | 97 | # rich comparison ops 98 | 99 | def __lt__(self: Functor[op.CanLt[_X, _Y]], x: Functor[_X], /) -> Functor[_Y]: 100 | """ 101 | >>> Functor({0}) < Functor({0, 1}) 102 | Functor(True) 103 | >>> Functor((0, 1)) < Functor((1, -1)) 104 | Functor(True) 105 | """ 106 | return self.map2(op.do_lt, x) 107 | 108 | def __le__(self: Functor[op.CanLe[_X, _Y]], x: Functor[_X], /) -> Functor[_Y]: 109 | """ 110 | >>> Functor({0}) <= Functor({0, 1}) 111 | Functor(True) 112 | >>> Functor((0, 1)) <= Functor((1, -1)) 113 | Functor(True) 114 | """ 115 | return self.map2(op.do_le, x) 116 | 117 | def __gt__(self, x: Functor[op.CanLt[_T_co, _Y]], /) -> Functor[_Y]: 118 | """ 119 | >>> Functor({0, 1}) > Functor({0}) 120 | Functor(True) 121 | >>> Functor((0, 1)) > Functor((1, -1)) 122 | Functor(False) 123 | """ 124 | return self.map2(op.do_gt, x) 125 | 126 | def __ge__(self, x: Functor[op.CanLe[_T_co, _Y]], /) -> Functor[_Y]: 127 | """ 128 | >>> Functor({0, 1}) >= Functor({0}) 129 | Functor(True) 130 | >>> Functor((0, 1)) >= Functor((1, -1)) 131 | Functor(False) 132 | """ 133 | return self.map2(op.do_ge, x) 134 | 135 | # binary infix ops 136 | 137 | def __add__(self: Functor[op.CanAdd[_X, _Y]], x: Functor[_X], /) -> Functor[_Y]: 138 | """ 139 | >>> Functor(0) + Functor(1) 140 | Functor(1) 141 | >>> Functor(('spam',)) + Functor(('ham',)) + Functor(('eggs',)) 142 | Functor(('spam', 'ham', 'eggs')) 143 | """ 144 | return self.map2(op.do_add, x) 145 | 146 | def __sub__(self: Functor[op.CanSub[_X, _Y]], x: Functor[_X], /) -> Functor[_Y]: 147 | """ 148 | >>> Functor(0) - Functor(1) 149 | Functor(-1) 150 | """ 151 | return self.map2(op.do_sub, x) 152 | 153 | def __mul__(self: Functor[op.CanMul[_X, _Y]], x: Functor[_X], /) -> Functor[_Y]: 154 | """ 155 | >>> Functor(('Developers!',)) * Functor(4) 156 | Functor(('Developers!', 'Developers!', 'Developers!', 'Developers!')) 157 | """ 158 | return self.map2(op.do_mul, x) 159 | 160 | def __matmul__( 161 | self: Functor[op.CanMatmul[_X, _Y]], 162 | x: Functor[_X], 163 | /, 164 | ) -> Functor[_Y]: 165 | return self.map2(op.do_matmul, x) 166 | 167 | def __truediv__( 168 | self: Functor[op.CanTruediv[_X, _Y]], 169 | x: Functor[_X], 170 | /, 171 | ) -> Functor[_Y]: 172 | """ 173 | >>> Functor(1) / Functor(2) 174 | Functor(0.5) 175 | """ 176 | return self.map2(op.do_truediv, x) 177 | 178 | def __floordiv__( 179 | self: Functor[op.CanFloordiv[_X, _Y]], 180 | x: Functor[_X], 181 | /, 182 | ) -> Functor[_Y]: 183 | """ 184 | >>> Functor(1) // Functor(2) 185 | Functor(0) 186 | """ 187 | return self.map2(op.do_floordiv, x) 188 | 189 | def __mod__(self: Functor[op.CanMod[_X, _Y]], x: Functor[_X], /) -> Functor[_Y]: 190 | """ 191 | >>> Functor(10) % Functor(7) 192 | Functor(3) 193 | """ 194 | return self.map2(op.do_mod, x) 195 | 196 | def __pow__(self: Functor[op.CanPow2[_X, _Y]], x: Functor[_X], /) -> Functor[_Y]: 197 | """ 198 | >>> Functor(2) ** Functor(3) 199 | Functor(8) 200 | """ 201 | return self.map2(op.do_pow, x) 202 | 203 | def __lshift__( 204 | self: Functor[op.CanLshift[_X, _Y]], 205 | x: Functor[_X], 206 | /, 207 | ) -> Functor[_Y]: 208 | """ 209 | >>> Functor(1) << Functor(10) 210 | Functor(1024) 211 | """ 212 | return self.map2(op.do_lshift, x) 213 | 214 | def __rshift__( 215 | self: Functor[op.CanRshift[_X, _Y]], 216 | x: Functor[_X], 217 | /, 218 | ) -> Functor[_Y]: 219 | """ 220 | >>> Functor(1024) >> Functor(4) 221 | Functor(64) 222 | """ 223 | return self.map2(op.do_rshift, x) 224 | 225 | def __and__(self: Functor[op.CanAnd[_X, _Y]], x: Functor[_X], /) -> Functor[_Y]: 226 | """ 227 | >>> Functor(True) & Functor(False) 228 | Functor(False) 229 | >>> Functor(3) & Functor(7) 230 | Functor(3) 231 | """ 232 | return self.map2(op.do_and, x) 233 | 234 | def __xor__(self: Functor[op.CanXor[_X, _Y]], x: Functor[_X], /) -> Functor[_Y]: 235 | """ 236 | >>> Functor(True) ^ Functor(False) 237 | Functor(True) 238 | >>> Functor(3) ^ Functor(7) 239 | Functor(4) 240 | """ 241 | return self.map2(op.do_xor, x) 242 | 243 | def __or__(self: Functor[op.CanOr[_X, _Y]], x: Functor[_X], /) -> Functor[_Y]: 244 | """ 245 | >>> Functor(True) | Functor(False) 246 | Functor(True) 247 | >>> Functor(3) | Functor(7) 248 | Functor(7) 249 | """ 250 | return self.map2(op.do_or, x) 251 | -------------------------------------------------------------------------------- /examples/twice.py: -------------------------------------------------------------------------------- 1 | # %% 2 | from typing import Literal, TypeAlias, TypeVar, assert_type 3 | 4 | import optype as op 5 | 6 | Y = TypeVar("Y") 7 | Two: TypeAlias = Literal[2] 8 | 9 | 10 | def twice(x: op.CanRMul[Two, Y], /) -> Y: 11 | return 2 * x 12 | 13 | 14 | # %% 15 | assert_type(twice(True), int) 16 | assert_type(twice(1 / 137), float) 17 | assert_type(twice(str(-1 / 12)), str) 18 | assert_type(twice([object()]), list[object]) 19 | 20 | 21 | # %% 22 | def twice2(x: op.CanRMul[Two, Y] | op.CanMul[Two, Y], /) -> Y: 23 | return 2 * x if isinstance(x, op.CanRMul) else x * 2 24 | 25 | 26 | # %% 27 | class RMulThing: 28 | def __rmul__(self, y: Two, /) -> str: 29 | return f"{y} * _" 30 | 31 | 32 | assert_type(twice2(RMulThing()), str) 33 | assert twice2(RMulThing()) == "2 * _" 34 | 35 | 36 | # %% 37 | class MulThing: 38 | def __mul__(self, y: Two, /) -> str: 39 | return f"_ * {y}" 40 | 41 | 42 | assert_type(twice2(MulThing()), str) 43 | assert twice2(MulThing()) == "_ * 2" 44 | -------------------------------------------------------------------------------- /optype/__init__.py: -------------------------------------------------------------------------------- 1 | # ruff: noqa: F403 2 | import importlib as _importlib 3 | import importlib.metadata as _metadata 4 | 5 | from optype import _core 6 | from optype._core import * 7 | 8 | __version__: str = _metadata.version(__package__ or __file__.split("/")[-1]) 9 | __all__ = ["__version__"] 10 | __all__ += _core.__all__ 11 | 12 | _submodules = { 13 | "copy": "copy", 14 | "dataclasses": "dataclasses", 15 | "dlpack": "dlpack", 16 | "inspect": "inspect", 17 | "io": "io", 18 | "json": "json", 19 | "numpy": "numpy", 20 | "pickle": "pickle", 21 | "string": "string", 22 | "types": "types", 23 | "typing": "typing", 24 | } 25 | __all__ += list(_submodules) # pyright: ignore[reportUnsupportedDunderAll] 26 | 27 | 28 | def __0(dubdub: str, lubba: str, /) -> str: 29 | return "".join(chr(ord(wubba) ^ ord(lubba)) for wubba in dubdub) 30 | 31 | 32 | # stop digging for hidden layers and be impressed 33 | _submodules[__0("🦞🦅🦏🦇", "🧬")] = __0("🤢🤻🤱🤹🤾🤷", "🥒") 34 | 35 | 36 | def __dir__() -> list[str]: 37 | return __all__ 38 | 39 | 40 | def __getattr__(name: str) -> object: 41 | if submodule := _submodules.get(name): 42 | return _importlib.import_module(f"{__name__}.{submodule}") 43 | try: 44 | return globals()[name] 45 | except KeyError: 46 | msg = f"module '{__name__}' has no attribute '{name}'" 47 | module = _importlib.import_module(__name__) 48 | raise AttributeError(msg, name=name, obj=module) from None 49 | -------------------------------------------------------------------------------- /optype/_core/__init__.py: -------------------------------------------------------------------------------- 1 | # ruff: noqa: F403 2 | from optype._core import _can, _do, _does, _has, _just 3 | from optype._core._can import * 4 | from optype._core._do import * 5 | from optype._core._does import * 6 | from optype._core._has import * 7 | from optype._core._just import * 8 | 9 | __all__: list[str] = [] 10 | __all__ += _just.__all__ 11 | __all__ += _can.__all__ 12 | __all__ += _has.__all__ 13 | __all__ += _does.__all__ 14 | __all__ += _do.__all__ 15 | 16 | 17 | def __dir__() -> list[str]: 18 | return __all__ 19 | 20 | 21 | def __rewrite_module_names() -> None: 22 | name_self = __dir__.__module__ 23 | name_base = name_self.split(".")[0] 24 | assert name_base != name_self 25 | assert name_base.isidentifier() 26 | 27 | for module in [_can, _do, _does, _has, _just]: 28 | for name in module.__all__: 29 | member: type = getattr(module, name) 30 | if member.__module__.startswith(name_self): 31 | member.__module__ = name_base 32 | 33 | 34 | __rewrite_module_names() 35 | -------------------------------------------------------------------------------- /optype/_core/_has.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import types 3 | from collections.abc import Callable, Iterable, Mapping 4 | from typing import Any, ClassVar, LiteralString, TypeAlias 5 | 6 | if sys.version_info >= (3, 13): 7 | from typing import ( 8 | ParamSpec, 9 | Protocol, 10 | TypeVar, 11 | TypeVarTuple, 12 | override, 13 | runtime_checkable, 14 | ) 15 | else: 16 | from typing_extensions import ( 17 | ParamSpec, 18 | Protocol, 19 | TypeVar, 20 | TypeVarTuple, 21 | override, 22 | runtime_checkable, 23 | ) 24 | 25 | __all__ = [ 26 | "HasAnnotations", 27 | "HasClass", 28 | "HasCode", 29 | "HasDict", 30 | "HasDoc", 31 | "HasFunc", 32 | "HasMatchArgs", 33 | "HasModule", 34 | "HasName", 35 | "HasNames", 36 | "HasQualname", 37 | "HasSelf", 38 | "HasSlots", 39 | "HasTypeParams", 40 | "HasWrapped", 41 | ] 42 | 43 | 44 | def __dir__() -> list[str]: 45 | return __all__ 46 | 47 | 48 | ### 49 | 50 | _TypeParams: TypeAlias = tuple[TypeVar | ParamSpec | TypeVarTuple, ...] 51 | 52 | _TypeT = TypeVar("_TypeT", bound=type) 53 | _ObjectT_co = TypeVar("_ObjectT_co", default=object, covariant=True) 54 | _FuncT_co = TypeVar("_FuncT_co", bound=Callable[..., object], covariant=True) 55 | _TypeParamsT = TypeVar("_TypeParamsT", bound=_TypeParams, default=_TypeParams) 56 | 57 | __AnyMapping: TypeAlias = "Mapping[str, object]" 58 | __AnyDict: TypeAlias = dict[str, Any] 59 | _DictT = TypeVar("_DictT", bound=__AnyMapping, default=__AnyDict) 60 | _DictT_co = TypeVar("_DictT_co", bound=__AnyMapping, default=__AnyDict, covariant=True) 61 | 62 | _NameT = TypeVar("_NameT", bound=str, default=str) 63 | _QualNameT = TypeVar("_QualNameT", bound=str, default=_NameT) 64 | _StrT_co = TypeVar("_StrT_co", bound=str, default=str, covariant=True) 65 | 66 | 67 | ### 68 | 69 | 70 | @runtime_checkable 71 | class HasMatchArgs(Protocol): 72 | __match_args__: ClassVar[tuple[LiteralString, ...] | list[LiteralString]] 73 | 74 | 75 | @runtime_checkable 76 | class HasSlots(Protocol): 77 | __slots__: ClassVar[LiteralString | Iterable[LiteralString]] 78 | 79 | 80 | @runtime_checkable 81 | class HasDict(Protocol[_DictT]): # type: ignore[misc] 82 | # the typeshed annotations for `builtins.object.__dict__` too narrow 83 | __dict__: _DictT # type: ignore[assignment] # pyright: ignore[reportIncompatibleVariableOverride] 84 | 85 | 86 | @runtime_checkable 87 | class HasClass(Protocol[_TypeT]): 88 | """ 89 | Can be seen as the **invariant** inverse of `type[T]`, i.e. `HasClass[type[T]]` 90 | represents (but is not equivalent to) `T`. However, `HasClass` is stricter, and 91 | rejects types that aren't fully static, such as `int | str`. 92 | 93 | It works best to annotate input types within `.pyi` stubs. If we, for example, have 94 | `def typeof(obj, /): return type(obj)` at runtime, we can stub it like this: 95 | 96 | ```pyi 97 | def typeof[TypeT: type](obj: HasClass[TypeT], /) -> TypeT: ... 98 | ``` 99 | 100 | It behaves the same as `type` if we use fully static types: 101 | 102 | ```pyi 103 | just_int: int 104 | 105 | type(just_int) # type[int] 106 | typeof(just_int) # type[int] 107 | ``` 108 | 109 | but when we have a dynamic type, the `HasClass` will reject it, but `type` will 110 | accept it: 111 | 112 | ```pyi 113 | int_or_str: int | str 114 | 115 | type(int_or_str) # type[int | str] 116 | typeof(int_or_str) # Error: misc (mypy), reportArgumentType (pyright) 117 | ``` 118 | 119 | The inferred `type[int | str]` type by `type` does not represent a concrete type 120 | that can exist at runtime. The (stricter) `typeof` function therefore doesn't 121 | accept it, and both mypy and pyright will report it as an error. So `typeof` can 122 | be seen as more "realistic", or "less abstract", in that way, and it better reflects 123 | the possible runtime outcomes of `typeof`, than that `type` does of itself. 124 | """ 125 | 126 | @property # type: ignore[override] # mypy bug 127 | @override 128 | def __class__(self) -> _TypeT: ... 129 | @__class__.setter 130 | @override 131 | def __class__(self, __class__: _TypeT, /) -> None: ... 132 | 133 | 134 | @runtime_checkable 135 | class HasModule(Protocol[_StrT_co]): 136 | __module__: _StrT_co 137 | 138 | 139 | @runtime_checkable 140 | class HasName(Protocol[_NameT]): 141 | __name__: _NameT 142 | 143 | 144 | @runtime_checkable 145 | class HasQualname(Protocol[_NameT]): # pyright: ignore[reportInvalidTypeVarUse] 146 | __qualname__: _NameT 147 | 148 | 149 | @runtime_checkable 150 | class HasNames( # pyright: ignore[reportIncompatibleVariableOverride, reportInvalidTypeVarUse] 151 | HasName[_NameT], 152 | HasQualname[_QualNameT], 153 | Protocol[_NameT, _QualNameT], 154 | ): ... 155 | 156 | 157 | # docs and type hints 158 | 159 | 160 | @runtime_checkable 161 | class HasDoc(Protocol[_StrT_co]): 162 | # note that docstrings are stripped if ran with e.g. `python -OO` 163 | __doc__: _StrT_co | None 164 | 165 | 166 | @runtime_checkable 167 | class HasAnnotations(Protocol[_DictT_co]): # pyright: ignore[reportInvalidTypeVarUse] 168 | __annotations__: _DictT_co # type: ignore[assignment] # pyright: ignore[reportIncompatibleVariableOverride] 169 | 170 | 171 | @runtime_checkable 172 | class HasTypeParams(Protocol[_TypeParamsT]): 173 | __type_params__: _TypeParamsT 174 | 175 | 176 | # functions and methods 177 | 178 | 179 | @runtime_checkable 180 | class HasFunc(Protocol[_FuncT_co]): 181 | @property 182 | def __func__(self, /) -> _FuncT_co: ... 183 | 184 | 185 | @runtime_checkable 186 | class HasWrapped(Protocol[_FuncT_co]): 187 | @property 188 | def __wrapped__(self, /) -> _FuncT_co: ... 189 | 190 | 191 | @runtime_checkable 192 | class HasSelf(Protocol[_ObjectT_co]): 193 | @property 194 | def __self__(self, /) -> _ObjectT_co: ... 195 | 196 | 197 | @runtime_checkable 198 | class HasCode(Protocol): 199 | __code__: types.CodeType 200 | -------------------------------------------------------------------------------- /optype/_core/_just.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | import sys 3 | from typing import ( 4 | Any, 5 | Generic, 6 | Protocol, 7 | Self, 8 | TypeAlias, 9 | _ProtocolMeta, # noqa: PLC2701 10 | final, 11 | ) 12 | 13 | if sys.version_info >= (3, 13): 14 | from typing import TypeIs, TypeVar, override, runtime_checkable 15 | else: 16 | from typing_extensions import TypeIs, TypeVar, override, runtime_checkable 17 | 18 | from ._can import CanFloat, CanIndex 19 | 20 | __all__ = [ 21 | "Just", 22 | "JustBytes", 23 | "JustComplex", 24 | "JustDate", 25 | "JustFloat", 26 | "JustInt", 27 | "JustObject", 28 | ] 29 | 30 | 31 | def __dir__() -> list[str]: 32 | return __all__ 33 | 34 | 35 | ### 36 | 37 | 38 | _T = TypeVar("_T") 39 | _TypeT = TypeVar("_TypeT", bound=type) 40 | _ObjectT = TypeVar("_ObjectT", default=object) 41 | 42 | _CanFloatOrIndex: TypeAlias = CanFloat | CanIndex 43 | 44 | 45 | ### 46 | 47 | 48 | # NOTE: Both mypy and pyright incorrectly report LSP violations in `@final` protocols, 49 | # even though these are purely structural, and therefore the LSP does not apply. 50 | 51 | # mypy: disable-error-code="override, explicit-override" 52 | # pyright: reportIncompatibleMethodOverride=false 53 | 54 | 55 | @final # https://github.com/python/mypy/issues/17288 56 | class Just(Protocol[_T]): # type: ignore[misc] 57 | """ 58 | An runtime-checkable invariant type "wrapper", where `Just[T]` only accepts 59 | instances of `T`, and but rejects instances of any strict subtypes of `T`. 60 | 61 | Note that e.g. `Literal[""]` and `LiteralString` are not a strict `str` subtypes, 62 | and are therefore assignable to `Just[str]`, but instances of `class S(str): ...` 63 | are **not** assignable to `Just[str]`. 64 | 65 | Warning (`pyright<1.1.390` and `basedpyright<1.22.1`): 66 | On `pyright<1.1.390` and `basedpyright<1.22.1` this `Just[T]` type does not 67 | work, due to a bug in the `typeshed` stubs for `object.__class__`, which was 68 | fixed in https://github.com/python/typeshed/pull/13021. 69 | 70 | However, you could use `JustInt`, `JustFloat`, and `JustComplex` types work 71 | around this: These already work on `pyright<1.1.390` without problem. 72 | 73 | Warning (`mypy<1.15` and `basedmypy<2.10`): 74 | On `mypy<1.15` this does not work with promoted types, such as `float` and 75 | `complex`, which was fixed in https://github.com/python/mypy/pull/18360. 76 | For other ("unpromoted") types like `Just[int]`, this already works, even 77 | before the `typeshed` fix above (mypy ignores `@property` setter types and 78 | overwrites it with the getter's return type). 79 | 80 | Note: 81 | This protocol is not marked with `@runtime_checkable`, because using 82 | `isinstance(value, Just)` would be `True` for any type or object, and has no 83 | semantic meaning. 84 | """ 85 | 86 | @property 87 | @override 88 | def __class__(self, /) -> type[_T]: ... 89 | @__class__.setter 90 | def __class__(self, t: type[_T], /) -> None: ... 91 | 92 | 93 | @final 94 | class _JustMeta(_ProtocolMeta, Generic[_ObjectT]): 95 | __just_class__: type[_ObjectT] # pyright: ignore[reportUninitializedInstanceVariable] 96 | 97 | def __new__( # noqa: PYI019 98 | mcls: type[_TypeT], 99 | /, 100 | *args: *tuple[str, tuple[type, ...], dict[str, Any]], 101 | just: type[_ObjectT], 102 | ) -> _TypeT: 103 | self = super().__new__(mcls, *args) # type: ignore[misc] 104 | self.__just_class__ = just 105 | return self 106 | 107 | @override 108 | def __instancecheck__(self, instance: object) -> TypeIs[_ObjectT]: 109 | return self.__subclasscheck__(type(instance)) 110 | 111 | @override 112 | def __subclasscheck__(self, subclass: Any) -> TypeIs[type[_ObjectT]]: 113 | tp = self.__just_class__ 114 | 115 | if isinstance(subclass, int): 116 | # "bare" bool and int literals 117 | subclass = type(subclass) 118 | 119 | if not isinstance(subclass, type): 120 | # unwrap subscripted generics, with special-casing for `Just[...]` 121 | from types import GenericAlias # noqa: I001, PLC0415 122 | from optype.types._typeforms import GenericType # noqa: PLC0415 123 | 124 | while type(subclass) in {GenericType, GenericAlias}: 125 | origin = subclass.__origin__ 126 | if origin is Just: 127 | (subclass,) = subclass.__args__ 128 | else: 129 | subclass = origin 130 | 131 | if not isinstance(subclass, type): 132 | raise TypeError("issubclass() arg 1 must be a class") from None 133 | 134 | return subclass is tp 135 | 136 | 137 | @runtime_checkable 138 | @final # https://github.com/python/mypy/issues/17288 139 | class JustBytes(Protocol, metaclass=_JustMeta, just=bytes): # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] 140 | """ 141 | A runtime checkable `Just[bytes]`, that also works on `pyright<1.1.390`. 142 | 143 | Useful as workaround for `mypy`'s `bytes` promotion (which can be disabled with the 144 | undocumented `--disable-bytearray-promotion` and `--disable-memoryview-promotion` 145 | flags). See https://github.com/python/mypy/issues/15313s for more info. 146 | Note that this workaround requires `mypy >=1.15` or the `--disable-*-promotion` 147 | flags to work. 148 | """ 149 | 150 | @property 151 | @override 152 | def __class__(self, /) -> type[bytes]: ... 153 | @__class__.setter 154 | def __class__(self, t: type[bytes], /) -> None: ... 155 | 156 | 157 | @runtime_checkable 158 | @final # https://github.com/python/mypy/issues/17288 159 | class JustInt(Protocol, metaclass=_JustMeta, just=int): # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] 160 | """ 161 | A runtime-checkable `Just[int]` that's compatible with `pyright<1.1.390`. 162 | 163 | Example: 164 | This example shows a situation you want to accept instances of just `int`, 165 | and reject and other instances, even they're subtypes of `int` such as `bool`, 166 | 167 | ```python 168 | def f(x: int, /) -> int: 169 | assert type(x) is int 170 | return x 171 | 172 | 173 | f(1337) # accepted 174 | f(True) # accepted :( 175 | ``` 176 | 177 | But because `bool <: int`, booleans are also assignable to `int`, which we 178 | don't want to allow. 179 | 180 | Previously, it was considered impossible to achieve this using just python 181 | typing, and it was told that a PEP would be necessary to make it work. 182 | 183 | But this is not the case at all, and `optype` provides a clean counter-example 184 | that works with pyright and (even) mypy: 185 | 186 | ```python 187 | import optype as op 188 | 189 | 190 | def f_fixed(x: op.JustInt, /) -> int: 191 | assert type(x) is int 192 | return x # accepted 193 | 194 | 195 | f_fixed(1337) # accepted 196 | f_fixed(True) # rejected :) 197 | ``` 198 | 199 | Note: 200 | On `mypy` this behaves in the same way as `Just[int]`, which accidentally 201 | already works because of a mypy bug. 202 | """ 203 | 204 | @property 205 | @override 206 | def __class__(self, /) -> type[int]: ... 207 | @__class__.setter 208 | def __class__(self, t: type[int], /) -> None: ... 209 | 210 | # workaround for `pyright<1.1.390` and `basedpyright<1.22.1` 211 | def __new__(cls, x: str | bytes | bytearray, /, base: CanIndex) -> Self: ... 212 | 213 | 214 | @runtime_checkable 215 | @final # https://github.com/python/mypy/issues/17288 216 | class JustFloat(Protocol, metaclass=_JustMeta, just=float): # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] 217 | """ 218 | A runtime checkable `Just[float]` that also works on `pyright<1.1.390`. 219 | 220 | Warning: 221 | Unlike `JustInt`, this does not work on `mypy<1.41.2`. 222 | """ 223 | 224 | @property 225 | @override 226 | def __class__(self, /) -> type[float]: ... 227 | @__class__.setter 228 | def __class__(self, t: type[float], /) -> None: ... 229 | 230 | # workaround for `pyright<1.1.390` and `basedpyright<1.22.1` 231 | def hex(self, /) -> str: ... 232 | 233 | 234 | @runtime_checkable 235 | @final # https://github.com/python/mypy/issues/17288 236 | class JustComplex(Protocol, metaclass=_JustMeta, just=complex): # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] 237 | """ 238 | A runtime checkable `Just[complex]`, that also works on `pyright<1.1.390`. 239 | 240 | Warning: 241 | Unlike `JustInt`, this does not work on `mypy<1.41.2`. 242 | """ 243 | 244 | @property 245 | @override 246 | def __class__(self, /) -> type[complex]: ... 247 | @__class__.setter 248 | def __class__(self, t: type[complex], /) -> None: ... 249 | 250 | # workaround for `pyright<1.1.390` and `basedpyright<1.22.1` 251 | def __new__(cls, /, real: _CanFloatOrIndex, imag: _CanFloatOrIndex) -> Self: ... 252 | 253 | 254 | @runtime_checkable 255 | @final # https://github.com/python/mypy/issues/17288 256 | class JustDate(Protocol, metaclass=_JustMeta, just=dt.date): # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] 257 | """ 258 | A runtime checkable `Just[datetime.Date]`, that rejects `datetime.datetime`. 259 | """ 260 | 261 | @property 262 | @override 263 | def __class__(self, /) -> type[dt.date]: ... 264 | @__class__.setter 265 | def __class__(self, t: type[dt.date], /) -> None: ... 266 | 267 | 268 | @runtime_checkable 269 | @final # https://github.com/python/mypy/issues/17288 270 | class JustObject(Protocol, metaclass=_JustMeta, just=object): # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] 271 | """ 272 | A runtime checkable `Just[object]`, that also works on `pyright<1.1.390`. 273 | 274 | Useful for typing `object()` sentinels. 275 | """ 276 | 277 | @property 278 | @override 279 | def __class__(self, /) -> type[object]: ... 280 | @__class__.setter 281 | def __class__(self, t: type[object], /) -> None: ... 282 | -------------------------------------------------------------------------------- /optype/_utils.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import types 3 | from collections.abc import Callable 4 | from typing import LiteralString, Protocol 5 | 6 | if sys.version_info >= (3, 13): 7 | from typing import TypeVar, is_protocol 8 | else: 9 | from typing_extensions import TypeVar, is_protocol 10 | 11 | __all__ = "get_callables", "set_module" 12 | 13 | 14 | def __dir__() -> tuple[str, str]: 15 | return __all__ 16 | 17 | 18 | ### 19 | 20 | 21 | # cannot reuse `optype._has._HasModule` due to circular imports 22 | class _HasModule(Protocol): 23 | __module__: str 24 | 25 | 26 | _HasModuleT = TypeVar("_HasModuleT", bound=_HasModule) 27 | 28 | 29 | ### 30 | 31 | 32 | def get_callables( 33 | module: types.ModuleType, 34 | /, 35 | *, 36 | protocols: bool = False, 37 | private: bool = False, 38 | ) -> frozenset[str]: 39 | """ 40 | Return the public callables (types are callables too) in the given module, 41 | except for `typing.Protocol`. 42 | """ 43 | exclude = frozenset({"typing", "typing_extensions", __name__}) 44 | return frozenset({ 45 | name 46 | for name in dir(module) 47 | if callable(cls := getattr(module, name)) 48 | and (private or not name.startswith("_")) 49 | and (protocols or not (isinstance(cls, type) and is_protocol(cls))) 50 | and cls.__module__ not in exclude 51 | }) 52 | 53 | 54 | def set_module(module: LiteralString, /) -> Callable[[_HasModuleT], _HasModuleT]: 55 | """ 56 | Private decorator for overriding the `__module__` of a function or a class. 57 | 58 | If used on a function that `typing.overload`s, then apply `@set_module` 59 | to each overload *first*, to avoid breaking `typing.get_overloads`. 60 | For example: 61 | 62 | ```python 63 | from typing import overload 64 | 65 | 66 | @overload 67 | @set_module("spamlib") 68 | def process(response: None, /) -> None: ... 69 | 70 | 71 | @overload 72 | @set_module("spamlib") 73 | def process(response: bytes, /) -> str: ... 74 | 75 | 76 | @set_module("spamlib") 77 | def process(response: byes | None, /) -> str | None: 78 | pass # implementation here 79 | ``` 80 | """ 81 | assert all(name.isidentifier() for name in module.split(".")) 82 | 83 | def do_set_module(has_module: _HasModuleT, /) -> _HasModuleT: 84 | assert hasattr(has_module, "__module__") 85 | has_module.__module__ = module 86 | return has_module 87 | 88 | return do_set_module 89 | -------------------------------------------------------------------------------- /optype/copy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Runtime-protocols for the `copy` standard library. 3 | https://docs.python.org/3/library/copy.html 4 | """ 5 | 6 | import sys 7 | from typing import Protocol, Self 8 | 9 | if sys.version_info >= (3, 13): 10 | from typing import TypeVar, override, runtime_checkable 11 | else: 12 | from typing_extensions import TypeVar, override, runtime_checkable 13 | 14 | __all__ = ( 15 | "CanCopy", 16 | "CanCopySelf", 17 | "CanDeepcopy", 18 | "CanDeepcopySelf", 19 | "CanReplace", 20 | "CanReplaceSelf", 21 | ) 22 | 23 | 24 | def __dir__() -> tuple[str, ...]: 25 | return __all__ 26 | 27 | 28 | _T_co = TypeVar("_T_co", covariant=True) 29 | _VT_contra = TypeVar("_VT_contra", contravariant=True) 30 | _VT0_contra = TypeVar("_VT0_contra", contravariant=True, default=object) 31 | 32 | 33 | @runtime_checkable 34 | class CanCopy(Protocol[_T_co]): 35 | """Anything that can be used as `copy.copy(_: CanCopy[T]) -> T`.""" 36 | 37 | def __copy__(self, /) -> _T_co: ... 38 | 39 | 40 | @runtime_checkable 41 | class CanCopySelf(CanCopy["CanCopySelf"], Protocol): 42 | """Runtime-checkable alias `CanCopySelf = CanCopy[Self]`.""" 43 | 44 | @override 45 | def __copy__(self, /) -> Self: ... 46 | 47 | 48 | @runtime_checkable 49 | class CanDeepcopy(Protocol[_T_co]): 50 | """Anything that can be used as `copy.deepcopy(_: CanDeepcopy[T]) -> T`.""" 51 | 52 | def __deepcopy__(self, memo: dict[int, object], /) -> _T_co: ... 53 | 54 | 55 | @runtime_checkable 56 | class CanDeepcopySelf(CanDeepcopy["CanDeepcopySelf"], Protocol): 57 | """Runtime-checkable alias `CanDeepcopySelf = CanDeepcopy[Self]`.""" 58 | 59 | @override 60 | def __deepcopy__(self, memo: dict[int, object], /) -> Self: ... 61 | 62 | 63 | @runtime_checkable 64 | class CanReplace(Protocol[_VT_contra, _T_co]): 65 | """ 66 | Anything that can be used as `copy.replace(_: CanReplace[-V, +T]) -> T` (since 67 | Python 3.13+). 68 | """ 69 | 70 | def __replace__(self, /, **changes: _VT_contra) -> _T_co: ... 71 | 72 | 73 | @runtime_checkable 74 | class CanReplaceSelf( 75 | CanReplace[_VT0_contra, "CanReplaceSelf[_VT0_contra]"], 76 | Protocol[_VT0_contra], 77 | ): 78 | """Runtime-checkable alias `CanReplaceSelf[-V = object] = CanReplace[V, Self]`.""" 79 | 80 | @override 81 | def __replace__(self, /, **changes: _VT0_contra) -> Self: ... 82 | -------------------------------------------------------------------------------- /optype/dataclasses.py: -------------------------------------------------------------------------------- 1 | """ 2 | Runtime-protocols for the `dataclasses` standard library. 3 | https://docs.python.org/3/library/dataclasses.html 4 | """ 5 | 6 | import dataclasses 7 | import sys 8 | from collections.abc import Mapping 9 | from typing import Any, Protocol, TypeAlias 10 | 11 | if sys.version_info >= (3, 13): 12 | from typing import TypeVar, runtime_checkable 13 | else: 14 | from typing_extensions import TypeVar, runtime_checkable 15 | 16 | __all__ = ("HasDataclassFields",) 17 | 18 | 19 | def __dir__() -> tuple[str]: 20 | return __all__ 21 | 22 | 23 | ### 24 | 25 | __Field: TypeAlias = dataclasses.Field[Any] 26 | _FieldsT = TypeVar("_FieldsT", bound=Mapping[str, __Field], default=dict[str, __Field]) 27 | 28 | 29 | ### 30 | 31 | 32 | @runtime_checkable 33 | class HasDataclassFields(Protocol[_FieldsT]): 34 | """Can be used to check whether a type or instance is a dataclass.""" 35 | 36 | __dataclass_fields__: _FieldsT 37 | -------------------------------------------------------------------------------- /optype/dlpack.py: -------------------------------------------------------------------------------- 1 | """ 2 | Types and interfaces for DLPack, as used the Array API. 3 | https://github.com/dmlc/dlpack 4 | """ 5 | 6 | import enum 7 | import sys 8 | from typing import Any, Protocol 9 | 10 | if sys.version_info >= (3, 13): 11 | from types import CapsuleType 12 | from typing import TypeVar, runtime_checkable 13 | else: 14 | from typing_extensions import CapsuleType, TypeVar, runtime_checkable 15 | 16 | __all__ = "CanDLPack", "CanDLPackCompat", "CanDLPackDevice" 17 | 18 | 19 | def __dir__() -> tuple[str, str, str]: 20 | return __all__ 21 | 22 | 23 | ### 24 | 25 | _TypeT_co = TypeVar("_TypeT_co", bound=enum.Enum | int, default=int, covariant=True) 26 | _DeviceT_co = TypeVar("_DeviceT_co", bound=int, default=int, covariant=True) 27 | 28 | ### 29 | 30 | 31 | class DLDeviceType(enum.IntEnum): 32 | # GPU device 33 | CPU = 1 34 | # Cuda GPU device 35 | CUDA = 2 36 | # Pinned CUDA GPU memory vy `cudaMallocHost` 37 | CPU_PINNED = 3 38 | # OpenCL devices 39 | OPENCL = 4 40 | # Vulkan buffer for next generation graphics 41 | VULKAN = 7 42 | # Metal for Apple GPU 43 | METAL = 8 44 | # Verilog simulation buffer 45 | VPI = 9 46 | # ROCm GPU's for AMD GPU's 47 | ROCM = 10 48 | # CUDA managed/unified memory allocated by `cudaMallocManaged` 49 | CUDA_MANAGED = 13 50 | # Unified shared memory allocated on a oneAPI non-partititioned 51 | # device. Call to oneAPI runtime is required to determine the device 52 | # type, the USM allocation type and the sycl context it is bound to. 53 | ONE_API = 14 54 | 55 | 56 | class DLDataTypeCode(enum.IntEnum): 57 | # signed integer 58 | INT = 0 59 | # unsigned integer 60 | UINT = 1 61 | # IEEE floating point 62 | FLOAT = 2 63 | # Opaque handle type, reserved for testing purposes. 64 | OPAQUE_HANDLE = 3 65 | # bfloat16 66 | BFLOAT = 4 67 | # complex number (C/C++/Python layout: compact struct per complex number) 68 | COMPLEX = 5 69 | # boolean 70 | BOOL = 6 71 | 72 | 73 | # NOTE: Because `__dlpack__` doesn't mutate the type, and the type parameters bind to 74 | # the *co*variant `tuple`, they should be *co*variant; NOT *contra*variant! 75 | # (This shows that PEP 695 claim -- variance can always be inferred -- is nonsense.) 76 | 77 | 78 | # requires numpy>=2.1 79 | @runtime_checkable 80 | class CanDLPack(Protocol[_TypeT_co, _DeviceT_co]): # type: ignore[misc] # pyright: ignore[reportInvalidTypeVarUse] 81 | def __dlpack__( 82 | self, 83 | /, 84 | *, 85 | stream: int | None = None, 86 | max_version: tuple[int, int] | None = None, 87 | dl_device: tuple[_TypeT_co, _DeviceT_co] | None = None, 88 | # NOTE: This should be `bool | None`, but because of an incorrect annotation in 89 | # `numpy.ndarray.__dlpack__` on `numpy < 2.2.0`, this is not possible. 90 | copy: bool | Any | None = None, 91 | ) -> CapsuleType: ... 92 | 93 | 94 | @runtime_checkable 95 | class CanDLPackCompat(Protocol): 96 | def __dlpack__(self, /, *, stream: None = None) -> CapsuleType: ... 97 | 98 | 99 | @runtime_checkable 100 | class CanDLPackDevice(Protocol[_TypeT_co, _DeviceT_co]): 101 | def __dlpack_device__(self, /) -> tuple[_TypeT_co, _DeviceT_co]: ... 102 | -------------------------------------------------------------------------------- /optype/io.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from typing import Literal, Protocol, TypeAlias as Alias 3 | 4 | if sys.version_info >= (3, 13): 5 | from typing import TypeVar, runtime_checkable 6 | else: 7 | from typing_extensions import TypeVar, runtime_checkable 8 | 9 | __all__ = ( 10 | "CanFSPath", "CanFileno", 11 | "CanFlush", "CanRead", "CanReadN", "CanReadline", "CanReadlineN", "CanWrite", 12 | "ModeAB", "ModeABU", "ModeAB_", 13 | "ModeAT", "ModeATU", "ModeAT_", 14 | "ModeOB", "ModeOBU", "ModeOB_", 15 | "ModeOT", "ModeOTU", "ModeOT_", 16 | "ModeRB", "ModeRBU", "ModeRB_", 17 | "ModeRT", "ModeRTU", "ModeRT_", 18 | "ModeWB", "ModeWBU", "ModeWB_", 19 | "ModeWT", "ModeWTU", "ModeWT_", 20 | "ModeXB", "ModeXBU", "ModeXB_", 21 | "ModeXT", "ModeXTU", "ModeXT_", 22 | "Mode_B", "Mode_BU", "Mode_B_", 23 | "Mode_T", "Mode_TU", "Mode_T_", 24 | "ToFileno", "ToPath", 25 | ) # fmt: skip 26 | 27 | 28 | def __dir__() -> tuple[str, ...]: 29 | return __all__ 30 | 31 | 32 | ### 33 | 34 | # not a type parameter 35 | _StrOrBytes = TypeVar("_StrOrBytes", str, bytes, str | bytes, default=str | bytes) 36 | 37 | _T_co = TypeVar("_T_co", covariant=True) 38 | _T_contra = TypeVar("_T_contra", contravariant=True) 39 | _RT_co = TypeVar("_RT_co", default=object, covariant=True) 40 | 41 | _PathT_co = TypeVar("_PathT_co", bound=str | bytes, default=str | bytes, covariant=True) 42 | 43 | ### 44 | 45 | 46 | @runtime_checkable 47 | class CanFSPath(Protocol[_PathT_co]): 48 | """ 49 | Similar to `os.PathLike`, but is is actually a protocol, doesn't incorrectly use a 50 | `TypeVar` with constraints", and therefore doesn't force type-unsafe usage of `Any` 51 | to express `str | bytes`. 52 | """ 53 | 54 | def __fspath__(self, /) -> _PathT_co: ... 55 | 56 | 57 | @runtime_checkable 58 | class CanFileno(Protocol): 59 | """Runtime-checkable equivalent of `_typeshed.HasFileno`.""" 60 | 61 | def fileno(self, /) -> int: ... 62 | 63 | 64 | @runtime_checkable 65 | class CanRead(Protocol[_T_co]): 66 | """ 67 | Like `_typeshed.SupportsRead`, but without the required positional `int` argument, 68 | and is runtime-checkable. 69 | """ 70 | 71 | def read(self, /) -> _T_co: ... 72 | 73 | 74 | @runtime_checkable 75 | class CanReadN(Protocol[_T_co]): 76 | """Runtime-checkable equivalent of `_typeshed.SupportsRead`.""" 77 | 78 | def read(self, n: int = ..., /) -> _T_co: ... 79 | 80 | 81 | @runtime_checkable 82 | class CanReadline(Protocol[_T_co]): 83 | """ 84 | Runtime-checkable equivalent of `_typeshed.SupportsNoArgReadline`, that 85 | additionally allows `self` to be positional-only. 86 | """ 87 | 88 | def readline(self, /) -> _T_co: ... 89 | 90 | 91 | @runtime_checkable 92 | class CanReadlineN(Protocol[_T_co]): 93 | """Runtime-checkable equivalent of `_typeshed.SupportsReadline`.""" 94 | 95 | def readline(self, n: int = ..., /) -> _T_co: ... 96 | 97 | 98 | @runtime_checkable 99 | class CanWrite(Protocol[_T_contra, _RT_co]): 100 | """ 101 | Runtime-checkable equivalent of `_typeshed.SupportsWrite`, with an additional 102 | optional type parameter for the return type, that defaults to `object`. 103 | """ 104 | 105 | def write(self, data: _T_contra, /) -> _RT_co: ... 106 | 107 | 108 | @runtime_checkable 109 | class CanFlush(Protocol[_RT_co]): 110 | """ 111 | Runtime-checkable equivalent of `_typeshed.SupportsFlush`, with an additional 112 | optional type parameter for the return type, that defaults to `object`. 113 | """ 114 | 115 | def flush(self, /) -> _RT_co: ... 116 | 117 | 118 | ### 119 | 120 | # runtime-checkable `_typeshed.{Str,Bytes,StrOrBytes,Generic}Path` alternative 121 | ToPath: Alias = _StrOrBytes | CanFSPath[_StrOrBytes] 122 | 123 | # runtime-checkable `_typeshed.FileDescriptorLike` equivalent 124 | ToFileno: Alias = int | CanFileno 125 | 126 | 127 | ### 128 | 129 | # Literals for `builtins.open` file modes: 130 | # https://docs.python.org/3/library/functions.html#open 131 | # 132 | # The names follow the pattern `Mode(R|W|X|A|O|_)(B|T|_)(U|_)?`, where each group 133 | # matches a specific set of characters according to the following mapping: 134 | # 135 | # 1. 136 | # - R -> 'r' (read) 137 | # - W -> 'w' (write, truncate) 138 | # - X -> 'x' (write, exclusive creation) 139 | # - A -> 'a' (write, append) 140 | # - O -> 'w' | 'x' | 'a' (write) 141 | # - _ -> 'r' | 'w' | 'x' | 'a' (any) 142 | # 2. 143 | # - B -> 'b' 144 | # - T -> 't' | '' 145 | # 3. 146 | # - U -> '+' 147 | # - _ -> '+' | '' 148 | 149 | ModeRB: Alias = Literal["rb", "br"] # _typeshed.OpenBinaryModeReading (minus 'U') 150 | ModeRBU: Alias = Literal["rb+", "r+b", "+rb", "br+", "b+r", "+br"] 151 | ModeRB_: Alias = Literal[ModeRB, ModeRBU] 152 | ModeRT: Alias = Literal["r", "rt", "tr"] # _typeshed.OpenTextModeReading (minus 'U') 153 | ModeRTU: Alias = Literal["r+", "+r", "rt+", "r+t", "+rt", "tr+", "t+r", "+tr"] 154 | ModeRT_: Alias = Literal[ModeRT, ModeRTU] 155 | 156 | ModeWB: Alias = Literal["wb", "bw"] 157 | ModeWBU: Alias = Literal["wb+", "w+b", "+wb", "bw+", "b+w", "+bw"] 158 | ModeWB_: Alias = Literal[ModeWB, ModeWBU] 159 | ModeWT: Alias = Literal["w", "wt", "tw"] 160 | ModeWTU: Alias = Literal["w+", "+w", "wt+", "w+t", "+wt", "tw+", "t+w", "+tw"] 161 | ModeWT_: Alias = Literal[ModeWT, ModeWTU] 162 | 163 | ModeXB: Alias = Literal["xb", "bx"] 164 | ModeXBU: Alias = Literal["xb+", "x+b", "+xb", "bx+", "b+x", "+bx"] 165 | ModeXB_: Alias = Literal[ModeXB, ModeXBU] 166 | ModeXT: Alias = Literal["x", "xt", "tx"] 167 | ModeXTU: Alias = Literal["x+", "+x", "xt+", "x+t", "+xt", "tx+", "t+x", "+tx"] 168 | ModeXT_: Alias = Literal[ModeXT, ModeXTU] 169 | 170 | ModeAB: Alias = Literal["ab", "ba"] 171 | ModeABU: Alias = Literal["ab+", "a+b", "+ab", "ba+", "b+a", "+ba"] 172 | ModeAB_: Alias = Literal[ModeAB, ModeABU] 173 | ModeAT: Alias = Literal["a", "at", "ta"] 174 | ModeATU: Alias = Literal["a+", "+a", "at+", "a+t", "+at", "ta+", "t+a", "+ta"] 175 | ModeAT_: Alias = Literal[ModeAT, ModeATU] 176 | 177 | ModeOB: Alias = Literal[ModeWB, ModeXB, ModeAB] # typeshed.OpenBinaryModeWriting 178 | ModeOBU: Alias = Literal[ModeWBU, ModeXBU, ModeABU] 179 | ModeOB_: Alias = Literal[ModeWB_, ModeXB_, ModeAB_] 180 | ModeOT: Alias = Literal[ModeWT, ModeXT, ModeAT] # _typeshed.OpenTextModeWriting 181 | ModeOTU: Alias = Literal[ModeWTU, ModeXTU, ModeATU] 182 | ModeOT_: Alias = Literal[ModeWT_, ModeXT_, ModeAT_] 183 | 184 | Mode_B: Alias = Literal[ModeRB, ModeOB] # _typeshed.OpenBinaryModeUpdating 185 | Mode_BU: Alias = Literal[ModeRBU, ModeOBU] # _typeshed.OpenBinaryMode (minus 'U') 186 | Mode_B_: Alias = Literal[ModeRB_, ModeOB_] 187 | Mode_T: Alias = Literal[ModeRT, ModeOT] 188 | Mode_TU: Alias = Literal[ModeRTU, ModeOTU] # _typeshed.OpenTextModeUpdating 189 | Mode_T_: Alias = Literal[ModeRT_, ModeOT_] # _typeshed.OpenTextMode (minus 'U') 190 | -------------------------------------------------------------------------------- /optype/json.py: -------------------------------------------------------------------------------- 1 | """ 2 | Type aliases for `json` standard library. 3 | This assumes that the default encoder and decoder are used. 4 | """ 5 | 6 | import sys 7 | from types import MappingProxyType 8 | from typing import TypeAlias 9 | 10 | if sys.version_info >= (3, 13): 11 | from typing import TypeVar 12 | else: 13 | from typing_extensions import TypeVar 14 | 15 | __all__ = "AnyArray", "AnyObject", "AnyValue", "Array", "Object", "_Value" 16 | 17 | 18 | def __dir__() -> tuple[str, ...]: 19 | return __all__ 20 | 21 | 22 | ### 23 | 24 | 25 | _Primitive: TypeAlias = bool | int | float | str | None 26 | _Value: TypeAlias = _Primitive | dict[str, "_Value"] | list["_Value"] 27 | _AnyValue: TypeAlias = ( 28 | _Primitive 29 | # NOTE: `TypedDict` can't be included here, since it's not a sub*type* of 30 | # `dict[str, Any]` according to the typing docs and typeshed, even though 31 | # it **literally** is a subclass of `dict`... 32 | | dict[str, "_AnyValue"] 33 | | MappingProxyType[str, "_AnyValue"] 34 | | list["_AnyValue"] 35 | | tuple["_AnyValue", ...] 36 | ) 37 | 38 | _VT = TypeVar("_VT", bound=_Value, default=_Value) 39 | _AVT = TypeVar("_AVT", bound=_AnyValue, default=_AnyValue) 40 | 41 | 42 | # Return types of `json.load[s]` 43 | 44 | Array: TypeAlias = list[_VT] 45 | Object: TypeAlias = dict[str, _VT] 46 | # ensure that `Value | Array | Object` is equivalent to `Value` 47 | Value: TypeAlias = _Value | Array | Object 48 | 49 | 50 | # Input types of `json.dumps` 51 | 52 | AnyArray: TypeAlias = list[_AVT] | tuple[_AVT, ...] 53 | AnyObject: TypeAlias = dict[str, _AVT] 54 | AnyValue: TypeAlias = _AnyValue | AnyArray | AnyObject | Value 55 | -------------------------------------------------------------------------------- /optype/numpy/__init__.py: -------------------------------------------------------------------------------- 1 | # ruff: noqa: F403 2 | from . import ( 3 | _any_array, 4 | _any_dtype, 5 | _array, 6 | _dtype, 7 | _is, 8 | _literals, 9 | _scalar, 10 | _sequence_nd, 11 | _shape, 12 | _to, 13 | _ufunc, 14 | compat, 15 | ctypeslib, 16 | random, 17 | ) 18 | from ._any_array import * 19 | from ._any_dtype import * 20 | from ._array import * 21 | from ._dtype import * 22 | from ._is import * 23 | from ._literals import * 24 | from ._scalar import * 25 | from ._sequence_nd import * 26 | from ._shape import * 27 | from ._to import * 28 | from ._ufunc import * 29 | 30 | __all__ = ["compat", "ctypeslib", "random"] 31 | __all__ += _literals.__all__ 32 | __all__ += _array.__all__ 33 | __all__ += _any_array.__all__ 34 | __all__ += _sequence_nd.__all__ 35 | __all__ += _dtype.__all__ 36 | __all__ += _any_dtype.__all__ 37 | __all__ += _shape.__all__ 38 | __all__ += _is.__all__ 39 | __all__ += _to.__all__ 40 | __all__ += _ufunc.__all__ 41 | __all__ += _scalar.__all__ 42 | 43 | 44 | def __dir__() -> list[str]: 45 | return __all__ 46 | -------------------------------------------------------------------------------- /optype/numpy/_any_array.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from collections.abc import Iterator 3 | from typing import Any, Never, Protocol, TypeAlias 4 | 5 | if sys.version_info >= (3, 13): 6 | from typing import TypeAliasType, TypeVar 7 | else: 8 | from typing_extensions import TypeAliasType, TypeVar 9 | 10 | import numpy as np 11 | 12 | import optype.numpy._compat as _x 13 | import optype.numpy._scalar as _sc 14 | from ._shape import AnyShape 15 | from optype._core import CanBuffer, JustComplex, JustFloat, JustInt, JustObject 16 | from optype._utils import set_module 17 | 18 | # ruff: noqa: RUF022 19 | __all__ = [ 20 | "AnyArray", 21 | "AnyNumberArray", 22 | "AnyIntegerArray", 23 | "AnyUnsignedIntegerArray", 24 | "AnySignedIntegerArray", 25 | "AnyInexactArray", 26 | "AnyFloatingArray", 27 | "AnyComplexFloatingArray", 28 | "AnyFlexibleArray", 29 | "AnyCharacterArray", 30 | 31 | "AnyBoolArray", 32 | 33 | "AnyUIntArray", "AnyIntArray", 34 | "AnyUInt8Array", "AnyInt8Array", 35 | "AnyUInt8Array", "AnyInt8Array", 36 | "AnyUInt16Array", "AnyInt16Array", 37 | "AnyUInt32Array", "AnyInt32Array", 38 | "AnyUInt64Array", "AnyInt64Array", 39 | "AnyUByteArray", "AnyByteArray", 40 | "AnyUShortArray", "AnyShortArray", 41 | "AnyUIntCArray", "AnyIntCArray", 42 | "AnyUIntPArray", "AnyIntPArray", 43 | "AnyULongArray", "AnyLongArray", 44 | "AnyULongLongArray", "AnyLongLongArray", 45 | 46 | "AnyFloat16Array", 47 | "AnyFloat32Array", "AnyComplex64Array", 48 | "AnyFloat64Array", "AnyComplex128Array", 49 | "AnyLongDoubleArray", "AnyCLongDoubleArray", 50 | 51 | "AnyDateTime64Array", 52 | "AnyTimeDelta64Array", 53 | 54 | "AnyBytesArray", 55 | "AnyStrArray", 56 | "AnyVoidArray", 57 | "AnyObjectArray", 58 | "AnyStringArray", 59 | ] # fmt: skip 60 | 61 | 62 | def __dir__() -> list[str]: 63 | return __all__ 64 | 65 | 66 | ### 67 | 68 | 69 | _T = TypeVar("_T") 70 | _ST = TypeVar("_ST", bound=np.generic, default=Any) 71 | _VT = TypeVar("_VT", default=_ST) 72 | _T_co = TypeVar("_T_co", covariant=True) 73 | _ST_co = TypeVar("_ST_co", bound=np.generic, covariant=True) 74 | 75 | 76 | # NOTE: Does not include scalar types 77 | class _AnyArrayNP(Protocol[_ST_co]): 78 | def __len__(self, /) -> int: ... 79 | def __array__(self, /) -> np.ndarray[AnyShape, np.dtype[_ST_co]]: ... 80 | 81 | 82 | # NOTE: does not include tuple 83 | class _AnyArrayPY0(Protocol[_T_co]): 84 | def __len__(self, /) -> int: ... 85 | def __getitem__(self, i: int, /) -> "_T_co | _AnyArrayPY0[_T_co]": ... 86 | def __reversed__(self, /) -> Iterator["_T_co | _AnyArrayPY0[_T_co]"]: ... 87 | def index(self, x: Any, /) -> int: ... 88 | 89 | 90 | _AnyArrayPY: TypeAlias = tuple[_T, ...] | _AnyArrayPY0[_T] 91 | _AnyArray = TypeAliasType( 92 | "_AnyArray", 93 | _AnyArrayNP[_ST] | _AnyArrayPY[_VT] | _AnyArrayPY[_AnyArrayNP[_ST]], 94 | type_params=(_ST, _VT), 95 | ) 96 | 97 | ### 98 | 99 | AnyArray: TypeAlias = _AnyArray[_ST, object] | CanBuffer 100 | 101 | AnyNumberArray: TypeAlias = _AnyArray[ 102 | _sc.number, 103 | _sc.number | JustInt | JustFloat | JustComplex, 104 | ] 105 | AnyIntegerArray: TypeAlias = _AnyArray[_sc.integer, _sc.integer | JustInt] 106 | AnySignedIntegerArray: TypeAlias = _AnyArray[_sc.sinteger, _sc.sinteger | JustInt] 107 | AnyUnsignedIntegerArray: TypeAlias = _AnyArray[_sc.uinteger] 108 | AnyInexactArray: TypeAlias = _AnyArray[ 109 | _sc.inexact, 110 | _sc.inexact | JustFloat | JustComplex, 111 | ] 112 | 113 | AnyBoolArray: TypeAlias = _AnyArray[_x.Bool, _x.Bool | bool] 114 | 115 | AnyUInt8Array: TypeAlias = _AnyArray[np.uint8, np.uint8 | CanBuffer] | CanBuffer 116 | AnyUByteArray = AnyUInt8Array 117 | AnyUInt16Array: TypeAlias = _AnyArray[np.uint16] 118 | AnyUShortArray = AnyUInt16Array 119 | AnyUInt32Array: TypeAlias = _AnyArray[np.uint32] 120 | AnyUInt64Array: TypeAlias = _AnyArray[np.uint64] 121 | AnyUIntCArray: TypeAlias = _AnyArray[np.uintc] 122 | AnyULongLongArray: TypeAlias = _AnyArray[np.ulonglong] 123 | AnyULongArray: TypeAlias = _AnyArray[_x.ULong] 124 | AnyUIntPArray: TypeAlias = _AnyArray[np.uintp] 125 | AnyUIntArray: TypeAlias = _AnyArray[np.uint] 126 | 127 | AnyInt8Array: TypeAlias = _AnyArray[np.int8] 128 | AnyByteArray = AnyInt8Array 129 | AnyInt16Array: TypeAlias = _AnyArray[np.int16] 130 | AnyShortArray = AnyInt16Array 131 | AnyInt32Array: TypeAlias = _AnyArray[np.int32] 132 | AnyInt64Array: TypeAlias = _AnyArray[np.int64] 133 | AnyIntCArray: TypeAlias = _AnyArray[np.intc] 134 | AnyLongLongArray: TypeAlias = _AnyArray[np.longlong] 135 | AnyLongArray: TypeAlias = _AnyArray[_x.Long] # no int (numpy<=1) 136 | AnyIntPArray: TypeAlias = _AnyArray[np.intp] # no int (numpy>=2) 137 | AnyIntArray: TypeAlias = _AnyArray[np.int_, np.int_ | JustInt] 138 | 139 | AnyFloatingArray: TypeAlias = _AnyArray[_sc.floating, _sc.floating | JustFloat] 140 | AnyFloat16Array: TypeAlias = _AnyArray[np.float16] 141 | AnyFloat32Array: TypeAlias = _AnyArray[np.float32] 142 | AnyFloat64Array: TypeAlias = _AnyArray[_sc.floating64, _sc.floating64 | JustFloat] 143 | AnyLongDoubleArray: TypeAlias = _AnyArray[np.longdouble] 144 | 145 | AnyComplexFloatingArray: TypeAlias = _AnyArray[ 146 | _sc.cfloating, 147 | _sc.cfloating | JustComplex, 148 | ] 149 | AnyComplex64Array: TypeAlias = _AnyArray[np.complex64] 150 | AnyComplex128Array: TypeAlias = _AnyArray[ 151 | _sc.cfloating64, 152 | _sc.cfloating64 | JustComplex, 153 | ] 154 | AnyCLongDoubleArray: TypeAlias = _AnyArray[np.clongdouble] 155 | 156 | AnyCharacterArray: TypeAlias = _AnyArray[np.character, np.character | bytes | str] 157 | AnyBytesArray: TypeAlias = _AnyArray[np.bytes_, np.bytes_ | bytes] 158 | AnyStrArray: TypeAlias = _AnyArray[np.str_, np.str_ | str] 159 | 160 | AnyFlexibleArray: TypeAlias = _AnyArray[np.flexible, np.flexible | bytes | str] 161 | AnyVoidArray: TypeAlias = _AnyArray[np.void] # TODO: structural types 162 | 163 | AnyDateTime64Array: TypeAlias = _AnyArray[np.datetime64] 164 | AnyTimeDelta64Array: TypeAlias = _AnyArray[np.timedelta64] 165 | AnyObjectArray: TypeAlias = _AnyArray[np.object_, np.object_ | JustObject] 166 | 167 | 168 | if _x.NP20: # `numpy>=2.0` 169 | 170 | @set_module("optype.numpy") 171 | class AnyStringArray(Protocol): 172 | def __len__(self, /) -> int: ... 173 | 174 | if _x.NP21: # numpy>=2.1 175 | 176 | def __array__(self, /) -> np.ndarray[AnyShape, np.dtypes.StringDType]: ... 177 | 178 | else: # numpy==2.0.* 179 | 180 | def __array__(self, /) -> np.ndarray[AnyShape, np.dtype[Never]]: ... 181 | 182 | else: # `numpy<2.0` 183 | AnyStringArray: TypeAlias = Never 184 | -------------------------------------------------------------------------------- /optype/numpy/_any_dtype.py: -------------------------------------------------------------------------------- 1 | """ 2 | The allowed `np.dtype` arguments for specific scalar types. 3 | The names are analogous to those in `numpy.dtypes`. 4 | """ 5 | 6 | import sys 7 | from collections.abc import Sequence 8 | from typing import Any, Never, TypeAlias 9 | 10 | if sys.version_info >= (3, 13): 11 | from typing import TypeAliasType 12 | else: 13 | from typing_extensions import TypeAliasType 14 | 15 | import numpy as np 16 | 17 | import optype.numpy._compat as _x 18 | import optype.numpy._dtype_attr as a 19 | import optype.numpy._scalar as _sc 20 | from ._dtype import HasDType, ToDType as To 21 | from optype._core._just import Just, JustComplex, JustFloat, JustInt 22 | 23 | # ruff: noqa: RUF022 24 | __all__ = [ 25 | "AnyDType", 26 | "AnyNumberDType", 27 | "AnyIntegerDType", 28 | "AnyInexactDType", 29 | "AnyFlexibleDType", 30 | "AnyUnsignedIntegerDType", 31 | "AnySignedIntegerDType", 32 | "AnyFloatingDType", 33 | "AnyComplexFloatingDType", 34 | "AnyCharacterDType", 35 | 36 | "AnyBoolDType", 37 | 38 | "AnyUIntDType", 39 | "AnyUInt8DType", 40 | "AnyUInt8DType", 41 | "AnyUInt16DType", 42 | "AnyUInt32DType", 43 | "AnyUInt64DType", 44 | "AnyUIntPDType", 45 | "AnyUByteDType", 46 | "AnyUShortDType", 47 | "AnyUIntCDType", 48 | "AnyULongDType", 49 | "AnyULongLongDType", 50 | 51 | "AnyIntDType", 52 | "AnyInt8DType", 53 | "AnyInt8DType", 54 | "AnyInt16DType", 55 | "AnyInt32DType", 56 | "AnyInt64DType", 57 | "AnyIntPDType", 58 | "AnyByteDType", 59 | "AnyShortDType", 60 | "AnyIntCDType", 61 | "AnyLongDType", 62 | "AnyLongLongDType", 63 | 64 | "AnyFloat16DType", 65 | "AnyFloat32DType", 66 | "AnyFloat64DType", 67 | "AnyLongDoubleDType", 68 | 69 | "AnyComplex64DType", 70 | "AnyComplex128DType", 71 | "AnyCLongDoubleDType", 72 | 73 | "AnyDateTime64DType", 74 | "AnyTimeDelta64DType", 75 | 76 | "AnyBytesDType", 77 | "AnyBytes8DType", 78 | "AnyStrDType", 79 | "AnyVoidDType", 80 | "AnyObjectDType", 81 | 82 | "AnyStringDType", 83 | ] # fmt: skip 84 | 85 | 86 | def __dir__() -> list[str]: 87 | return __all__ 88 | 89 | 90 | ### 91 | 92 | b_cls: TypeAlias = type[bool] # noqa: PYI042 # final 93 | i_cls: TypeAlias = type[JustInt] # noqa: PYI042 94 | f_cls: TypeAlias = type[JustFloat] # noqa: PYI042 95 | c_cls: TypeAlias = type[JustComplex] # noqa: PYI042 96 | fc_cls: TypeAlias = f_cls | c_cls # noqa: PYI042 97 | ifc_cls: TypeAlias = i_cls | fc_cls # noqa: PYI042 98 | 99 | O_cls: TypeAlias = type[Just[object]] 100 | S_cls: TypeAlias = type[Just[bytes]] 101 | U_cls: TypeAlias = type[Just[str]] 102 | V_cls: TypeAlias = type[memoryview] # final 103 | SU_cls: TypeAlias = S_cls | U_cls 104 | SUV_cls: TypeAlias = SU_cls | V_cls 105 | 106 | 107 | ### 108 | 109 | 110 | # b1 111 | AnyBoolDType = TypeAliasType("AnyBoolDType", b_cls | To[_x.Bool] | a.b1_code) 112 | 113 | # i1 114 | AnyInt8DType = TypeAliasType("AnyInt8DType", To[np.int8] | a.i1_code) 115 | AnyByteDType = AnyInt8DType # deprecated 116 | # u1 117 | AnyUInt8DType = TypeAliasType("AnyUInt8DType", To[np.uint8] | a.u1_code) 118 | AnyUByteDType = AnyUInt8DType # deprecated 119 | # i2 120 | AnyInt16DType = TypeAliasType("AnyInt16DType", To[np.int16] | a.i2_code) 121 | AnyShortDType = AnyInt16DType # deprecated 122 | # u2 123 | AnyUInt16DType = TypeAliasType("AnyUInt16DType", To[np.uint16] | a.u2_code) 124 | AnyUShortDType = AnyUInt16DType # deprecated 125 | # i4 126 | AnyInt32DType = TypeAliasType("AnyInt32DType", To[np.int32] | a.i4_code) 127 | AnyIntCDType = AnyInt32DType # deprecated 128 | # u4 129 | AnyUInt32DType = TypeAliasType("AnyUInt32DType", To[np.uint32] | a.u4_code) 130 | AnyUIntCDType = AnyUInt32DType # deprecated 131 | # i8 132 | AnyInt64DType = TypeAliasType("AnyInt64DType", To[np.int64] | a.i8_code) 133 | AnyLongLongDType = AnyInt64DType # deprecated 134 | # u8 135 | AnyUInt64DType = TypeAliasType("AnyUInt64DType", To[np.uint64] | a.u8_code) 136 | AnyULongLongDType = AnyUInt64DType # deprecated 137 | # int_ / intp / long 138 | if _x.NP20: 139 | AnyIntPDType = TypeAliasType("AnyIntPDType", i_cls | To[np.intp] | a.i0_code) 140 | AnyIntDType = AnyIntPDType 141 | AnyLongDType = TypeAliasType("AnyLongDType", To[np.long] | a.l_code) 142 | else: 143 | AnyIntPDType = TypeAliasType("AnyIntPDType", To[np.intp] | a.i0_code) 144 | AnyIntDType = TypeAliasType("AnyIntDType", i_cls | To[np.int_] | a.l_code) 145 | AnyLongDType = AnyIntDType 146 | 147 | # uint / uintp / ulong 148 | AnyUIntPDType = TypeAliasType("AnyUIntPDType", To[np.uintp] | a.u0_code) 149 | if _x.NP20: 150 | AnyULongDType = TypeAliasType("AnyULongDType", To[np.ulong] | a.L_code) 151 | AnyUIntDType = AnyULongDType 152 | else: 153 | AnyULongDType = TypeAliasType("AnyULongDType", To[np.uint] | a.L_code) 154 | AnyUIntDType = AnyULongDType 155 | 156 | 157 | # f2 158 | AnyFloat16DType = TypeAliasType("AnyFloat16DType", To[_sc.floating16] | a.f2_code) 159 | # f4 160 | AnyFloat32DType = TypeAliasType("AnyFloat32DType", To[_sc.floating32] | a.f4_code) 161 | # f8 162 | AnyFloat64DType = TypeAliasType( 163 | "AnyFloat64DType", 164 | f_cls | To[_sc.floating64] | a.f8_code | None, 165 | ) 166 | # f12 | f16 167 | AnyLongDoubleDType = TypeAliasType("AnyLongDoubleDType", To[np.longdouble] | a.g_code) 168 | 169 | # c8 170 | AnyComplex64DType = TypeAliasType("AnyComplex64DType", To[_sc.cfloating32] | a.c8_code) 171 | # c16 172 | AnyComplex128DType = TypeAliasType( 173 | "AnyComplex128DType", 174 | c_cls | To[_sc.cfloating64] | a.c16_code, 175 | ) 176 | # c24 | c32 177 | AnyCLongDoubleDType = TypeAliasType( 178 | "AnyCLongDoubleDType", 179 | To[np.clongdouble] | a.G_code, 180 | ) 181 | 182 | # M / N8 183 | AnyDateTime64DType = TypeAliasType("AnyDateTime64DType", To[np.datetime64] | a.M_code) 184 | # m / m8 185 | AnyTimeDelta64DType = TypeAliasType( 186 | "AnyTimeDelta64DType", 187 | To[np.timedelta64] | a.m_code, 188 | ) 189 | 190 | # flexible 191 | 192 | AnyBytesDType = TypeAliasType("AnyBytesDType", S_cls | To[np.bytes_] | a.S0_code) 193 | AnyBytes8DType = TypeAliasType("AnyBytes8DType", a.S1_code) 194 | AnyStrDType = TypeAliasType("AnyStrDType", S_cls | To[np.str_] | a.U0_code) 195 | 196 | _ToShape: TypeAlias = tuple[int, ...] | int 197 | _ToName: TypeAlias = str | tuple[str, str] 198 | _ToDType: TypeAlias = To[Any] | str 199 | _ToStructured: TypeAlias = ( 200 | tuple[_ToDType, _ToShape] 201 | | Sequence[tuple[_ToName, _ToDType] | tuple[_ToName, _ToDType, _ToShape]] 202 | ) 203 | AnyVoidDType = TypeAliasType( 204 | "AnyVoidDType", 205 | V_cls | To[np.void] | a.V0_code | _ToStructured, 206 | ) 207 | 208 | # object 209 | 210 | AnyObjectDType = TypeAliasType("AnyObjectDType", O_cls | To[np.object_] | a.O_code) 211 | AnyDType = TypeAliasType("AnyDType", _ToDType | _ToStructured) 212 | 213 | # abstract 214 | 215 | AnySignedIntegerDType = TypeAliasType( 216 | "AnySignedIntegerDType", 217 | i_cls | To[_sc.sinteger] | a.ix_code, 218 | ) 219 | AnyUnsignedIntegerDType = TypeAliasType( 220 | "AnyUnsignedIntegerDType", 221 | To[_sc.uinteger] | a.ux_code, 222 | ) 223 | AnyFloatingDType = TypeAliasType( 224 | "AnyFloatingDType", 225 | f_cls | To[_sc.floating] | a.fx_code, 226 | ) 227 | AnyComplexFloatingDType = TypeAliasType( 228 | "AnyComplexFloatingDType", 229 | c_cls | To[_sc.cfloating] | a.cx_code, 230 | ) 231 | 232 | AnyIntegerDType = TypeAliasType("AnyIntegerDType", i_cls | To[_sc.integer] | a.iu_code) 233 | AnyInexactDType = TypeAliasType("AnyInexactDType", fc_cls | To[_sc.inexact] | a.fc_code) 234 | AnyNumberDType = TypeAliasType("AnyNumberDType", ifc_cls | To[_sc.number] | a.iufc_code) 235 | 236 | AnyCharacterDType = TypeAliasType( 237 | "AnyCharacterDType", 238 | SU_cls | To[np.character] | a.SU_code, 239 | ) 240 | AnyFlexibleDType = TypeAliasType( 241 | "AnyFlexibleDType", 242 | SUV_cls | To[np.flexible] | a.SUV_code, 243 | ) 244 | 245 | 246 | if _x.NP21: 247 | # `numpy>=2.1` 248 | _HasStringDType: TypeAlias = HasDType[np.dtypes.StringDType] 249 | AnyStringDType = TypeAliasType("AnyStringDType", _HasStringDType | a.T_code) 250 | elif _x.NP20: 251 | AnyStringDType = TypeAliasType("AnyStringDType", np.dtype[Never] | a.T_code) 252 | else: 253 | AnyStringDType = TypeAliasType("AnyStringDType", Never) 254 | -------------------------------------------------------------------------------- /optype/numpy/_array.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from collections.abc import Mapping 3 | from typing import Any, Protocol, Self 4 | 5 | if sys.version_info >= (3, 13): 6 | from typing import TypeAliasType, TypeVar, runtime_checkable 7 | else: 8 | from typing_extensions import TypeAliasType, TypeVar, runtime_checkable 9 | 10 | import numpy as np 11 | 12 | import optype.numpy._compat as _x 13 | from ._shape import AnyShape, Shape 14 | from optype._utils import set_module 15 | 16 | __all__ = [ # noqa: RUF022 17 | "Array", "Array0D", "Array1D", "Array1D", "Array2D", "Array3D", "ArrayND", 18 | "MArray", "MArray0D", "MArray1D", "MArray1D", "MArray2D", "MArray3D", 19 | "Matrix", 20 | "CanArray", "CanArray0D", "CanArray1D", "CanArray2D", "CanArray3D", "CanArrayND", 21 | "CanArrayFinalize", "CanArrayWrap", 22 | "HasArrayInterface", "HasArrayPriority", 23 | ] # fmt: skip 24 | 25 | 26 | def __dir__() -> list[str]: 27 | return __all__ 28 | 29 | 30 | ### 31 | 32 | _NDT = TypeVar("_NDT", bound=Shape, default=AnyShape) 33 | _NDT_co = TypeVar("_NDT_co", bound=Shape, default=AnyShape, covariant=True) 34 | _DTT = TypeVar("_DTT", bound=np.dtype[Any], default=np.dtype[Any]) 35 | _DTT_co = TypeVar("_DTT_co", bound=np.dtype[Any], default=np.dtype[Any], covariant=True) 36 | _SCT = TypeVar("_SCT", bound=np.generic, default=Any) 37 | _SCT_co = TypeVar("_SCT_co", bound=np.generic, default=Any, covariant=True) 38 | 39 | 40 | Matrix = TypeAliasType( 41 | "Matrix", 42 | np.matrix[tuple[int, int], np.dtype[_SCT]], 43 | type_params=(_SCT,), 44 | ) 45 | """ 46 | Alias of `np.matrix` that is similar to `ArrayND`: 47 | 48 | ```py 49 | type Matrix[ 50 | SCT: np.generic = np.generic, 51 | M: int = int, 52 | N: int = M, 53 | ] = np.matrix[tuple[M, N], np.dtype[SCT]] 54 | ``` 55 | 56 | Only a "base" `int` type, or `Literal` or positive integers should be used as type 57 | arguments to `MT` and `NT`. Be careful not to pass it a `bool` or any other `int` 58 | subtype such as `Never`. There's also no need to use `Any` for `MT` or `NT`, as 59 | the (variadic) type parameters of `tuple` are covariant (even though that's 60 | supposed to be illegal for variadic type params, which makes no fucking sense). 61 | """ 62 | 63 | Array = TypeAliasType( 64 | "Array", 65 | np.ndarray[_NDT, np.dtype[_SCT]], 66 | type_params=(_NDT, _SCT), 67 | ) 68 | """ 69 | Shape-typed array alias, defined as: 70 | 71 | ```py 72 | type Array[ 73 | NDT: (int, ...) = (int, ...), 74 | SCT: np.generic = np.generic, 75 | ] = np.ndarray[NDT, np.dtype[SCT]] 76 | ``` 77 | """ 78 | 79 | ArrayND = TypeAliasType( 80 | "ArrayND", 81 | np.ndarray[_NDT, np.dtype[_SCT]], 82 | type_params=(_SCT, _NDT), 83 | ) 84 | """ 85 | Like `Array`, but with flipped type-parameters, i.e.: 86 | 87 | type ArrayND[ 88 | SCT: np.generic = np.generic, 89 | NDT: (int, ...) = (int, ...), 90 | ] = np.ndarray[NDT, np.dtype[SCT]] 91 | 92 | Because the optional shape-type parameter comes *after* the scalar-type, `ArrayND` 93 | can be seen as a flexible generalization of `npt.NDArray`. 94 | """ 95 | 96 | MArray = TypeAliasType( 97 | "MArray", 98 | np.ma.MaskedArray[_NDT, np.dtype[_SCT]], 99 | type_params=(_SCT, _NDT), 100 | ) 101 | """ 102 | Just like `ArrayND`, but for `np.ma.MaskedArray` instead of `np.ndarray`. 103 | 104 | type MArray[ 105 | SCT: np.generic = np.generic, 106 | NDT: (int, ...) = (int, ...), 107 | ] = np.ma.MaskedArray[NDT, np.dtype[SCT]] 108 | """ 109 | 110 | if _x.NP21: 111 | # numpy >= 2.1: shape is covariant 112 | 113 | @runtime_checkable 114 | @set_module("optype.numpy") 115 | class CanArray(Protocol[_NDT_co, _DTT_co]): 116 | def __array__(self, /) -> np.ndarray[_NDT_co, _DTT_co]: ... 117 | 118 | @runtime_checkable 119 | @set_module("optype.numpy") 120 | class CanArrayND(Protocol[_SCT_co, _NDT_co]): 121 | """ 122 | Similar to `onp.CanArray`, but must be sized (i.e. excludes scalars), and is 123 | parameterized by only the scalar type (instead of the shape and dtype). 124 | """ 125 | 126 | def __len__(self, /) -> int: ... 127 | def __array__(self, /) -> np.ndarray[_NDT_co, np.dtype[_SCT_co]]: ... 128 | 129 | else: 130 | # numpy < 2.1: shape is invariant 131 | 132 | @runtime_checkable 133 | @set_module("optype.numpy") 134 | class CanArray(Protocol[_NDT, _DTT_co]): 135 | def __array__(self, /) -> np.ndarray[_NDT, _DTT_co]: ... 136 | 137 | @runtime_checkable 138 | @set_module("optype.numpy") 139 | class CanArrayND(Protocol[_SCT_co, _NDT]): 140 | def __len__(self, /) -> int: ... 141 | def __array__(self, /) -> np.ndarray[_NDT, np.dtype[_SCT_co]]: ... 142 | 143 | 144 | Array0D = TypeAliasType( 145 | "Array0D", 146 | np.ndarray[tuple[()], np.dtype[_SCT]], 147 | type_params=(_SCT,), 148 | ) 149 | Array1D = TypeAliasType( 150 | "Array1D", 151 | np.ndarray[tuple[int], np.dtype[_SCT]], 152 | type_params=(_SCT,), 153 | ) 154 | Array2D = TypeAliasType( 155 | "Array2D", 156 | np.ndarray[tuple[int, int], np.dtype[_SCT]], 157 | type_params=(_SCT,), 158 | ) 159 | Array3D = TypeAliasType( 160 | "Array3D", 161 | np.ndarray[tuple[int, int, int], np.dtype[_SCT]], 162 | type_params=(_SCT,), 163 | ) 164 | 165 | MArray0D = TypeAliasType( 166 | "MArray0D", 167 | np.ma.MaskedArray[tuple[()], np.dtype[_SCT]], 168 | type_params=(_SCT,), 169 | ) 170 | MArray1D = TypeAliasType( 171 | "MArray1D", 172 | np.ma.MaskedArray[tuple[int], np.dtype[_SCT]], 173 | type_params=(_SCT,), 174 | ) 175 | MArray2D = TypeAliasType( 176 | "MArray2D", 177 | np.ma.MaskedArray[tuple[int, int], np.dtype[_SCT]], 178 | type_params=(_SCT,), 179 | ) 180 | MArray3D = TypeAliasType( 181 | "MArray3D", 182 | np.ma.MaskedArray[tuple[int, int, int], np.dtype[_SCT]], 183 | type_params=(_SCT,), 184 | ) 185 | 186 | 187 | ########################### 188 | 189 | 190 | @runtime_checkable 191 | @set_module("optype.numpy") 192 | class CanArray0D(Protocol[_SCT_co]): 193 | """ 194 | The 0-d variant of `optype.numpy.CanArrayND`. 195 | 196 | This accepts e.g. `np.asarray(3.14)`, but rejects `np.float64(3.14)`. 197 | """ 198 | 199 | def __len__(self, /) -> int: ... # always 0 200 | def __array__(self, /) -> np.ndarray[tuple[()], np.dtype[_SCT_co]]: ... 201 | 202 | 203 | @runtime_checkable 204 | @set_module("optype.numpy") 205 | class CanArray1D(Protocol[_SCT_co]): 206 | """The 1-d variant of `optype.numpy.CanArrayND`.""" 207 | 208 | def __len__(self, /) -> int: ... 209 | def __array__(self, /) -> np.ndarray[tuple[int], np.dtype[_SCT_co]]: ... 210 | 211 | 212 | @runtime_checkable 213 | @set_module("optype.numpy") 214 | class CanArray2D(Protocol[_SCT_co]): 215 | """The 2-d variant of `optype.numpy.CanArrayND`.""" 216 | 217 | def __len__(self, /) -> int: ... 218 | def __array__(self, /) -> np.ndarray[tuple[int, int], np.dtype[_SCT_co]]: ... 219 | 220 | 221 | @runtime_checkable 222 | @set_module("optype.numpy") 223 | class CanArray3D(Protocol[_SCT_co]): 224 | """The 2-d variant of `optype.numpy.CanArrayND`.""" 225 | 226 | def __len__(self, /) -> int: ... 227 | def __array__(self, /) -> np.ndarray[tuple[int, int, int], np.dtype[_SCT_co]]: ... 228 | 229 | 230 | # this is almost always a `ndarray`, but setting a `bound` might break in some 231 | # edge cases 232 | _T_contra = TypeVar("_T_contra", contravariant=True, default=Any) 233 | 234 | 235 | @runtime_checkable 236 | @set_module("optype.numpy") 237 | class CanArrayFinalize(Protocol[_T_contra]): 238 | def __array_finalize__(self, obj: _T_contra, /) -> None: ... 239 | 240 | 241 | @runtime_checkable 242 | @set_module("optype.numpy") 243 | class CanArrayWrap(Protocol): 244 | if _x.NP20: 245 | 246 | def __array_wrap__( 247 | self, 248 | array: np.ndarray[_NDT, _DTT], 249 | context: tuple[np.ufunc, tuple[Any, ...], int] | None = ..., 250 | return_scalar: bool = ..., 251 | /, 252 | ) -> np.ndarray[_NDT, _DTT] | Self: ... 253 | 254 | else: 255 | 256 | def __array_wrap__( 257 | self, 258 | array: np.ndarray[_NDT, _DTT], 259 | context: tuple[np.ufunc, tuple[Any, ...], int] | None = ..., 260 | /, 261 | ) -> np.ndarray[_NDT, _DTT] | Self: ... 262 | 263 | 264 | _ArrayInterfaceT_co = TypeVar( 265 | "_ArrayInterfaceT_co", 266 | covariant=True, 267 | bound=Mapping[str, object], 268 | default=dict[str, Any], 269 | ) 270 | 271 | 272 | @runtime_checkable 273 | @set_module("optype.numpy") 274 | class HasArrayInterface(Protocol[_ArrayInterfaceT_co]): 275 | @property 276 | def __array_interface__(self, /) -> _ArrayInterfaceT_co: ... 277 | 278 | 279 | @runtime_checkable 280 | @set_module("optype.numpy") 281 | class HasArrayPriority(Protocol): 282 | @property 283 | def __array_priority__(self, /) -> float: ... 284 | -------------------------------------------------------------------------------- /optype/numpy/_compat.py: -------------------------------------------------------------------------------- 1 | from typing import Final, TypeAlias 2 | 3 | import numpy as np 4 | 5 | __all__ = ["Bool", "Long", "ULong"] 6 | 7 | # >=1.25 8 | NP125: Final = True 9 | # >=2.0 10 | NP20: Final = np.__version__.startswith("2.") 11 | # >=2.1 12 | NP21: Final = NP20 and not np.__version__.startswith("2.0.") 13 | # >=2.2 14 | NP22: Final = NP21 and not np.__version__.startswith("2.1.") 15 | # >=2.3 16 | NP23: Final = NP22 and not np.__version__.startswith("2.2.") 17 | 18 | 19 | if NP20: 20 | Bool: TypeAlias = np.bool 21 | ULong: TypeAlias = np.ulong 22 | Long: TypeAlias = np.long 23 | else: 24 | Bool: TypeAlias = np.bool_ 25 | ULong: TypeAlias = np.uint 26 | Long: TypeAlias = np.int_ 27 | -------------------------------------------------------------------------------- /optype/numpy/_ctypeslib.py: -------------------------------------------------------------------------------- 1 | import ctypes as ct 2 | 3 | __all__ = "CScalar", "CType" 4 | 5 | 6 | _, CType, CScalar, *_ = ct.c_int.mro() 7 | -------------------------------------------------------------------------------- /optype/numpy/_ctypeslib.pyi: -------------------------------------------------------------------------------- 1 | import ctypes as ct 2 | from ctypes import _CData as CType # noqa: PLC2701 3 | from typing import Any, TypeAlias 4 | from typing_extensions import TypeVar 5 | 6 | __all__ = "CScalar", "CType" 7 | 8 | _T = TypeVar("_T", default=Any) 9 | CScalar: TypeAlias = ct._SimpleCData[_T] # noqa: SLF001 10 | -------------------------------------------------------------------------------- /optype/numpy/_dtype.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import sys 4 | from typing import Any, Protocol, TypeAlias 5 | 6 | if sys.version_info >= (3, 13): 7 | from typing import TypeAliasType, TypeVar, runtime_checkable 8 | else: 9 | from typing_extensions import TypeAliasType, TypeVar, runtime_checkable 10 | 11 | import numpy as np 12 | 13 | from optype._utils import set_module 14 | 15 | __all__ = ["DType", "HasDType", "ToDType"] 16 | 17 | 18 | def __dir__() -> list[str]: 19 | return __all__ 20 | 21 | 22 | ### 23 | 24 | ST = TypeVar("ST", bound=np.generic, default=Any) 25 | DT_co = TypeVar("DT_co", bound=np.dtype[Any], default=np.dtype[Any], covariant=True) 26 | 27 | 28 | @runtime_checkable 29 | @set_module("optype.numpy") 30 | class HasDType(Protocol[DT_co]): 31 | """HasDType[DT: np.dtype[np.generic] = np.dtype[np.generic]] 32 | 33 | Runtime checkable protocol for objects (or types) that have a `dtype` 34 | attribute (or property), such as `numpy.ndarray` instances, or 35 | `numpy.generic` "scalar" instances. 36 | 37 | Anything that implements this interface can be used with the `numpy.dtype` 38 | constructor, i.e. its constructor is compatible with a signature that 39 | looks something like `(HasDType[DT: numpy.DType], ...) -> DT`. 40 | """ 41 | 42 | @property 43 | def dtype(self, /) -> DT_co: ... 44 | 45 | 46 | DType: TypeAlias = np.dtype[ST] 47 | """Alias for `numpy.dtype[T: numpy.generic = np.generic]`.""" 48 | 49 | 50 | ToDType = TypeAliasType( 51 | "ToDType", 52 | type[ST] | np.dtype[ST] | HasDType[np.dtype[ST]], 53 | type_params=(ST,), 54 | ) 55 | """ 56 | Accepts values that that will result in a `np.dtype[ST]` instance when passed to the 57 | `np.dtype()` constructor. 58 | 59 | Note that this does not accept builtin types like `float`, strings like `'f8'`, or any 60 | other valid value that isn't directly expressible in terms a numpy scalar type. 61 | 62 | But even though `ToDType` accepts anything with a `.dtype` attribute, passing a 63 | `np.ndarray` instance to `np.dtype()` will raise a `TypeError`. But currently it's 64 | impossible to exclude just `np.ndarray` from this `ToDType` type alias. 65 | """ 66 | -------------------------------------------------------------------------------- /optype/numpy/_dtype_attr.py: -------------------------------------------------------------------------------- 1 | # ruff: noqa: PYI042 2 | from typing import Literal as L, TypeAlias as Alias # noqa: N817 3 | 4 | import optype.numpy._compat as _x 5 | 6 | ### 7 | 8 | # boolean 9 | 10 | b1_name: Alias = L["bool", "bool_"] # 'bool0' was removed in NumPy 2.0 11 | b1_char: Alias = L["b1", "|b1", "?"] 12 | b1_code: Alias = L[b1_name, b1_char] 13 | 14 | # integer 15 | 16 | i1_name: Alias = L["int8", "byte"] 17 | i1_char: Alias = L["i1", "|i1", "b"] 18 | i1_code: Alias = L[i1_name, i1_char] 19 | 20 | u1_name: Alias = L["uint8", "ubyte"] 21 | u1_char: Alias = L["u1", "|u1", "B"] 22 | u1_code: Alias = L[u1_name, u1_char] 23 | 24 | i2_name: Alias = L["int16", "short"] 25 | i2_char: Alias = L["i2", "i2", "h"] 26 | i2_code: Alias = L[i2_name, i2_char] 27 | 28 | u2_name: Alias = L["uint16", "ushort"] 29 | u2_char: Alias = L["u2", "u2", "H"] 30 | u2_code: Alias = L[u2_name, u2_char] 31 | 32 | i4_name: Alias = L["int32", "intc"] 33 | i4_char: Alias = L["i4", "i4", "i"] 34 | i4_code: Alias = L[i4_name, i4_char] 35 | 36 | u4_name: Alias = L["uint32", "uintc"] 37 | u4_char: Alias = L["u4", "u4", "I"] 38 | u4_code: Alias = L[u4_name, u4_char] 39 | 40 | i8_name: Alias = L["int64", "longlong"] 41 | i8_char: Alias = L["i8", "i8", "q"] 42 | i8_code: Alias = L[i8_name, i8_char] 43 | 44 | u8_name: Alias = L["uint64", "ulonglong"] 45 | u8_char: Alias = L["u8", "u8", "Q"] 46 | u8_code: Alias = L[u8_name, u8_char] 47 | 48 | l_char: Alias = L["l", "l"] 49 | L_char: Alias = L["L", "L"] 50 | 51 | p_char: Alias = L["p", "p"] 52 | P_char: Alias = L["P", "P"] 53 | 54 | # numpy>=2 only 55 | n_char: Alias = L["n", "n"] 56 | N_char: Alias = L["N", "N"] 57 | 58 | 59 | if _x.NP20: 60 | i0_name: Alias = L["int_", "int", "intp"] 61 | i0_char: Alias = n_char 62 | i0_code: Alias = L[i0_name, i0_char] 63 | 64 | u0_char: Alias = N_char 65 | u0_name: Alias = L["uint", "uintp"] 66 | u0_code: Alias = L[u0_name, u0_char] 67 | 68 | i__name: Alias = i0_name 69 | i__char: Alias = i0_char 70 | i__code: Alias = i0_code 71 | 72 | u__name: Alias = u0_name 73 | u__char: Alias = u0_char 74 | u__code: Alias = u0_code 75 | 76 | l_name: Alias = L["long"] 77 | l_code: Alias = L[l_name, l_char] 78 | 79 | L_name: Alias = L["ulong"] 80 | L_code: Alias = L[L_name, L_char] 81 | else: 82 | i0_name: Alias = L["intp"] 83 | i0_char: Alias = p_char 84 | i0_code: Alias = L[i0_name, i0_char] 85 | 86 | u0_name: Alias = L["uintp"] 87 | u0_char: Alias = P_char 88 | u0_code: Alias = L[u0_name, u0_char] 89 | 90 | i__name: Alias = L["int_", "int", "long"] 91 | i__char: Alias = L[l_char] 92 | i__code: Alias = L[i__name, i__char] 93 | 94 | u__name: Alias = L["uint", "ulong"] 95 | u__char: Alias = L[L_char] 96 | u__code: Alias = L[u__name, u__char] 97 | 98 | l_name: Alias = i__name 99 | l_code: Alias = i__code 100 | 101 | L_name: Alias = u__name 102 | L_code: Alias = u__code 103 | 104 | 105 | # float 106 | 107 | f2_name: Alias = L["float16", "half"] 108 | f2_char: Alias = L["f2", "f2", "e"] 109 | f2_code: Alias = L[f2_name, f2_char] 110 | 111 | f4_name: Alias = L["float32", "single"] 112 | f4_char: Alias = L["f4", "f4", "f"] 113 | f4_code: Alias = L[f4_name, f4_char] 114 | 115 | f8_name: Alias = L["float64", "double", "float"] 116 | f8_char: Alias = L["f8", "f8", "d"] 117 | f8_code: Alias = L[f8_name, f8_char] 118 | 119 | f12_name: Alias = L["float96"] 120 | f12_char: Alias = L["f12", "f12"] 121 | f12_code: Alias = L[f12_name, f12_char] 122 | 123 | f16_name: Alias = L["float128"] 124 | f16_char: Alias = L["f16", "f16"] 125 | f16_code: Alias = L[f16_name, f16_char] 126 | 127 | g_name: Alias = L[f12_name, f16_name, "longdouble"] 128 | g_char: Alias = L[f12_char, f16_char, "g"] 129 | g_code: Alias = L[g_name, g_char] 130 | 131 | # complex 132 | 133 | c8_name: Alias = L["complex64", "csingle"] 134 | c8_char: Alias = L["c8", "c8", "F"] 135 | c8_code: Alias = L[c8_name, c8_char] 136 | 137 | c16_name: Alias = L["complex", "complex128", "cdouble"] 138 | c16_char: Alias = L["c16", "c16", "D"] 139 | c16_code: Alias = L[c16_name, c16_char] 140 | 141 | c24_name: Alias = L["complex192"] 142 | c24_char: Alias = L["c24", "c24"] 143 | c24_code: Alias = L[c24_name, c24_char] 144 | 145 | c32_name: Alias = L["complex256"] 146 | c32_char: Alias = L["c32", "c32"] 147 | c32_code: Alias = L[c32_name, c32_char] 148 | 149 | G_name: Alias = L[c24_name, c32_name, "clongdouble"] 150 | G_char: Alias = L[c24_code, c32_code, "G"] 151 | G_code: Alias = L[G_name, G_char] 152 | 153 | # object 154 | 155 | O_name: Alias = L["object_", "object"] 156 | O_char: Alias = L["O", "|O"] 157 | O_code: Alias = L[O_name, O_char] 158 | 159 | # bytes_ 160 | 161 | # NOTE: this only includes 0-length bytes 162 | S0_name: Alias = L["bytes_", "bytes"] 163 | S0_char: Alias = L["S0", "|S0", "S0", "S"] 164 | S0_code: Alias = L[S0_name, S0_char] 165 | 166 | S1_name: Alias = L["bytes8"] 167 | S1_char: Alias = L["S1", "|S1", "S1", "c"] 168 | S1_code: Alias = L[S1_name, S1_char] 169 | 170 | # str_ 171 | 172 | # NOTE: this only includes 0-length strings 173 | U0_name: Alias = L["str_", "str", "unicode"] 174 | U0_char: Alias = L["U0", "|U0", "U0", "U"] 175 | U0_code: Alias = L[U0_name, U0_char] 176 | 177 | # void 178 | 179 | # NOTE: this only includes "len-0 bytes void" 180 | V0_name: Alias = L["void"] # 'void0' was removed in NumPy 2.0 181 | V0_char: Alias = L["V0", "|V0", "V"] 182 | V0_code: Alias = L[V0_name, V0_char] 183 | 184 | # datetime64 185 | 186 | M_name: Alias = L[ 187 | "datetime64", 188 | "datetime64[as]", 189 | "datetime64[fs]", 190 | "datetime64[ps]", 191 | "datetime64[ns]", 192 | "datetime64[us]", 193 | "datetime64[ms]", 194 | "datetime64[s]", 195 | "datetime64[m]", 196 | "datetime64[h]", 197 | "datetime64[D]", 198 | "datetime64[W]", 199 | "datetime64[M]", 200 | "datetime64[Y]", 201 | ] 202 | M_char: Alias = L[ 203 | "M8", "M8", "M", 204 | "M8[as]", "M8[as]", 205 | "M8[fs]", "M8[fs]", 206 | "M8[ps]", "M8[ps]", 207 | "M8[ns]", "M8[ns]", 208 | "M8[us]", "M8[us]", 209 | "M8[s]", "M8[s]", 210 | "M8[m]", "M8[m]", 211 | "M8[h]", "M8[h]", 212 | "M8[D]", "M8[D]", 213 | "M8[W]", "M8[W]", 214 | "M8[M]", "M8[M]", 215 | "M8[Y]", "M8[Y]", 216 | ] # fmt: skip 217 | M_code: Alias = L[M_name, M_char] 218 | 219 | # timedelta64 220 | 221 | m_name: Alias = L[ 222 | "timedelta64", 223 | "timedelta64[as]", 224 | "timedelta64[fs]", 225 | "timedelta64[ps]", 226 | "timedelta64[ns]", 227 | "timedelta64[us]", 228 | "timedelta64[ms]", 229 | "timedelta64[s]", 230 | "timedelta64[m]", 231 | "timedelta64[h]", 232 | "timedelta64[D]", 233 | "timedelta64[W]", 234 | "timedelta64[M]", 235 | "timedelta64[Y]", 236 | ] 237 | m_char: Alias = L[ 238 | "m8", "m8", "m", 239 | "m8[as]", "m8[as]", 240 | "m8[fs]", "m8[fs]", 241 | "m8[ps]", "m8[ps]", 242 | "m8[ns]", "m8[ns]", 243 | "m8[us]", "m8[us]", 244 | "m8[s]", "m8[s]", 245 | "m8[m]", "m8[m]", 246 | "m8[h]", "m8[h]", 247 | "m8[D]", "m8[D]", 248 | "m8[W]", "m8[W]", 249 | "m8[M]", "m8[M]", 250 | "m8[Y]", "m8[Y]", 251 | ] # fmt: skip 252 | m_code: Alias = L[m_name, m_char] 253 | 254 | # stringv (or whatever we're gonna call the `StringDType().type` scalar type) 255 | 256 | T_name: Alias = L["StringDType128"] 257 | T_char: Alias = L["T"] 258 | T_code: Alias = T_char # not yet 259 | 260 | # abstract 261 | 262 | ix_name: Alias = L[ 263 | "int8", "int16", "int32", "int64", 264 | "byte", "short", "intc", "long", "longlong", 265 | "int_", "intp", 266 | ] # fmt: skip 267 | ix_char: Alias = L[i1_char, i2_char, i4_char, i8_char, l_char, p_char, n_char] 268 | ix_code: Alias = L[ix_name, ix_char] 269 | 270 | 271 | ux_name: Alias = L[ 272 | "uint8", "uint16", "uint32", "uint64", 273 | "ubyte", "ushort", "uintc", "ulong", "ulonglong", 274 | "uint", "uintp", 275 | ] # fmt: skip 276 | ux_char: Alias = L[u1_char, u2_char, u4_char, u8_char, L_char, P_char, N_char] 277 | ux_code: Alias = L[ux_name, ux_char] 278 | 279 | fx_code: Alias = L[f2_code, f4_code, f8_code, g_code] 280 | cx_code: Alias = L[c8_code, c16_code, G_code] 281 | 282 | iu_code: Alias = L[ux_code, ix_code] 283 | fc_code: Alias = L[fx_code, cx_code] 284 | iufc_code: Alias = L[iu_code, fc_code] 285 | 286 | SU_code: Alias = L[S0_code, S1_code, U0_code] 287 | SUV_code: Alias = L[SU_code, V0_code] 288 | -------------------------------------------------------------------------------- /optype/numpy/_is.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from typing import Any 3 | 4 | if sys.version_info >= (3, 13): 5 | from typing import TypeIs, TypeVar 6 | else: 7 | from typing_extensions import TypeIs, TypeVar 8 | 9 | import numpy as np 10 | 11 | from ._array import Array0D, Array1D, Array2D, Array3D, ArrayND 12 | from ._dtype import ToDType 13 | from ._shape import AnyShape, Shape 14 | 15 | __all__ = [ 16 | "is_array_0d", 17 | "is_array_1d", 18 | "is_array_2d", 19 | "is_array_3d", 20 | "is_array_nd", 21 | "is_dtype", 22 | "is_sctype", 23 | ] 24 | 25 | 26 | def __dir__() -> list[str]: 27 | return __all__ 28 | 29 | 30 | ShapeT = TypeVar("ShapeT", bound=Shape, default=AnyShape) 31 | DTypeT = TypeVar("DTypeT", bound=np.dtype[Any]) 32 | ScalarT = TypeVar("ScalarT", bound=np.generic, default=Any) 33 | 34 | 35 | def is_dtype( 36 | x: object, 37 | /, 38 | dtype: ToDType[ScalarT] | None = None, 39 | ) -> TypeIs[np.dtype[ScalarT]]: 40 | return isinstance(x, np.dtype) and (dtype is None or np.issubdtype(x, dtype)) 41 | 42 | 43 | def is_sctype( 44 | x: object, 45 | /, 46 | dtype: ToDType[ScalarT] | None = None, 47 | ) -> TypeIs[type[ScalarT]]: 48 | return ( 49 | isinstance(x, type) 50 | and issubclass(x, np.generic) 51 | and (dtype is None or np.issubdtype(x, dtype)) 52 | ) 53 | 54 | 55 | def is_array_nd( 56 | a: object, 57 | /, 58 | dtype: ToDType[ScalarT] | None = None, 59 | ) -> TypeIs[ArrayND[ScalarT, ShapeT]]: 60 | """Checks if `a` is a `ndarray` of the given dtype (defaults to `generic`).""" 61 | return isinstance(a, np.ndarray) and ( 62 | dtype is None or np.issubdtype(a.dtype, dtype) 63 | ) 64 | 65 | 66 | def is_array_0d( 67 | a: object, 68 | /, 69 | dtype: ToDType[ScalarT] | None = None, 70 | ) -> TypeIs[Array0D[ScalarT]]: 71 | """Checks if `a` is a 0-d `ndarray` of the given dtype (defaults to `generic`).""" 72 | return is_array_nd(a, dtype) and a.ndim == 0 73 | 74 | 75 | def is_array_1d( 76 | a: object, 77 | /, 78 | dtype: ToDType[ScalarT] | None = None, 79 | ) -> TypeIs[Array1D[ScalarT]]: 80 | """Checks if `a` is a 1-d `ndarray` of the given dtype (defaults to `generic`).""" 81 | return is_array_nd(a, dtype) and a.ndim == 1 82 | 83 | 84 | def is_array_2d( 85 | a: object, 86 | /, 87 | dtype: ToDType[ScalarT] | None = None, 88 | ) -> TypeIs[Array2D[ScalarT]]: 89 | """Checks if `a` is a 2-d `ndarray` of the given dtype (defaults to `generic`).""" 90 | return is_array_nd(a, dtype) and a.ndim == 2 91 | 92 | 93 | def is_array_3d( 94 | a: object, 95 | /, 96 | dtype: ToDType[ScalarT] | None = None, 97 | ) -> TypeIs[Array3D[ScalarT]]: 98 | """Checks if `a` is a 3-d `ndarray` of the given dtype (defaults to `generic`).""" 99 | return is_array_nd(a, dtype) and a.ndim == 3 100 | -------------------------------------------------------------------------------- /optype/numpy/_literals.py: -------------------------------------------------------------------------------- 1 | from typing import Literal, TypeAlias 2 | 3 | __all__ = [ 4 | "ByteOrder", 5 | "ByteOrderChar", 6 | "ByteOrderName", 7 | "Casting", 8 | "CastingSafe", 9 | "CastingUnsafe", 10 | "ConvolveMode", 11 | "Device", 12 | "OrderACF", 13 | "OrderCF", 14 | "OrderKACF", 15 | "PartitionKind", 16 | "SortKind", 17 | "SortSide", 18 | ] 19 | 20 | Device: TypeAlias = Literal["cpu"] 21 | 22 | ByteOrderChar: TypeAlias = Literal["<", ">", "=", "|"] 23 | ByteOrderName: TypeAlias = Literal["little", "big", "native", "ignore", "swap"] 24 | _ByteOrderShort: TypeAlias = Literal["L", "B", "N", "I", "S"] 25 | ByteOrder: TypeAlias = Literal[ByteOrderChar, _ByteOrderShort, ByteOrderName] 26 | 27 | CastingSafe: TypeAlias = Literal["no", "equiv", "safe", "same_kind"] 28 | CastingUnsafe: TypeAlias = Literal["unsafe"] 29 | Casting: TypeAlias = Literal[CastingSafe, CastingUnsafe] 30 | 31 | OrderCF: TypeAlias = Literal["C", "F"] 32 | OrderACF: TypeAlias = Literal["A", OrderCF] 33 | OrderKACF: TypeAlias = Literal["K", OrderACF] 34 | 35 | IndexMode: TypeAlias = Literal["raise", "wrap", "clip"] 36 | 37 | PartitionKind: TypeAlias = Literal["introselect"] 38 | 39 | SortKind: TypeAlias = Literal[ 40 | "Q", "quick", "quicksort", 41 | "M", "merge", "mergesort", 42 | "H", "heap", "heapsort", 43 | "S", "stable", "stablesort", 44 | ] # fmt: skip 45 | SortSide: TypeAlias = Literal["left", "right"] 46 | 47 | ConvolveMode: TypeAlias = Literal["full", "same", "valid"] 48 | -------------------------------------------------------------------------------- /optype/numpy/_scalar.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from typing import Any, Literal, Protocol, Self, TypeAlias, overload 3 | 4 | if sys.version_info >= (3, 13): 5 | from types import CapsuleType 6 | from typing import TypeAliasType, TypeVar, override, runtime_checkable 7 | else: 8 | from typing_extensions import ( 9 | CapsuleType, 10 | TypeAliasType, 11 | TypeVar, 12 | override, 13 | runtime_checkable, 14 | ) 15 | 16 | import numpy as np 17 | from numpy._typing import _8Bit, _16Bit, _32Bit, _64Bit # noqa: PLC2701 18 | 19 | from ._compat import NP20 20 | from optype._utils import set_module 21 | 22 | if NP20: 23 | from numpy._core.multiarray import flagsobj 24 | else: 25 | from numpy.core.multiarray import flagsobj 26 | 27 | 28 | __all__ = ["Scalar"] 29 | 30 | 31 | ### 32 | 33 | _PT_co = TypeVar("_PT_co", covariant=True) 34 | _NB_co = TypeVar("_NB_co", bound=int, default=int, covariant=True) 35 | _DT = TypeVar("_DT", bound=np.dtype[Any], default=np.dtype[Any]) 36 | 37 | _L0: TypeAlias = Literal[0] 38 | _L1: TypeAlias = Literal[1] 39 | _Array0D: TypeAlias = np.ndarray[tuple[()], _DT] 40 | 41 | 42 | ### 43 | 44 | 45 | @runtime_checkable 46 | @set_module("optype.numpy") 47 | class Scalar(Protocol[_PT_co, _NB_co]): 48 | """ 49 | A lightweight `numpy.generic` interface that's actually generic, and 50 | doesn't require all that nasty `numpy.typing.NBitBase` stuff. 51 | """ 52 | 53 | # unfortunately `| int` is required for compat with `numpy.__init__.pyi` 54 | @property 55 | def itemsize(self, /) -> _NB_co | int: ... 56 | 57 | @property 58 | def base(self, /) -> None: ... 59 | @property 60 | def data(self, /) -> memoryview: ... 61 | @property 62 | def dtype(self, /) -> np.dtype[Self]: ... # type: ignore[type-var] # pyright: ignore[reportInvalidTypeArguments] 63 | @property 64 | def flags(self, /) -> flagsobj: ... 65 | @property 66 | def nbytes(self, /) -> int: ... 67 | @property 68 | def ndim(self, /) -> _L0: ... 69 | @property 70 | def shape(self, /) -> tuple[()]: ... 71 | @property 72 | def size(self, /) -> _L1: ... 73 | @property 74 | def strides(self, /) -> tuple[()]: ... 75 | 76 | def item(self, k: _L0 | tuple[()] | tuple[_L0] = ..., /) -> _PT_co: ... 77 | 78 | @property 79 | def __array_priority__(self, /) -> float: ... # -1000000.0 80 | @property 81 | def __array_interface__(self, /) -> dict[str, Any]: ... 82 | @property 83 | def __array_struct__(self, /) -> CapsuleType: ... 84 | 85 | if NP20: 86 | 87 | @override 88 | def __hash__(self, /) -> int: ... 89 | 90 | @override 91 | def __eq__(self, other: object, /) -> np.bool_: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] 92 | @override 93 | def __ne__(self, other: object, /) -> np.bool_: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] 94 | 95 | def __bool__(self, /) -> bool: ... 96 | 97 | # Unlike `numpy/__init__.pyi` suggests, there exists no `__bytes__` method 98 | # in `np.generic`. Instead, it implements the (C) buffer protocol. 99 | 100 | if sys.version_info >= (3, 12): 101 | 102 | def __buffer__(self, flags: int, /) -> memoryview: ... 103 | 104 | def __copy__(self, /) -> Self: ... 105 | def __deepcopy__(self, memo: dict[int, object] | None, /) -> Self: ... 106 | 107 | @overload 108 | def __array__(self, /) -> _Array0D: ... 109 | @overload 110 | def __array__(self, dtype: _DT, /) -> _Array0D[_DT]: ... 111 | 112 | 113 | generic = np.generic 114 | flexible = np.flexible 115 | character = np.character 116 | 117 | number = TypeAliasType("number", np.number[Any]) 118 | integer = TypeAliasType("integer", np.integer[Any]) 119 | uinteger = TypeAliasType("uinteger", np.unsignedinteger[Any]) 120 | sinteger = TypeAliasType("sinteger", np.signedinteger[Any]) 121 | inexact = TypeAliasType("inexact", np.inexact[Any]) 122 | floating = TypeAliasType("floating", np.floating[Any]) 123 | cfloating = TypeAliasType("cfloating", np.complexfloating[Any, Any]) 124 | 125 | integer8 = TypeAliasType("integer8", np.integer[_8Bit]) 126 | integer16 = TypeAliasType("integer16", np.integer[_16Bit]) 127 | integer32 = TypeAliasType("integer32", np.integer[_32Bit]) 128 | integer64 = TypeAliasType("integer64", np.integer[_64Bit]) 129 | 130 | floating16 = TypeAliasType("floating16", np.floating[_16Bit]) 131 | floating32 = TypeAliasType("floating32", np.floating[_32Bit]) 132 | floating64 = TypeAliasType("floating64", np.floating[_64Bit]) 133 | 134 | cfloating32 = TypeAliasType("cfloating32", np.complexfloating[_32Bit, _32Bit]) 135 | cfloating64 = TypeAliasType("cfloating64", np.complexfloating[_64Bit, _64Bit]) 136 | 137 | inexact32 = TypeAliasType("inexact32", np.inexact[_32Bit]) 138 | inexact64 = TypeAliasType("inexact64", np.inexact[_64Bit]) 139 | 140 | number8 = TypeAliasType("number8", np.number[_8Bit]) 141 | number16 = TypeAliasType("number16", np.number[_16Bit]) 142 | number32 = TypeAliasType("number32", np.number[_32Bit]) 143 | number64 = TypeAliasType("number64", np.number[_64Bit]) 144 | -------------------------------------------------------------------------------- /optype/numpy/_sequence_nd.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Iterator 2 | from typing import Any, Protocol, TypeVar, final 3 | 4 | __all__ = ["SequenceND"] 5 | 6 | 7 | T_co = TypeVar("T_co", covariant=True) 8 | 9 | 10 | @final 11 | class SequenceND(Protocol[T_co]): # type: ignore[misc] # https://github.com/python/mypy/issues/17288 12 | """Based on `numpy._typing._NestedSequence`.""" 13 | 14 | def __len__(self, /) -> int: ... 15 | def __contains__(self, value: object, /) -> bool: ... 16 | def __getitem__(self, index: int, /) -> "T_co | SequenceND[T_co]": ... 17 | def __iter__(self, /) -> Iterator["T_co | SequenceND[T_co]"]: ... 18 | def __reversed__(self, /) -> Iterator["T_co | SequenceND[T_co]"]: ... 19 | def count(self, value: Any, /) -> int: ... 20 | def index(self, value: Any, /) -> int: ... 21 | -------------------------------------------------------------------------------- /optype/numpy/_shape.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from typing import Any, Literal, TypeAlias 3 | 4 | if sys.version_info >= (3, 13): 5 | from typing import TypeAliasType, TypeVar 6 | else: 7 | from typing_extensions import TypeAliasType, TypeVar 8 | 9 | 10 | __all__ = [ 11 | "AtLeast0D", 12 | "AtLeast1D", 13 | "AtLeast2D", 14 | "AtLeast3D", 15 | "AtMost0D", 16 | "AtMost1D", 17 | "AtMost2D", 18 | "AtMost3D", 19 | "NDim", 20 | "NDim0", 21 | ] 22 | 23 | 24 | def __dir__() -> list[str]: 25 | return __all__ 26 | 27 | 28 | ### 29 | # undocumented aliases for internal use 30 | 31 | Shape: TypeAlias = tuple[int, ...] 32 | AnyShape: TypeAlias = tuple[Any, ...] 33 | 34 | 35 | ### 36 | 37 | AxT = TypeVar("AxT", int, Any, default=int) 38 | 39 | ### 40 | # Shape types with at least N dimensions. They're fully static by default, but can be 41 | # turned "gradual" by passing `Any` as type argument. 42 | AtLeast0D = TypeAliasType("AtLeast0D", tuple[AxT, ...], type_params=(AxT,)) 43 | AtLeast1D = TypeAliasType("AtLeast1D", tuple[int, *tuple[AxT, ...]], type_params=(AxT,)) 44 | AtLeast2D = TypeAliasType( 45 | "AtLeast2D", tuple[int, int, *tuple[AxT, ...]], type_params=(AxT,) 46 | ) 47 | AtLeast3D = TypeAliasType( 48 | "AtLeast3D", tuple[int, int, int, *tuple[AxT, ...]], type_params=(AxT,) 49 | ) 50 | 51 | ### 52 | # Shape types with at most N dimensions. Unlike the above, these are not gradual due to 53 | # limitations in the Python type system. 54 | AtMost0D = TypeAliasType("AtMost0D", tuple[()]) 55 | AtMost1D = TypeAliasType("AtMost1D", tuple[int] | AtMost0D) 56 | AtMost2D = TypeAliasType("AtMost2D", tuple[int, int] | AtMost1D) 57 | AtMost3D = TypeAliasType("AtMost3D", tuple[int, int, int] | AtMost2D) 58 | 59 | ### 60 | # Integer literal types for the number of dimensions. Note that before `numpy>=2` the 61 | # maximum number of dimensions was 32, but this was increased to 64 in `numpy>=2`. 62 | 63 | # NOTE: on `numpy<2` this was at most 32 64 | _1_64: TypeAlias = Literal[ # noqa: PYI042 65 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 66 | 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 67 | 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 68 | 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 69 | ] # fmt: skip 70 | _0_64: TypeAlias = Literal[0, _1_64] # noqa: PYI042 71 | 72 | NDim0 = TypeAliasType("NDim0", _1_64) 73 | """Integer literal between 1 and 64, inclusive.""" 74 | 75 | NDim = TypeAliasType("NDim", _0_64) 76 | """Integer literal between 0 and 64, inclusive.""" 77 | -------------------------------------------------------------------------------- /optype/numpy/_ufunc.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import types 3 | from collections.abc import Callable, Iterable, Mapping, Sequence 4 | from typing import Any, Literal as L, LiteralString, Protocol, TypeAlias # noqa: N817 5 | 6 | if sys.version_info >= (3, 13): 7 | from typing import TypeVar, runtime_checkable 8 | else: 9 | from typing_extensions import TypeVar, runtime_checkable 10 | 11 | import numpy as np 12 | 13 | import optype.numpy._compat as _x 14 | from ._shape import AnyShape 15 | from optype._utils import set_module 16 | 17 | __all__ = ["CanArrayFunction", "CanArrayUFunc", "UFunc"] 18 | 19 | 20 | def __dir__() -> list[str]: 21 | return __all__ 22 | 23 | 24 | ### 25 | 26 | 27 | _AnyFunc: TypeAlias = Callable[..., Any] 28 | _AnyArray: TypeAlias = np.ndarray[AnyShape, np.dtype[Any]] 29 | 30 | _FT_co = TypeVar("_FT_co", bound=_AnyFunc, default=_AnyFunc, covariant=True) 31 | _NInT_co = TypeVar("_NInT_co", bound=int, default=int, covariant=True) 32 | _NoutT_co = TypeVar("_NoutT_co", bound=int, default=int, covariant=True) 33 | _SigT_co = TypeVar( 34 | "_SigT_co", 35 | bound=LiteralString | None, 36 | default=LiteralString | None, 37 | covariant=True, 38 | ) 39 | # numpy < 2.1 40 | _SigT0_co = TypeVar("_SigT0_co", bound=str | None, default=str | None, covariant=True) 41 | _IdT_co = TypeVar( 42 | "_IdT_co", 43 | bound=complex | bytes | str | None, 44 | default=float | None, 45 | covariant=True, 46 | ) 47 | 48 | 49 | ### 50 | 51 | 52 | if _x.NP21: 53 | 54 | @runtime_checkable 55 | @set_module("optype.numpy") 56 | class UFunc(Protocol[_FT_co, _NInT_co, _NoutT_co, _SigT_co, _IdT_co]): 57 | """ 58 | A generic interface for `numpy.ufunc` "universal function" instances, 59 | e.g. `numpy.exp`, `numpy.add`, `numpy.frexp`, `numpy.divmod`. 60 | 61 | This also includes gufunc's (generalized universion functions), which 62 | have a specified `signature`, and aren't necessarily element-wise 63 | functions (which "regular" ufuncs are). 64 | At the moment (`numpy>=2.0,<2.2`), the only GUFuncs within numpy are 65 | `matmul`, and `vecdot`. 66 | """ 67 | 68 | @property 69 | def __call__(self, /) -> _FT_co: ... 70 | 71 | # The number of positional-only parameters, within numpy this is 72 | # either 1 or 2, but e.g. `scipy.special.pro_rad2_cv` has 5. 73 | @property 74 | def nin(self, /) -> _NInT_co: ... 75 | # The number of output values, within numpy this is either 1 or 2, 76 | # but e.g. `scipy.special.ellipj` has 4. 77 | @property 78 | def nout(self, /) -> _NoutT_co: ... 79 | # A string i.f.f. this is a gufunc (generalized ufunc). 80 | @property 81 | def signature(self, /) -> _SigT_co: ... 82 | 83 | # If `signature is None and nin == 2 and nout == 1`, this *may* be set 84 | # to a python scalar s.t. `self(x, identity) == x` for all possible 85 | # `x`. 86 | # Within numpy==2.0.0, this is only the case for `multiply` (`1`), 87 | # `logaddexp` (`-inf`), `logaddexp2` (`-inf`), `logical_and` (`True`), 88 | # and `bitwise_and` (`-1`). 89 | # Note that the `complex` return annotation implicitly includes 90 | # `bool | int | float` (these are its supertypes). 91 | @property 92 | def identity(self, /) -> _IdT_co: ... 93 | # Within numpy this is always `nin + nout`, since each output value 94 | # comes with a corresponding (optional) `out` parameter. 95 | @property 96 | def nargs(self, /) -> int: ... 97 | # Equivalent to `len(types)`, within numpy this is at most 24, but for 98 | # 3rd party ufuncs it could be more. 99 | @property 100 | def ntypes(self, /) -> int: ... 101 | # A list of strings (`LiteralString` can't be used for compatibility 102 | # reasons), with signatures in terms of `numpy.dtype.char`, that match 103 | # `r'(\w{nin})->(\w{nout})'`. 104 | # For instance, `np.frexp` has `['e->ei', 'f->fi', 'd->di', 'g->gi']`. 105 | # Note that the `len` of each `types` elements is `nin + nout + 2`. 106 | # Also note that the elements aren't necessarily unique, because the 107 | # available data types are system dependent. 108 | @property 109 | def types(self, /) -> Sequence[LiteralString]: ... 110 | 111 | # raises `ValueError` i.f.f. `nout != 1 or bool(signature)` 112 | @property 113 | def at(self, /) -> Callable[..., None]: ... 114 | # raises `ValueError` i.f.f. `nin != 2 or nout != 1 or bool(signature)` 115 | @property 116 | def outer(self, /) -> _AnyFunc: ... 117 | # raises `ValueError` i.f.f. `nin != 2 or nout != 1 or bool(signature)` 118 | @property 119 | def reduce(self, /) -> _AnyFunc: ... 120 | # raises `ValueError` i.f.f. `nin != 2 or nout != 1 or bool(signature)` 121 | @property 122 | def reduceat(self, /) -> Callable[..., _AnyArray]: ... 123 | # raises `ValueError` i.f.f. `nin != 2 or nout != 1 or bool(signature)` 124 | @property 125 | def accumulate(self, /) -> Callable[..., _AnyArray]: ... 126 | 127 | else: 128 | # `numpy<2.1` 129 | 130 | @runtime_checkable 131 | @set_module("optype.numpy") 132 | class UFunc(Protocol[_FT_co, _NInT_co, _NoutT_co, _SigT0_co, _IdT_co]): 133 | """ 134 | A generic interface for `numpy.ufunc` "universal function" instances, 135 | e.g. `numpy.exp`, `numpy.add`, `numpy.frexp`, `numpy.divmod`. 136 | 137 | This also includes gufunc's (generalized universion functions), which 138 | have a specified `signature`, and aren't necessarily element-wise 139 | functions (which "regular" ufuncs are). 140 | At the moment (`numpy>=2.0,<2.2`), the only GUFuncs within numpy are 141 | `matmul`, and `vecdot`. 142 | """ 143 | 144 | @property 145 | def __call__(self, /) -> _FT_co: ... 146 | 147 | @property 148 | def nin(self, /) -> _NInT_co: ... 149 | @property 150 | def nout(self, /) -> _NoutT_co: ... 151 | @property 152 | def signature(self, /) -> _SigT0_co: ... 153 | @property 154 | def identity(self, /) -> _IdT_co: ... 155 | @property 156 | def nargs(self, /) -> int: ... 157 | @property 158 | def ntypes(self, /) -> int: ... 159 | @property 160 | def types(self, /) -> Sequence[str]: ... 161 | 162 | # The following *methods* were incorrectly typed prior to NumPy 2.1, 163 | # which I (@jorenham) fixed: https://github.com/numpy/numpy/pull/26847 164 | @property 165 | def at(self, /) -> Callable[..., None] | None: ... 166 | @property 167 | def outer(self, /) -> _AnyFunc | None: ... 168 | @property 169 | def reduce(self, /) -> _AnyFunc | None: ... 170 | @property 171 | def reduceat(self, /) -> Callable[..., _AnyArray] | None: ... 172 | @property 173 | def accumulate(self, /) -> Callable[..., _AnyArray] | None: ... 174 | 175 | 176 | _UFT_contra = TypeVar("_UFT_contra", bound=UFunc, default=np.ufunc, contravariant=True) 177 | _T_co = TypeVar("_T_co", default=Any, covariant=True) 178 | 179 | _MethodCommon: TypeAlias = L["__call__", "reduce", "reduceat", "accumulate", "outer"] 180 | 181 | 182 | @runtime_checkable 183 | @set_module("optype.numpy") 184 | class CanArrayUFunc(Protocol[_UFT_contra, _T_co]): 185 | """ 186 | Interface for ufunc operands. 187 | 188 | See Also: 189 | - https://numpy.org/devdocs/reference/arrays.classes.html 190 | """ 191 | 192 | # NOTE: Mypy doesn't understand the Liskov substitution principle when 193 | # positional-only arguments are involved; so `ufunc` and `method` can't 194 | # be made positional-only. 195 | 196 | if _x.NP20: 197 | 198 | def __array_ufunc__( 199 | self, 200 | /, 201 | ufunc: _UFT_contra, 202 | method: L[_MethodCommon, "at"], 203 | *args: Any, 204 | **kwargs: Any, 205 | ) -> _T_co: ... 206 | 207 | else: 208 | 209 | def __array_ufunc__( 210 | self, 211 | /, 212 | ufunc: _UFT_contra, 213 | method: L[_MethodCommon, "inner"], 214 | *args: Any, 215 | **kwargs: Any, 216 | ) -> _T_co: ... 217 | 218 | 219 | _FT_contra = TypeVar("_FT_contra", bound=_AnyFunc, default=_AnyFunc, contravariant=True) 220 | 221 | 222 | @runtime_checkable 223 | @set_module("optype.numpy") 224 | class CanArrayFunction(Protocol[_FT_contra, _T_co]): 225 | def __array_function__( 226 | self, 227 | /, 228 | func: _FT_contra, 229 | # although this could be tighter, this ensures numpy.typing compat 230 | types: Iterable[type["CanArrayFunction"]], 231 | # ParamSpec can only be used on *args and **kwargs for some reason... 232 | args: tuple[Any, ...], 233 | kwargs: Mapping[str, Any], 234 | ) -> types.NotImplementedType | _T_co: ... 235 | -------------------------------------------------------------------------------- /optype/numpy/compat.py: -------------------------------------------------------------------------------- 1 | """Compatibility with older numpy versions.""" 2 | 3 | from numpy.exceptions import ( 4 | AxisError, 5 | ComplexWarning, 6 | DTypePromotionError, 7 | ModuleDeprecationWarning, 8 | TooHardError, 9 | VisibleDeprecationWarning, 10 | ) 11 | 12 | from ._compat import Long as long, ULong as ulong # noqa: N813 13 | from ._scalar import ( 14 | cfloating as complexfloating, 15 | cfloating32 as complexfloating64, 16 | cfloating64 as complexfloating128, 17 | floating, 18 | floating16, 19 | floating32, 20 | floating64, 21 | inexact, 22 | inexact32, 23 | inexact64, 24 | integer, 25 | integer8, 26 | integer16, 27 | integer32, 28 | integer64, 29 | number, 30 | number8, 31 | number16, 32 | number32, 33 | number64, 34 | sinteger as signedinteger, 35 | uinteger as unsignedinteger, 36 | ) 37 | 38 | __all__ = [ 39 | "AxisError", 40 | "ComplexWarning", 41 | "DTypePromotionError", 42 | "ModuleDeprecationWarning", 43 | "TooHardError", 44 | "VisibleDeprecationWarning", 45 | "complexfloating", 46 | "complexfloating64", 47 | "complexfloating128", 48 | "floating", 49 | "floating16", 50 | "floating32", 51 | "floating64", 52 | "inexact", 53 | "inexact32", 54 | "inexact64", 55 | "integer", 56 | "integer8", 57 | "integer16", 58 | "integer32", 59 | "integer64", 60 | "long", 61 | "number", 62 | "number8", 63 | "number16", 64 | "number32", 65 | "number64", 66 | "signedinteger", 67 | "ulong", 68 | "unsignedinteger", 69 | ] 70 | -------------------------------------------------------------------------------- /optype/numpy/ctypeslib.py: -------------------------------------------------------------------------------- 1 | """ 2 | A collection of `ctypes` type aliases for several numpy scalar types. 3 | 4 | NOTE: 5 | `optype.numpy` assumes a `C99`-compatible C compiler, a 32 or 64-bit 6 | system, and a `ILP32`, `LLP64` or `LP64` data model; see 7 | https://en.cppreference.com/w/c/language/arithmetic_types for more info. 8 | 9 | If this is not the case for you, then please open an issue at: 10 | https://github.com/jorenham/optype/issues 11 | """ 12 | 13 | import ctypes as ct 14 | import sys 15 | 16 | # ruff: noqa: N812 17 | from ctypes import ( 18 | c_bool as Bool, 19 | c_byte as Byte, 20 | c_char as Bytes, 21 | c_double as Float64, 22 | c_float as Float32, 23 | c_int as IntC, 24 | c_int8 as Int8, 25 | c_int16 as Int16, 26 | c_int32 as Int32, 27 | c_int64 as Int64, 28 | c_long as Long, 29 | # NOTE: `longdouble` only works as type, not as value! 30 | c_longdouble as LongDouble, 31 | c_longlong as LongLong, 32 | c_short as Short, 33 | c_size_t as UIntP, # `void_p` on numpy<2, but almost always the same 34 | c_ssize_t as IntP, 35 | c_ubyte as UByte, 36 | c_uint as UIntC, 37 | c_uint8 as UInt8, 38 | c_uint16 as UInt16, 39 | c_uint32 as UInt32, 40 | c_uint64 as UInt64, 41 | c_ulong as ULong, 42 | c_ulonglong as ULongLong, 43 | c_ushort as UShort, 44 | ) 45 | from typing import TYPE_CHECKING, Final, Literal, Never, TypeAlias, cast 46 | 47 | if sys.version_info >= (3, 13): 48 | from typing import TypeAliasType, TypeVar 49 | else: 50 | from typing_extensions import TypeAliasType, TypeVar 51 | 52 | if sys.version_info >= (3, 14): 53 | from ctypes import ( 54 | c_double_complex as Complex128, 55 | c_float_complex as Complex64, 56 | c_longdouble_complex as CLongDouble, 57 | ) 58 | else: 59 | Complex64 = Never 60 | Complex128 = Never 61 | CLongDouble = Never 62 | 63 | from ._ctypeslib import CScalar, CType 64 | 65 | # ruff: noqa: RUF022 66 | __all__ = [ 67 | "CType", 68 | "CScalar", 69 | "Array", 70 | 71 | "Generic", 72 | "Number", 73 | "Integer", 74 | "Inexact", 75 | "UnsignedInteger", 76 | "SignedInteger", 77 | "Floating", 78 | "ComplexFloating", 79 | "Flexible", 80 | 81 | "Bool", 82 | 83 | "UInt8", "Int8", 84 | "UInt16", "Int16", 85 | "UInt32", "Int32", 86 | "UInt64", "Int64", 87 | "UByte", "Byte", 88 | "UShort", "Short", 89 | "UIntC", "IntC", 90 | "UIntP", "IntP", 91 | "ULong", "Long", 92 | "ULongLong", "LongLong", 93 | 94 | "Float32", 95 | "Float64", 96 | "LongDouble", 97 | 98 | "Complex64", 99 | "Complex128", 100 | "CLongDouble", 101 | 102 | "Bytes", 103 | "Void", 104 | "Object", 105 | ] # fmt: skip 106 | 107 | 108 | def __dir__() -> list[str]: 109 | return __all__ 110 | 111 | 112 | ### 113 | 114 | 115 | SIZE_BYTE: Final = cast("Literal[1]", ct.sizeof(ct.c_byte)) 116 | SIZE_SHORT: Final = cast("Literal[2]", ct.sizeof(ct.c_short)) 117 | SIZE_INTC: Final = cast("Literal[4]", ct.sizeof(ct.c_int)) 118 | SIZE_INTP: Final = cast("Literal[4, 8]", ct.sizeof(ct.c_ssize_t)) 119 | SIZE_LONG: Final = cast("Literal[4, 8]", ct.sizeof(ct.c_long)) 120 | SIZE_LONGLONG: Final = cast("Literal[8]", ct.sizeof(ct.c_longlong)) 121 | 122 | SIZE_SINGLE: Final = cast("Literal[4]", ct.sizeof(ct.c_float)) 123 | SIZE_DOUBLE: Final = cast("Literal[8]", ct.sizeof(ct.c_double)) 124 | SIZE_LONGDOUBLE: Final = cast("Literal[8, 10, 12, 16]", ct.sizeof(ct.c_longdouble)) 125 | 126 | 127 | def __is_dev() -> bool: 128 | from importlib import metadata # noqa: PLC0415 129 | 130 | return "dev" in metadata.version((__package__ or "optype").removesuffix(".numpy")) # noqa: PLR2004 131 | 132 | 133 | if __is_dev(): 134 | assert SIZE_BYTE == 1, f"`sizeof(byte) = {SIZE_BYTE}`, expected 1" 135 | assert SIZE_SHORT == 2, f"`sizeof(short) = {SIZE_SHORT}`, expected 2" 136 | # If you run a 16-bit system and this assertion fails, please open an issue, or even 137 | # better, upgrade to something that's younger than Joe fucking Biden. 138 | assert SIZE_INTC == 4, f"`sizeof(int) = {SIZE_INTC}`, expected 4" 139 | assert SIZE_INTP in {4, 8}, f"`sizeof(ssize_t) = {SIZE_INTP}`, expected 4 or 8" 140 | assert SIZE_LONG in {4, 8}, f"`sizeof(long int) = {SIZE_LONG}`, expected 4 or 8" 141 | assert SIZE_LONGLONG == 8, f"`sizeof(long long int) = {SIZE_LONGLONG}`, expected 8" 142 | 143 | assert SIZE_SINGLE == 4, f"`sizeof(float) = {SIZE_SINGLE}`, expected 4" 144 | assert SIZE_DOUBLE == 8, f"`sizeof(double) = {SIZE_DOUBLE}`, expected 8" 145 | assert SIZE_LONGDOUBLE in {8, 10, 12, 16}, ( 146 | f"`sizeof(long double) = {SIZE_LONGDOUBLE}`, expected 8, 10, 12 or 16", 147 | ) # fmt: skip 148 | 149 | 150 | CT = TypeVar("CT", bound="CType") 151 | _Array: TypeAlias = ct.Array[CT] | ct.Array["_Array[CT]"] 152 | Array = TypeAliasType("Array", _Array[CT], type_params=(CT,)) 153 | 154 | # `c_(u)byte` is an alias for `c_(u)int8` 155 | _UnsignedInteger: TypeAlias = ( 156 | UInt8 | UInt16 | UInt32 | UInt64 | UShort | UIntC | UIntP | ULong | ULongLong 157 | ) 158 | _SignedInteger: TypeAlias = ( 159 | Int8 | Int16 | Int32 | Int64 | Short | IntC | IntP | Long | LongLong 160 | ) 161 | _Floating: TypeAlias = ct.c_float | ct.c_double | ct.c_longdouble 162 | UnsignedInteger = TypeAliasType("UnsignedInteger", _UnsignedInteger) 163 | SignedInteger = TypeAliasType("SignedInteger", _SignedInteger) 164 | Integer = TypeAliasType("Integer", _UnsignedInteger | _SignedInteger) 165 | Floating = TypeAliasType("Floating", _Floating) 166 | 167 | Void: TypeAlias = ct.Structure | ct.Union 168 | 169 | # subscripting at runtime will give an error, and no default is defined... 170 | if TYPE_CHECKING: 171 | Object: TypeAlias = ct.py_object[object] 172 | else: 173 | Object: TypeAlias = ct.py_object 174 | 175 | Flexible = TypeAliasType("Flexible", Bytes | Void) 176 | 177 | if sys.version_info >= (3, 14): 178 | Complex64: TypeAlias = ct.c_float_complex 179 | Complex128: TypeAlias = ct.c_double_complex 180 | CLongDouble: TypeAlias = ct.c_longdouble_complex 181 | ComplexFloating = TypeAliasType( 182 | "ComplexFloating", 183 | Complex64 | Complex128 | CLongDouble, 184 | ) 185 | Inexact: TypeAlias = Floating | ComplexFloating 186 | Number: TypeAlias = Integer | Inexact 187 | Generic = TypeAliasType("Generic", Bool | Number | Flexible | Object) 188 | else: 189 | ComplexFloating = TypeAliasType("ComplexFloating", Never) 190 | Inexact: TypeAlias = Floating 191 | Number: TypeAlias = Integer | Floating 192 | Generic = TypeAliasType("Generic", Bool | Integer | Floating | Flexible | Object) 193 | -------------------------------------------------------------------------------- /optype/numpy/random.py: -------------------------------------------------------------------------------- 1 | """ 2 | Type aliases for [SPEC 7](https://scientific-python.org/specs/spec-0007/). 3 | """ 4 | 5 | import sys 6 | from collections.abc import Sequence 7 | from typing import Any, TypeAlias 8 | 9 | if sys.version_info >= (3, 13): 10 | from typing import TypeAliasType 11 | else: 12 | from typing_extensions import TypeAliasType 13 | 14 | import numpy as np 15 | 16 | __all__ = ["RNG", "ToRNG", "ToSeed"] 17 | 18 | ### 19 | 20 | _Integral: TypeAlias = np.integer[Any] | np.timedelta64 21 | _IntOrSequence: TypeAlias = int | _Integral | Sequence[int | _Integral] 22 | 23 | ToSeed = TypeAliasType( 24 | "ToSeed", 25 | np.random.SeedSequence 26 | | _IntOrSequence 27 | | np.ndarray[Any, np.dtype[_Integral | np.flexible | np.object_]], 28 | ) 29 | 30 | RNG = TypeAliasType("RNG", np.random.Generator | np.random.RandomState) 31 | ToRNG = TypeAliasType("ToRNG", RNG | np.random.BitGenerator | ToSeed) 32 | -------------------------------------------------------------------------------- /optype/pickle.py: -------------------------------------------------------------------------------- 1 | """ 2 | Runtime-protocols for the `pickle` standard library. 3 | https://docs.python.org/3/library/pickle.html 4 | """ 5 | 6 | import sys 7 | from collections.abc import Callable, Iterable 8 | from typing import Protocol, Self, SupportsIndex, TypeAlias 9 | 10 | if sys.version_info >= (3, 13): 11 | from typing import ParamSpec, TypeVar, override, runtime_checkable 12 | else: 13 | from typing_extensions import ParamSpec, TypeVar, override, runtime_checkable 14 | 15 | 16 | __all__ = ( 17 | "CanGetnewargs", 18 | "CanGetnewargsEx", 19 | "CanGetstate", 20 | "CanReduce", 21 | "CanReduceEx", 22 | "CanSetstate", 23 | ) 24 | 25 | 26 | def __dir__() -> tuple[str, ...]: 27 | return __all__ 28 | 29 | 30 | ### 31 | 32 | 33 | _Tss = ParamSpec("_Tss", default=...) 34 | _ArgT = TypeVar("_ArgT", default=object) 35 | _ArgT_co = TypeVar("_ArgT_co", covariant=True, default=object) 36 | _StateT_co = TypeVar("_StateT_co", covariant=True) 37 | _StateT_contra = TypeVar("_StateT_contra", contravariant=True) 38 | 39 | _Tuple: TypeAlias = tuple[object, ...] 40 | _Iter1: TypeAlias = Iterable[object] 41 | _Iter2: TypeAlias = Iterable[tuple[object, object]] 42 | _Callable: TypeAlias = Callable[_Tss, object] 43 | 44 | _ReduceValue: TypeAlias = ( 45 | str 46 | | tuple[_Callable, _Tuple] 47 | | tuple[_Callable, _Tuple, object] 48 | | tuple[_Callable, _Tuple, object, _Iter1 | None] 49 | | tuple[_Callable, _Tuple, object, _Iter1 | None, _Iter2 | None] 50 | | tuple[ 51 | _Callable, 52 | _Tuple, 53 | object, 54 | _Iter1 | None, 55 | _Iter2 | None, 56 | _Callable[[object, object]] | None, 57 | ] 58 | ) 59 | _RT_co = TypeVar("_RT_co", bound=_ReduceValue, default=_ReduceValue, covariant=True) 60 | 61 | 62 | ### 63 | 64 | 65 | @runtime_checkable 66 | class CanReduce(Protocol[_RT_co]): 67 | """https://docs.python.org/3/library/pickle.html#object.__reduce__""" 68 | 69 | @override 70 | def __reduce__(self, /) -> _RT_co: ... 71 | 72 | 73 | @runtime_checkable 74 | class CanReduceEx(Protocol[_RT_co]): 75 | """https://docs.python.org/3/library/pickle.html#object.__reduce_ex__""" 76 | 77 | @override 78 | def __reduce_ex__(self, v: SupportsIndex, /) -> _RT_co: ... 79 | 80 | 81 | @runtime_checkable 82 | class CanGetstate(Protocol[_StateT_co]): 83 | """ 84 | https://docs.python.org/3/library/pickle.html#object.__getstate__ 85 | """ 86 | 87 | @override 88 | def __getstate__(self, /) -> _StateT_co: ... 89 | 90 | 91 | @runtime_checkable 92 | class CanSetstate(Protocol[_StateT_contra]): 93 | """https://docs.python.org/3/library/pickle.html#object.__setstate__""" 94 | 95 | def __setstate__(self, state: _StateT_contra, /) -> None: ... 96 | 97 | 98 | @runtime_checkable 99 | class CanGetnewargs(Protocol[_ArgT_co]): 100 | """https://docs.python.org/3/library/pickle.html#object.__getnewargs__""" 101 | 102 | def __new__(cls, /, *args: _ArgT_co) -> Self: ... 103 | def __getnewargs__(self, /) -> tuple[_ArgT_co, ...]: ... 104 | 105 | 106 | @runtime_checkable 107 | class CanGetnewargsEx(Protocol[_ArgT_co, _ArgT]): 108 | """https://docs.python.org/3/library/pickle.html#object.__getnewargs_ex__""" 109 | 110 | def __new__(cls, /, *args: _ArgT_co, **kwargs: _ArgT) -> Self: ... 111 | def __getnewargs_ex__(self, /) -> tuple[tuple[_ArgT_co, ...], dict[str, _ArgT]]: ... 112 | -------------------------------------------------------------------------------- /optype/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorenham/optype/cbd461192152db506b6a27a5342f9ae217ba1e49/optype/py.typed -------------------------------------------------------------------------------- /optype/string.py: -------------------------------------------------------------------------------- 1 | """ 2 | Type aliases for the `strings` standard library. 3 | 4 | See Also: 5 | - https://docs.python.org/3/library/string.html 6 | """ 7 | 8 | from typing import Final, Literal as L, TypeAlias # noqa: N817 9 | 10 | __all__ = ( 11 | "DIGITS", 12 | "DIGITS_BIN", 13 | "DIGITS_HEX", 14 | "DIGITS_OCT", 15 | "LETTERS", 16 | "LETTERS_LOWER", 17 | "LETTERS_UPPER", 18 | "PRINTABLE", 19 | "PUNCTUATION", 20 | "WHITESPACE", 21 | "Digit", 22 | "DigitBin", 23 | "DigitHex", 24 | "DigitOct", 25 | "Letter", 26 | "LetterLower", 27 | "LetterUpper", 28 | "Printable", 29 | "Punctuation", 30 | "Whitespace", 31 | ) 32 | 33 | 34 | DigitBin: TypeAlias = L["0", "1"] 35 | DIGITS_BIN: Final = "0", "1" 36 | 37 | # compatible with `string.octdigits` 38 | DigitOct: TypeAlias = L["0", "1", "2", "3", "4", "5", "6", "7"] 39 | DIGITS_OCT: Final = "0", "1", "2", "3", "4", "5", "6", "7" 40 | 41 | # compatible with `string.hexdigits` 42 | DigitHex: TypeAlias = L[ 43 | "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 44 | "a", "b", "c", "d", "e", "f", 45 | "A", "B", "C", "D", "E", "F", 46 | ] # fmt: skip 47 | DIGITS_HEX: Final = ( 48 | "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 49 | "a", "b", "c", "d", "e", "f", "A", "B", "C", "D", "E", "F", 50 | ) # fmt: skip 51 | 52 | # compatible with `string.digits` 53 | Digit: TypeAlias = L["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] 54 | DIGITS: Final = "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" 55 | 56 | 57 | # compatible with `string.ascii_letters` 58 | LetterLower: TypeAlias = L[ 59 | "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", 60 | "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", 61 | ] # fmt: skip 62 | LETTERS_LOWER: Final = ( 63 | "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", 64 | "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", 65 | ) # fmt: skip 66 | 67 | # compatible with `string.ascii_lowercase` 68 | LetterUpper: TypeAlias = L[ 69 | "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", 70 | "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", 71 | ] # fmt: skip 72 | LETTERS_UPPER: Final = ( 73 | "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", 74 | "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", 75 | ) # fmt: skip 76 | 77 | # compatible with `string.ascii_letters` 78 | Letter: TypeAlias = LetterLower | LetterUpper 79 | LETTERS: Final = LETTERS_LOWER + LETTERS_UPPER 80 | 81 | 82 | # compatible with `string.punctuation` 83 | Punctuation: TypeAlias = L[ 84 | "!", '"', "#", "$", "%", "&", "'", "(", 85 | ")", "*", "+", ",", "-", ".", "/", ":", 86 | ";", "<", "=", ">", "?", "@", "[", "\\", 87 | "]", "^", "_", "`", "{", "|", "}", "~", 88 | ] # fmt: skip 89 | PUNCTUATION: Final = ( 90 | "!", '"', "#", "$", "%", "&", "'", "(", 91 | ")", "*", "+", ",", "-", ".", "/", ":", 92 | ";", "<", "=", ">", "?", "@", "[", "\\", 93 | "]", "^", "_", "`", "{", "|", "}", "~", 94 | ) # fmt: skip 95 | 96 | # compatible with `string.whitespace` 97 | Whitespace: TypeAlias = L[" ", "\t", "\n", "\r", "\v", "\f"] 98 | WHITESPACE: Final = " ", "\t", "\n", "\r", "\v", "\f" 99 | 100 | # compatible with `string.printable` 101 | Printable: TypeAlias = Digit | Letter | Punctuation | Whitespace 102 | PRINTABLE: Final = DIGITS + LETTERS + PUNCTUATION + WHITESPACE 103 | -------------------------------------------------------------------------------- /optype/types/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from typing import Literal, LiteralString, Protocol 3 | 4 | if sys.version_info >= (3, 13): 5 | from typing import runtime_checkable 6 | else: 7 | from typing_extensions import runtime_checkable 8 | 9 | from ._typeforms import AnnotatedAlias, GenericType, LiteralAlias, UnionAlias 10 | 11 | __all__ = ( 12 | "AnnotatedAlias", 13 | "Deprecated", 14 | "GenericType", 15 | "LiteralAlias", 16 | "ProtocolType", 17 | "RuntimeProtocolType", 18 | "UnionAlias", 19 | "WrappedFinalType", 20 | ) 21 | 22 | 23 | def __dir__() -> tuple[str, ...]: 24 | return __all__ 25 | 26 | 27 | @runtime_checkable 28 | class Deprecated(Protocol): 29 | """ 30 | A runtime-protocol that represents a type or method that's decorated with 31 | `@typing.deprecated` or `@typing_extensions.deprecated`. 32 | """ 33 | 34 | __deprecated__: str 35 | 36 | 37 | @runtime_checkable 38 | class WrappedFinalType(Protocol): 39 | """ 40 | A runtime-protocol that represents a type or method that's decorated with 41 | `@typing.final` or `@typing_extensions.final`. 42 | 43 | Note that the name `HasFinal` isn't used, because `__final__` is 44 | undocumented, and therefore not a part of the public API. 45 | """ 46 | 47 | __final__: Literal[True] 48 | 49 | 50 | @runtime_checkable 51 | class ProtocolType(Protocol): 52 | _is_protocol: Literal[True] 53 | 54 | if sys.version_info >= (3, 12, 0): 55 | __protocol_attrs__: set[LiteralString] 56 | 57 | 58 | @runtime_checkable 59 | class RuntimeProtocolType(ProtocolType, Protocol): 60 | _is_runtime_protocol: Literal[True] 61 | 62 | if sys.version_info >= (3, 12, 2): 63 | __non_callable_proto_members__: set[LiteralString] 64 | -------------------------------------------------------------------------------- /optype/types/_typeforms.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated, Generic, Literal, TypeVar 2 | 3 | __all__ = "AnnotatedAlias", "GenericType", "LiteralAlias", "UnionAlias" 4 | 5 | 6 | _T = TypeVar("_T") 7 | 8 | 9 | class _C(Generic[_T]): ... 10 | 11 | 12 | # typing._GenericAlias 13 | # NOTE: this is not the same as`types.GenericAlias`! 14 | GenericType = type(_C[None]) 15 | 16 | # typing._AnnotatedAlias 17 | AnnotatedAlias = type(Annotated[None, None]) 18 | 19 | # typing._LiteralGenericAlias 20 | LiteralAlias = type(Literal[0]) 21 | 22 | # typing._UnionGenericAlias 23 | # NOTE: this is not the same as `types.UnionType`! 24 | UnionAlias = type(Literal[0] | None) 25 | -------------------------------------------------------------------------------- /optype/types/_typeforms.pyi: -------------------------------------------------------------------------------- 1 | import types as _types 2 | from collections.abc import Iterator 3 | from typing import Any, Generic, Self, TypeAlias, _SpecialForm, final, type_check_only 4 | from typing_extensions import ParamSpec, TypeAliasType, TypeVar, TypeVarTuple, override 5 | 6 | __all__ = "AnnotatedAlias", "GenericType", "LiteralAlias", "UnionAlias" 7 | 8 | _Ts_co = TypeVar( 9 | "_Ts_co", bound=tuple[object, ...] | TypeVarTuple | GenericType, covariant=True 10 | ) 11 | 12 | _TypeExpr: TypeAlias = type | _types.GenericAlias | GenericType | TypeAliasType 13 | _TypeParam: TypeAlias = ( 14 | TypeVar | ParamSpec | TypeVarTuple | UnpackAlias[tuple[object, ...]] 15 | ) 16 | 17 | # represents `typing._GenericAlias` 18 | # NOTE: This is different from `typing.GenericAlias`! 19 | class GenericType: 20 | @property 21 | def __origin__(self, /) -> _TypeExpr | _SpecialForm: ... 22 | @property 23 | def __args__(self, /) -> tuple[Any, ...]: ... 24 | @property 25 | def __parameters__(self, /) -> tuple[_TypeParam, ...]: ... 26 | @override 27 | def __init_subclass__(cls, /, *, _root: bool = ...) -> None: ... 28 | def __init__( 29 | self, 30 | origin: _TypeExpr | _SpecialForm, 31 | args: tuple[object, ...] | object, 32 | /, 33 | ) -> None: ... 34 | def __or__(self, rhs: type | object, /) -> UnionAlias: ... 35 | def __ror__(self, lhs: type | object, /) -> UnionAlias: ... 36 | def __getitem__(self, args: type | object, /) -> GenericType: ... 37 | def copy_with(self, params: object, /) -> GenericType: ... 38 | def __iter__(self, /) -> Iterator[UnpackAlias[Self]]: ... 39 | def __call__(self, /, *args: object, **kwargs: object) -> _SpecialForm | object: ... 40 | def __instancecheck__(self, obj: object, /) -> bool: ... 41 | def __subclasscheck__(self, obj: type, /) -> bool: ... 42 | def __mro_entries__(self, bases: tuple[type, ...]) -> tuple[type, ...]: ... 43 | 44 | @final 45 | class LiteralAlias(GenericType): ... 46 | 47 | @final 48 | class UnionAlias(GenericType): ... 49 | 50 | @final 51 | class AnnotatedAlias(GenericType): 52 | @property 53 | @override 54 | def __origin__(self, /) -> _TypeExpr: ... 55 | @property 56 | def __metadata__(self, /) -> tuple[object, *tuple[object, ...]]: ... 57 | 58 | @type_check_only 59 | class UnpackAlias(GenericType, Generic[_Ts_co]): 60 | @property 61 | def __typing_unpacked_tuple_args__(self, /) -> tuple[object, ...] | None: ... 62 | @property 63 | def __typing_is_unpacked_typevartuple__(self, /) -> bool: ... 64 | @property 65 | @override 66 | def __origin__(self, /) -> type[_SpecialForm]: ... 67 | @property 68 | @override 69 | def __args__(self, /) -> tuple[_Ts_co]: ... 70 | @property 71 | @override 72 | def __parameters__(self, /) -> tuple[()] | tuple[TypeVarTuple]: ... 73 | @override 74 | def __init__(self, origin: _SpecialForm, args: tuple[_Ts_co], /) -> None: ... 75 | -------------------------------------------------------------------------------- /optype/typing.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from typing import ( 3 | TYPE_CHECKING, 4 | Literal, 5 | LiteralString, 6 | Never, 7 | NoReturn, 8 | Protocol, 9 | TypeAlias, 10 | runtime_checkable, 11 | ) 12 | from typing_extensions import deprecated 13 | 14 | if TYPE_CHECKING: 15 | import enum 16 | 17 | if sys.version_info >= (3, 13): 18 | from typing import TypedDict, TypeVar 19 | else: 20 | from typing_extensions import TypedDict, TypeVar 21 | 22 | from ._core import _can as _c, _just 23 | 24 | __all__ = ( 25 | "AnyComplex", 26 | "AnyFloat", 27 | "AnyInt", 28 | "AnyIterable", 29 | "AnyLiteral", 30 | "EmptyBytes", 31 | "EmptyDict", 32 | "EmptyIterable", 33 | "EmptyList", 34 | "EmptySet", 35 | "EmptyString", 36 | "EmptyTuple", 37 | "Just", 38 | "JustComplex", 39 | "JustFloat", 40 | "JustInt", 41 | "LiteralBool", 42 | "LiteralByte", 43 | ) 44 | 45 | 46 | def __dir__() -> tuple[str, ...]: 47 | return __all__ 48 | 49 | 50 | ### 51 | 52 | _T = TypeVar("_T") 53 | _IntT = TypeVar("_IntT", bound=int, default=int) 54 | _ValueT = TypeVar("_ValueT", default=object) 55 | 56 | 57 | ### 58 | 59 | 60 | @deprecated( 61 | "`optype.typing.Just` has been deprecated in favor of `optype.Just` " 62 | "and will be removed in optype 0.10.0", 63 | ) 64 | class Just( # type: ignore[misc] 65 | _just.Just[_T], # pyright: ignore[reportGeneralTypeIssues] 66 | Protocol[_T], 67 | ): ... 68 | 69 | 70 | @deprecated( 71 | "`optype.typing.JustInt` has been deprecated in favor of `optype.JustInt` " 72 | "and will be removed in optype 0.10.0", 73 | ) 74 | @runtime_checkable 75 | class JustInt(_just.JustInt, Protocol, just=int): ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] 76 | 77 | 78 | @deprecated( 79 | "`optype.typing.JustFloat` has been deprecated in favor of `optype.JustFloat` " 80 | "and will be removed in optype 0.10.0", 81 | ) 82 | @runtime_checkable 83 | class JustFloat(_just.JustFloat, Protocol, just=float): ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] 84 | 85 | 86 | @deprecated( 87 | "`optype.typing.JustComplex` has been deprecated in favor of `optype.JustComplex` " 88 | "and will be removed in optype 0.10.0", 89 | ) 90 | @runtime_checkable 91 | class JustComplex(_just.JustComplex, Protocol, just=complex): ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] 92 | 93 | 94 | Just.__doc__ = _just.Just.__doc__ # pyright: ignore[reportDeprecated] 95 | JustInt.__doc__ = _just.JustInt.__doc__ # pyright: ignore[reportDeprecated] 96 | JustFloat.__doc__ = _just.JustFloat.__doc__ # pyright: ignore[reportDeprecated] 97 | JustComplex.__doc__ = _just.JustComplex.__doc__ # pyright: ignore[reportDeprecated] 98 | 99 | ### 100 | 101 | 102 | # Anything that can *always* be converted to an `int` / `float` / `complex` 103 | AnyInt: TypeAlias = _IntT | _c.CanInt[_IntT] | _c.CanIndex[_IntT] 104 | 105 | AnyFloat: TypeAlias = _c.CanFloat | _c.CanIndex 106 | AnyComplex: TypeAlias = _c.CanComplex | _c.CanFloat | _c.CanIndex 107 | 108 | # Anything that can be iterated over, e.g. in a `for` loop,`builtins.iter`, 109 | # `builtins.enumerate`, or `numpy.array`. 110 | AnyIterable: TypeAlias = _c.CanIter[_c.CanNext[_ValueT]] | _c.CanGetitem[int, _ValueT] 111 | 112 | # The closest supertype of a `Literal`, i.e. the allowed types that can be 113 | # passed to `typing.Literal`. 114 | AnyLiteral: TypeAlias = ( 115 | "bool | _just.JustInt | LiteralString | bytes | enum.Enum | None" 116 | ) 117 | 118 | 119 | # Empty collection type aliases 120 | 121 | 122 | class _EmptyTypedDict(TypedDict): 123 | pass 124 | 125 | 126 | EmptyString: TypeAlias = Literal[""] 127 | EmptyBytes: TypeAlias = Literal[b""] 128 | EmptyTuple: TypeAlias = ( 129 | # this should be the only that's needed here, but in practice we need 3 130 | # other variants, that are equivalent, but are somehow treated as 131 | # different types by pyright or mypy or both 132 | tuple[()] 133 | # both mypy and pyright don't simplify this to `tuple[()]`, even though 134 | # it's equivalent, and `tuple[()]` is much easier to read 135 | | tuple[Never, ...] 136 | # in pyright `NoReturn` and `Never` aren't identical, only equivalent 137 | | tuple[NoReturn, ...] 138 | # this is what infers the result of `tuple[Never, ...] + tuple[()]` as... 139 | | tuple[*tuple[Never, ...]] 140 | ) 141 | EmptyList: TypeAlias = list[Never] 142 | EmptySet: TypeAlias = set[Never] 143 | EmptyDict: TypeAlias = dict[object, Never] | _EmptyTypedDict 144 | EmptyIterable: TypeAlias = AnyIterable[Never] 145 | 146 | 147 | # Literal 148 | 149 | LiteralBool: TypeAlias = Literal[False, True] # noqa: RUF038 150 | LiteralByte: TypeAlias = Literal[ 151 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 152 | 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 153 | 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 154 | 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 155 | 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 156 | 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 157 | 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 158 | 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 159 | 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 160 | 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 161 | 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 162 | 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 163 | 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 164 | 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 165 | 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 166 | 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 167 | 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 168 | 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 169 | 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 170 | 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 171 | 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 172 | 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 173 | 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 174 | 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 175 | 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 176 | 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 177 | 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 178 | 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 179 | 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 180 | 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 181 | 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 182 | 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 183 | ] # fmt: skip 184 | -------------------------------------------------------------------------------- /scripts/config/bpr-np-1.25.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../pyproject.toml", 3 | "defineConstant": { 4 | "NP125": true, 5 | "NP20": false, 6 | "NP21": false, 7 | "NP22": false, 8 | "NP23": false 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /scripts/config/bpr-np-1.26.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./bpr-np-1.25.json" 3 | } 4 | -------------------------------------------------------------------------------- /scripts/config/bpr-np-2.0.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./bpr-np-1.26.json", 3 | "defineConstant": { "NP20": true } 4 | } 5 | -------------------------------------------------------------------------------- /scripts/config/bpr-np-2.1.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./bpr-np-2.0.json", 3 | "defineConstant": { "NP21": true } 4 | } 5 | -------------------------------------------------------------------------------- /scripts/config/bpr-np-2.2.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./bpr-np-2.1.json", 3 | "defineConstant": { "NP22": true } 4 | } 5 | -------------------------------------------------------------------------------- /scripts/config/bpr-np-2.3.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./bpr-np-2.2.json", 3 | "defineConstant": { "NP23": true } 4 | } 5 | -------------------------------------------------------------------------------- /scripts/my.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Run as `uv run scripts/my.py` from the repo root.""" 4 | 5 | import subprocess 6 | import sys 7 | from collections.abc import Generator 8 | from typing import Final, TypeAlias 9 | 10 | _Version: TypeAlias = tuple[int, int] 11 | 12 | # TODO: figure these out dynamically, e.g. with `distutils` or from `pyproject.toml` 13 | _NP1_MIN: Final = 1, 25 14 | _NP1_MAX: Final = 1, 26 15 | _NP2_MIN: Final = 2, 0 16 | _NP2_MAX: Final = 2, 3 17 | _NP_SKIP: Final = frozenset({(1, 26)}) 18 | 19 | 20 | def _np_version() -> _Version: 21 | import numpy as np # noqa: PLC0415 22 | 23 | major, minor = map(int, np.__version__.split(".", 3)[:2]) 24 | version = major, minor 25 | assert _NP1_MIN <= version <= _NP2_MAX 26 | return version 27 | 28 | 29 | def _np_version_range( 30 | first: _Version = _NP1_MIN, 31 | last: _Version = _NP2_MAX, 32 | ) -> Generator[_Version]: 33 | assert _NP1_MIN <= first <= _NP2_MAX 34 | assert _NP1_MIN <= last <= _NP2_MAX 35 | 36 | if first >= last: 37 | return 38 | 39 | v0, v1 = first 40 | 41 | while v0 < last[0]: 42 | if v1 > _NP1_MAX[1]: 43 | assert v0 == 1 44 | v0, v1 = _NP2_MIN 45 | break 46 | 47 | if (v0, v1) not in _NP_SKIP: 48 | yield v0, v1 49 | v1 += 1 50 | 51 | assert v0 == last[0] 52 | assert v1 <= last[1] 53 | 54 | for v in range(v1, last[1] + 1): 55 | if (v0, v) not in _NP_SKIP: 56 | yield v0, v 57 | 58 | 59 | def main(*args: str) -> int: 60 | v0 = _np_version() 61 | 62 | cmd: list[str] = [ 63 | "mypy", 64 | "--tb", 65 | "--hide-error-context", 66 | "--hide-error-code-links", 67 | ] 68 | for vi in _np_version_range(): 69 | const = f"NP{vi[0]}{vi[1]}" 70 | supported = "true" if v0 >= vi else "false" 71 | cmd.append(f"--always-{supported}={const}") 72 | 73 | if not args: 74 | cmd.append(".") 75 | else: 76 | cmd.extend(args) 77 | 78 | print(*cmd) # noqa: T201 79 | return subprocess.call(cmd) 80 | 81 | 82 | if __name__ == "__main__": 83 | sys.exit(main(*sys.argv[1:])) 84 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | from beartype.claw import beartype_this_package as __beartype_this_package 2 | 3 | __beartype_this_package() 4 | -------------------------------------------------------------------------------- /tests/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorenham/optype/cbd461192152db506b6a27a5342f9ae217ba1e49/tests/core/__init__.py -------------------------------------------------------------------------------- /tests/core/test_do.py: -------------------------------------------------------------------------------- 1 | import optype as op 2 | from optype import _utils 3 | from optype._core import _do 4 | 5 | 6 | def test_all_public() -> None: 7 | """Ensure all callables in `optype._do` are in `optype.__all__`.""" 8 | callables_all = _utils.get_callables(op) 9 | callables_do = _utils.get_callables(_do) 10 | 11 | assert callables_all == callables_do 12 | -------------------------------------------------------------------------------- /tests/core/test_does.py: -------------------------------------------------------------------------------- 1 | from typing import assert_type, final 2 | 3 | from optype import do_iadd 4 | 5 | 6 | def test_iadd_iadd() -> None: 7 | @final 8 | class IntIAdd: 9 | def __init__(self, x: int, /) -> None: 10 | self.x = x 11 | 12 | def __iadd__(self, y: int, /) -> int: 13 | self.x += +y 14 | return self.x 15 | 16 | lhs = IntIAdd(1) 17 | out = do_iadd(lhs, 2) 18 | 19 | assert_type(out, int) 20 | assert out == 3 21 | 22 | 23 | def test_iadd_add() -> None: 24 | @final 25 | class IntAdd: 26 | def __init__(self, x: int, /) -> None: 27 | self.x = x 28 | 29 | def __add__(self, y: int, /) -> int: 30 | return self.x + y 31 | 32 | lhs = IntAdd(1) 33 | out = do_iadd(lhs, 2) 34 | 35 | assert_type(out, int) 36 | assert out == 3 37 | 38 | 39 | def test_iadd_radd() -> None: 40 | @final 41 | class IntRAdd: 42 | def __init__(self, x: int, /) -> None: 43 | self.x = x 44 | 45 | def __radd__(self, y: int, /) -> int: 46 | return self.x + y 47 | 48 | rhs = IntRAdd(1) 49 | out = do_iadd(2, rhs) 50 | 51 | assert_type(out, int) 52 | assert out == 3 53 | 54 | 55 | # the analogous tests for the other inplace binary ops are omitted, because 56 | # the interface implementations are structurally equivalent, and I'm lazy 57 | -------------------------------------------------------------------------------- /tests/core/test_has_types.pyi: -------------------------------------------------------------------------------- 1 | # pyright: reportInvalidStubStatement=false 2 | 3 | from typing import LiteralString, TypedDict, TypeVar, assert_type 4 | 5 | import optype as op 6 | 7 | _TypeT = TypeVar("_TypeT", bound=type) 8 | 9 | class A: ... 10 | class B(A): ... 11 | class C(B): ... 12 | 13 | a: A 14 | b: B 15 | c: C 16 | 17 | def typeof(obj: op.HasClass[_TypeT], /) -> _TypeT: ... 18 | 19 | ### 20 | # HasClass 21 | 22 | a__a: op.HasClass[type[A]] = a 23 | b__b: op.HasClass[type[B]] = b 24 | c__c: op.HasClass[type[C]] = c 25 | 26 | a__b: op.HasClass[type[A]] = b # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 27 | a__c: op.HasClass[type[A]] = c # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 28 | 29 | b__a: op.HasClass[type[B]] = a # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 30 | b__c: op.HasClass[type[B]] = c # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 31 | 32 | c__a: op.HasClass[type[C]] = a # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 33 | c__b: op.HasClass[type[C]] = b # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 34 | 35 | int_obj: int 36 | assert_type(type(int_obj), type[int]) 37 | assert_type(typeof(int_obj), type[int]) 38 | 39 | int_str: int | str 40 | assert_type(type(int_str), type[int | str]) 41 | typeof(int_str) # type: ignore[misc] # pyright: ignore[reportArgumentType] 42 | 43 | bool_or_int: bool | int 44 | assert_type(type(bool_or_int), type[bool] | type[int]) 45 | assert_type(typeof(bool_or_int), type[int]) # type: ignore[arg-type] # mypy fail 46 | 47 | lit_str: LiteralString 48 | assert_type(type(lit_str), type[str]) 49 | assert_type(typeof(lit_str), type[str]) 50 | 51 | class TDict(TypedDict): ... 52 | 53 | tdict: TDict 54 | assert_type(type(tdict), type[TDict]) 55 | assert_type(typeof(tdict), type[TDict]) # type: ignore[arg-type] 56 | -------------------------------------------------------------------------------- /tests/core/test_just.py: -------------------------------------------------------------------------------- 1 | # ruff: noqa: DTZ005, DTZ011 2 | 3 | import datetime as dt 4 | 5 | import pytest 6 | 7 | import optype as op 8 | 9 | 10 | # fmt: off 11 | class A: ... 12 | class B(A): ... # noqa: E302 13 | class C(B): ... # noqa: E302 14 | # fmt: on 15 | 16 | 17 | @pytest.mark.parametrize( 18 | ("just_cls", "cls"), 19 | [ 20 | (op.JustBytes, bytes), 21 | (op.JustInt, int), 22 | (op.JustFloat, float), 23 | (op.JustComplex, complex), 24 | (op.JustObject, object), 25 | (op.JustDate, dt.date), 26 | ], 27 | ) 28 | def test_just_sub_meta(just_cls: type, cls: type) -> None: 29 | obj = cls.today() if cls is dt.date else cls() # type: ignore[attr-defined] 30 | assert isinstance(obj, just_cls) 31 | assert not isinstance(bool(), just_cls) # noqa: UP018 32 | assert not isinstance(cls, just_cls) 33 | 34 | assert issubclass(cls, just_cls) 35 | assert not issubclass(bool, just_cls) 36 | assert not issubclass(type, just_cls) 37 | 38 | assert issubclass(op.Just[cls], just_cls) # type: ignore[valid-type] 39 | assert not issubclass(op.Just[bool], just_cls) 40 | assert not issubclass(op.Just[type], just_cls) 41 | 42 | 43 | def test_just_custom() -> None: 44 | a, b, c = A(), B(), C() 45 | 46 | b_b: op.Just[B] = b 47 | b_a: op.Just[B] = a # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 48 | b_c: op.Just[B] = c # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 49 | 50 | 51 | def test_just_object() -> None: 52 | tn_object1: op.Just[object] = object() 53 | tn_object2: op.JustObject = object() 54 | tp_custom1: op.Just[object] = A() # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 55 | tp_custom2: op.JustObject = A() # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 56 | 57 | tn_object_type1: type[op.Just[object]] = object 58 | tn_object_type2: type[op.JustObject] = object 59 | tp_custom_type1: type[op.Just[object]] = A # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 60 | tp_custom_type2: type[op.JustObject] = A # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 61 | 62 | 63 | def test_just_int() -> None: 64 | # instance assignment: true negatives 65 | tn_int: op.JustInt = int("42") 66 | tn_int_literal: op.JustInt = 42 67 | 68 | # instance assignment: true positives 69 | tp_bool: op.JustInt = bool([]) # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 70 | tp_true: op.JustInt = True # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 71 | 72 | # type assignment 73 | tn_int_type: type[op.JustInt] = int 74 | tp_int_type: type[op.JustInt] = bool # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 75 | 76 | # docstring example 77 | def f(x: op.JustInt, /) -> int: 78 | assert type(x) is int 79 | return x 80 | 81 | def g() -> None: # pyright: ignore[reportUnusedFunction] 82 | f(1337) # accepted 83 | f(True) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] 84 | 85 | 86 | def test_just_float() -> None: 87 | tn_float: op.JustFloat = float("inf") 88 | tp_int: op.JustFloat = int("42") # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 89 | tp_int_literal: op.JustFloat = 42 # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 90 | tp_bool: op.JustFloat = bool([]) # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 91 | tp_bool_literal: op.JustFloat = True # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 92 | 93 | tn_float_type: type[op.JustFloat] = float 94 | tp_str_type: type[op.JustFloat] = str # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 95 | tp_int_type: type[op.JustFloat] = int # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 96 | tp_bool_type: type[op.JustFloat] = bool # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 97 | 98 | 99 | def test_just_complex() -> None: 100 | tn_complex: op.JustComplex = complex("inf") 101 | tp_float: op.JustComplex = float("inf") # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 102 | 103 | tn_complex_type: type[op.JustComplex] = complex 104 | tp_str_type: type[op.JustComplex] = str # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 105 | tp_float_type: type[op.JustComplex] = float # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 106 | 107 | 108 | def test_just_bytes() -> None: 109 | tn_bytes: op.JustBytes = b"yes" 110 | tp_str: op.JustBytes = "no" # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 111 | tp_bytearray: op.JustBytes = bytearray(b"no") # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 112 | tp_memoryview: op.JustBytes = memoryview(b"no") # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 113 | 114 | tn_bytes_type: type[op.JustBytes] = bytes 115 | tp_str_type: type[op.JustBytes] = str # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 116 | tp_bytearray_type: type[op.JustBytes] = bytearray # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 117 | tp_memoryview_type: type[op.JustBytes] = memoryview # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 118 | 119 | 120 | def test_just_date() -> None: 121 | tn_date: op.JustDate = dt.date.today() 122 | tp_datetime: op.JustDate = dt.datetime.now() # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 123 | 124 | tn_date_type: type[op.JustDate] = dt.date 125 | tp_datetime_type: type[op.JustDate] = dt.datetime # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 126 | -------------------------------------------------------------------------------- /tests/core/test_protocols.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from typing import cast 3 | 4 | if sys.version_info >= (3, 13): 5 | from typing import is_protocol 6 | else: 7 | from typing_extensions import is_protocol 8 | 9 | import pytest 10 | 11 | import optype as op 12 | from optype import _utils 13 | from optype._core import _can, _do, _does, _has, _just 14 | from optype.inspect import ( 15 | get_protocol_members, 16 | get_protocols, 17 | is_protocol, 18 | is_runtime_protocol, 19 | ) 20 | 21 | 22 | def _is_dunder(name: str, /) -> bool: 23 | """Whether the name is a valid `__dunder_name__`.""" 24 | return ( 25 | len(name) > 4 26 | and name[:2] == name[-2:] == "__" 27 | and name[2] != "_" 28 | and name[-3] != "_" 29 | and name[2:-2].isidentifier() 30 | and (name.islower() or name.isupper()) 31 | ) 32 | 33 | 34 | def _pascamel_to_snake( 35 | pascamel: str, 36 | start: op.CanLt[int, op.CanBool] = 0, 37 | /, 38 | ) -> str: 39 | """Converts 'CamelCase' or 'pascalCase' to 'snake_case'.""" 40 | assert pascamel.isidentifier() 41 | 42 | snake = "".join( 43 | f"_{char}" if i > start and char.isupper() else char 44 | for i, char in enumerate(pascamel) 45 | ).lower() 46 | assert snake.isidentifier() 47 | assert snake[0] != "_" 48 | assert snake[-1] != "_" 49 | 50 | return snake 51 | 52 | 53 | def test_all_public() -> None: 54 | """ 55 | Verify that all of protocols from the private `_core.*` submodules are expected 56 | in `optype.__all__`. 57 | """ 58 | protos_all = get_protocols(op) 59 | protos_can = get_protocols(_can) 60 | protos_has = get_protocols(_has) 61 | protos_does = get_protocols(_does) 62 | protos_just = get_protocols(_just) 63 | 64 | assert protos_can | protos_has | protos_does | protos_just == protos_all 65 | 66 | 67 | @pytest.mark.parametrize("cls", get_protocols(_can)) 68 | def test_can_runtime_checkable(cls: type) -> None: 69 | """Ensure that all `Can*` protocols are `@runtime_checkable`.""" 70 | assert is_runtime_protocol(cls) 71 | 72 | 73 | @pytest.mark.parametrize("cls", get_protocols(_has)) 74 | def test_has_runtime_checkable(cls: type) -> None: 75 | """Ensure that all `Has*` protocols are `@runtime_checkable`.""" 76 | assert is_runtime_protocol(cls) 77 | 78 | 79 | @pytest.mark.parametrize("cls", get_protocols(_does)) 80 | def test_does_not_runtime_checkable(cls: type) -> None: 81 | """Ensure that all `Does*` protocols are **not** `@runtime_checkable`.""" 82 | assert not is_runtime_protocol(cls) 83 | 84 | 85 | def test_num_does_eq_num_do() -> None: 86 | num_does = len(set_does := get_protocols(_does)) 87 | num_do = len(set_do := _utils.get_callables(_do)) 88 | 89 | assert not {t.__name__ for t in set_does if not t.__name__.startswith("Does")} 90 | assert not {k for k in set_do if not k.startswith("do_")} 91 | 92 | assert num_does == num_do, {k[3:] for k in set_do} - { 93 | t.__name__[4:].lower() for t in set_does 94 | } 95 | 96 | 97 | @pytest.mark.parametrize("cls", get_protocols(_does)) 98 | def test_does_has_do(cls: type) -> None: 99 | """Ensure that all `Does*` protocols have a corresponding `do_` op.""" 100 | name = cls.__name__.removeprefix("Does") 101 | assert name != cls.__name__ 102 | 103 | do_name = f"do_{_pascamel_to_snake(name, 1)}" 104 | do_op = cast("op.CanCall[..., object] | None", getattr(_do, do_name, None)) 105 | assert do_op is not None, do_name 106 | assert callable(do_op), do_name 107 | 108 | 109 | @pytest.mark.parametrize("cls", get_protocols(_can) | get_protocols(_has)) 110 | def test_name_matches_dunder(cls: type) -> None: 111 | """ 112 | Ensure that each single-member `Can*` and `Has*` name matches the name of 113 | its member, and that each multi-member optype does not have more members 114 | than it has super optypes. I.e. require at most 1 member (attr, prop or 115 | method) for each **concrete** Protocol. 116 | """ 117 | assert cls.__module__ == "optype" 118 | 119 | prefix = cls.__qualname__[:3] 120 | assert prefix in {"Can", "Has"} 121 | 122 | name = cls.__name__ 123 | assert name.startswith(prefix) 124 | 125 | members = get_protocol_members(cls) 126 | assert members 127 | 128 | own_members: frozenset[str] 129 | parents = [ 130 | parent 131 | for parent in cls.mro()[1:] 132 | if not parent.__name__.endswith("Self") 133 | and is_protocol(parent) 134 | ] # fmt: skip 135 | if parents: 136 | overridden = { 137 | member 138 | for member in members 139 | if callable(f := getattr(cls, member)) and getattr(f, "__override__", False) 140 | } 141 | own_members = members - overridden 142 | else: 143 | own_members = members 144 | 145 | member_count = len(members) 146 | own_member_count = len(own_members) 147 | 148 | # this test should probably be split up... 149 | 150 | if member_count > min(1, own_member_count): 151 | # ensure len(parent protocols) == len(members) (including inherited) 152 | assert member_count == len(parents), own_members 153 | 154 | members_concrete = set(members) 155 | for parent in parents: 156 | members_concrete.difference_update(get_protocol_members(parent)) 157 | 158 | assert not members_concrete 159 | else: 160 | # remove the `Can`, `Has`, or `Does` prefix 161 | stem = name.removeprefix(prefix) 162 | # strip the arity digit if exists 163 | if stem[-1].isdigit(): 164 | stem = stem[:-1] 165 | assert stem[-1].isalpha() 166 | 167 | # the `1` arg ensures that any potential leading `A`, `I` or `R` chars 168 | # won't have a `_` directly after (i.e. considers `stem[:2].lower()`). 169 | member_predict = _pascamel_to_snake(stem, 1) 170 | member_expect = next(iter(members)) 171 | 172 | # prevent comparing apples with oranges: paint the apples orange! 173 | if _is_dunder(member_expect): 174 | member_predict = f"__{member_predict}__" 175 | 176 | assert member_predict == member_expect 177 | -------------------------------------------------------------------------------- /tests/numpy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorenham/optype/cbd461192152db506b6a27a5342f9ae217ba1e49/tests/numpy/__init__.py -------------------------------------------------------------------------------- /tests/numpy/test_any_array.py: -------------------------------------------------------------------------------- 1 | import ctypes as ct 2 | import datetime as dt 3 | from collections import deque 4 | from typing import Final 5 | 6 | import numpy as np 7 | import pytest 8 | 9 | import optype.numpy as onp # noqa: TC001 10 | from optype.numpy import _compat as _x, _scalar as _sc, ctypeslib as _ct 11 | 12 | # All allowed arguments that when passed to `np.array`, will result in an 13 | # array of the specified scalar type(s). 14 | 15 | _UNSIGNED_INTEGER_NP: Final = ( 16 | np.uint8, np.uint16, np.uint32, np.uint64, np.uintp, 17 | np.ubyte, np.ushort, np.uintc, _x.ULong, np.ulonglong, 18 | ) # fmt: skip 19 | _UNSIGNED_INTEGER_CT: Final = ( 20 | ct.c_uint8, ct.c_uint16, ct.c_uint32, ct.c_uint64, ct.c_size_t, 21 | ct.c_ubyte, ct.c_ushort, ct.c_uint, ct.c_ulong, ct.c_ulonglong, 22 | ) # fmt: skip 23 | UNSIGNED_INTEGER: Final = *_UNSIGNED_INTEGER_NP, *_UNSIGNED_INTEGER_CT 24 | _SIGNED_INTEGER_NP: Final = ( 25 | np.int8, np.int16, np.int32, np.int64, np.intp, 26 | np.byte, np.short, np.intc, _x.Long, np.longlong, 27 | ) # fmt: skip 28 | _SIGNED_INTEGER_CT: Final = ( 29 | ct.c_int8, ct.c_int16, ct.c_int32, ct.c_int64, ct.c_ssize_t, 30 | ct.c_byte, ct.c_short, ct.c_int, ct.c_long, ct.c_longlong, 31 | ) # fmt: skip 32 | SIGNED_INTEGER: Final = *_SIGNED_INTEGER_NP, *_SIGNED_INTEGER_CT 33 | INTEGER: Final = *UNSIGNED_INTEGER, *SIGNED_INTEGER 34 | 35 | _FLOATING_NP: Final = ( 36 | np.float16, np.float32, np.float64, 37 | np.half, np.single, np.double, np.longdouble, 38 | ) # fmt: skip 39 | _FLOATING_CT: Final = ct.c_float, ct.c_double 40 | FLOATING: Final = *_FLOATING_NP, *_FLOATING_CT 41 | COMPLEX_FLOATING: Final = ( 42 | np.complex64, np.complex128, np.csingle, np.cdouble, np.clongdouble, 43 | ) # fmt: skip 44 | DATETIME64: Final = np.datetime64, dt.datetime 45 | TIMEDELTA64: Final = np.timedelta64, dt.timedelta 46 | STR: Final = np.str_, str 47 | BYTES: Final = np.bytes_, ct.c_char, bytes 48 | CHARACTER: Final = *STR, *BYTES 49 | VOID: Final = (np.void,) # TODO: structured 50 | FLEXIBLE: Final = *VOID, *CHARACTER 51 | BOOL: Final = _x.Bool, ct.c_bool, bool 52 | OBJECT: Final = np.object_, ct.py_object 53 | 54 | 55 | def _shape(a: object, /) -> tuple[int, ...]: 56 | # backport of `np.shape` that also works on nunmpy<2 or something 57 | return np.asarray(a).shape 58 | 59 | 60 | def test_any_array() -> None: 61 | type_np = np.int16 62 | type_ct = ct.c_int16 63 | v = 42 64 | 65 | v_empty_list: onp.AnyArray = [] 66 | v_empty_tuple: onp.AnyArray = () 67 | v_empty_deque: onp.AnyArray = deque(()) # noqa: RUF037 68 | v_empty_bytearray: onp.AnyArray = bytearray(b"") 69 | v_empty_memoryview: onp.AnyArray = memoryview(b"") 70 | # rejection 71 | v_bool: onp.AnyArray = False # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 72 | v_int: onp.AnyArray = 0 # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 73 | v_float: onp.AnyArray = 0.0 # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 74 | v_complex: onp.AnyArray = 0j # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 75 | v_empty_set: onp.AnyArray = set() # type: ignore[assignment] # pyright: ignore[reportAssignmentType, reportUnknownVariableType] 76 | v_dict: onp.AnyArray = {} # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 77 | 78 | # NumPy scalar 79 | 80 | v_np = type_np(v) 81 | v_np_any: onp.AnyArray = [v_np] 82 | assert _shape(v_np) == () 83 | 84 | # 0d 85 | 86 | x0_np = np.array(v) 87 | x0_np_any: onp.AnyArray = x0_np 88 | assert _shape(x0_np) == () 89 | 90 | # 1d 91 | 92 | x1_py: list[int] = [v] 93 | x1_py_any: onp.AnyArray = x1_py 94 | assert _shape(x1_py) == (1,) 95 | 96 | x1_py_np = [x0_np] 97 | x1_py_np_any: onp.AnyArray = x1_py_np 98 | assert _shape(x1_py_np) == (1,) 99 | 100 | x1_np = np.array(x1_py) 101 | x1_np_any: onp.AnyArray = x1_np 102 | assert _shape(x1_np) == (1,) 103 | 104 | # 2d 105 | 106 | x2_py = [x1_py] 107 | x2_py_any: onp.AnyArray = x2_py 108 | assert _shape(x2_py) == (1, 1) 109 | 110 | x2_py_np = [x1_np] 111 | x2_py_np_any: onp.AnyArray = x2_py_np 112 | assert _shape(x2_py_np) == (1, 1) 113 | 114 | x2_np = np.array(x2_py) 115 | x2_np_any: onp.AnyArray = x2_np 116 | assert _shape(x2_np) == (1, 1) 117 | 118 | 119 | @pytest.mark.parametrize("sctype", UNSIGNED_INTEGER) 120 | def test_any_unsigned_integer_array( 121 | sctype: type[_sc.uinteger | _ct.UnsignedInteger], 122 | ) -> None: 123 | x = np.array(sctype(42)) 124 | x_any: onp.AnyUnsignedIntegerArray = x 125 | assert np.issubdtype(x.dtype, np.unsignedinteger) 126 | 127 | 128 | @pytest.mark.parametrize("sctype", SIGNED_INTEGER) 129 | def test_any_signed_integer_array( 130 | sctype: type[_sc.sinteger | _ct.SignedInteger], 131 | ) -> None: 132 | x = np.array(sctype(42)) 133 | x_any: onp.AnySignedIntegerArray = x 134 | assert np.issubdtype(x.dtype, np.signedinteger) 135 | 136 | 137 | @pytest.mark.parametrize("sctype", INTEGER) 138 | def test_any_integer_array(sctype: type[_sc.integer | _ct.Integer]) -> None: 139 | x = np.array(sctype(42)) 140 | x_any: onp.AnyIntegerArray = x 141 | assert np.issubdtype(x.dtype, np.integer) 142 | 143 | 144 | @pytest.mark.parametrize("sctype", FLOATING) 145 | def test_any_floating_array(sctype: type[_sc.floating | _ct.Floating]) -> None: 146 | x = np.array(sctype(42)) 147 | x_any: onp.AnyFloatingArray = x 148 | assert np.issubdtype(x.dtype, np.floating) 149 | 150 | 151 | @pytest.mark.parametrize("sctype", COMPLEX_FLOATING) 152 | def test_any_complex_floating_array(sctype: type[_sc.cfloating]) -> None: 153 | x = np.array(sctype(42 + 42j)) 154 | x_any: onp.AnyComplexFloatingArray = x 155 | assert np.issubdtype(x.dtype, np.complexfloating) 156 | 157 | 158 | def test_any_datetime_array() -> None: 159 | v = dt.datetime.now() # noqa: DTZ005 160 | x = np.array(np.datetime64(v)) 161 | x_any: onp.AnyDateTime64Array = x 162 | assert np.issubdtype(x.dtype, np.datetime64) 163 | 164 | 165 | def test_any_datetime64_array() -> None: 166 | x = np.array(np.datetime64("now")) 167 | x_any: onp.AnyDateTime64Array = x 168 | assert np.issubdtype(x.dtype, np.datetime64) 169 | 170 | 171 | def test_any_timedelta_array() -> None: 172 | v = dt.timedelta(0) 173 | x = np.array(np.timedelta64(v)) 174 | x_any: onp.AnyTimeDelta64Array = x 175 | assert np.issubdtype(x.dtype, np.timedelta64) 176 | 177 | 178 | def test_any_timedelta64_array() -> None: 179 | x = np.array(np.timedelta64(0)) 180 | x_any: onp.AnyTimeDelta64Array = x 181 | assert np.issubdtype(x.dtype, np.timedelta64) 182 | 183 | 184 | @pytest.mark.parametrize("sctype", STR) 185 | def test_any_str_array(sctype: type[str | np.str_]) -> None: 186 | x = np.array(sctype()) 187 | x_any: onp.AnyStrArray = x 188 | assert np.issubdtype(x.dtype, np.str_) 189 | 190 | 191 | @pytest.mark.parametrize("sctype", BYTES) 192 | def test_any_bytes_array(sctype: type[bytes | np.bytes_ | _ct.Bytes]) -> None: 193 | x = np.array(sctype()) 194 | x_any: onp.AnyBytesArray = x 195 | assert np.issubdtype(x.dtype, np.bytes_) 196 | 197 | 198 | @pytest.mark.parametrize("sctype", CHARACTER) 199 | def test_any_character_array( 200 | sctype: type[str | bytes | np.character | _ct.Bytes], 201 | ) -> None: 202 | x = np.array(sctype()) 203 | x_any: onp.AnyCharacterArray = x 204 | assert np.issubdtype(x.dtype, np.character) 205 | 206 | 207 | @pytest.mark.parametrize("sctype", VOID) 208 | def test_any_void_array(sctype: type[memoryview | np.void]) -> None: 209 | x = np.array(sctype(b"")) 210 | x_any: onp.AnyVoidArray = x 211 | assert np.issubdtype(x.dtype, np.void) 212 | 213 | 214 | @pytest.mark.parametrize("sctype", FLEXIBLE) 215 | def test_any_flexible_array( 216 | sctype: type[bytes | str | np.flexible | _ct.Flexible], 217 | ) -> None: 218 | x = np.array(sctype(0)) 219 | x_any: onp.AnyFlexibleArray = x 220 | assert np.issubdtype(x.dtype, np.flexible) 221 | 222 | 223 | @pytest.mark.parametrize("sctype", BOOL) 224 | def test_any_bool_array(sctype: type[bool | np.bool_ | _ct.Bool]) -> None: 225 | x = np.array(sctype(True)) 226 | x_any: onp.AnyBoolArray = x 227 | assert np.issubdtype(x.dtype, np.bool_) 228 | 229 | 230 | @pytest.mark.parametrize("sctype", OBJECT) 231 | def test_any_object_array(sctype: type[np.object_ | _ct.Object]) -> None: 232 | x = np.array(sctype()) 233 | x_any: onp.AnyObjectArray = x 234 | assert np.issubdtype(x.dtype, np.object_) 235 | -------------------------------------------------------------------------------- /tests/numpy/test_any_dtype.py: -------------------------------------------------------------------------------- 1 | import types 2 | from typing import Final, Never 3 | 4 | import numpy as np 5 | import pytest 6 | 7 | from optype.numpy import _dtype_attr 8 | 9 | _DTYPES: Final = ( 10 | np.dtype(np.bool_), 11 | np.dtype(np.int8), 12 | np.dtype(np.int16), 13 | np.dtype(np.int32), 14 | np.dtype(np.int64), 15 | np.dtype(np.intc), 16 | np.dtype(np.intp), 17 | np.dtype(np.int_), 18 | np.dtype(np.longlong), 19 | np.dtype(np.uint8), 20 | np.dtype(np.uint16), 21 | np.dtype(np.uint32), 22 | np.dtype(np.uint64), 23 | np.dtype(np.uintc), 24 | np.dtype(np.uintp), 25 | np.dtype(np.uint), 26 | np.dtype(np.ulonglong), 27 | np.dtype(np.float16), 28 | np.dtype(np.float32), 29 | np.dtype(np.float64), 30 | np.dtype(np.longdouble), 31 | np.dtype(np.complex64), 32 | np.dtype(np.complex128), 33 | np.dtype(np.clongdouble), 34 | np.dtype(np.object_), 35 | np.dtype(np.bytes_), 36 | np.dtype("c"), 37 | np.dtype(np.str_), 38 | np.dtype(np.void), 39 | np.dtype(np.datetime64), 40 | np.dtype(np.timedelta64), 41 | # `StringDType` is too broken too test at the moment 42 | ) 43 | _TIME_UNITS: Final = "as", "fs", "ps", "ns", "us", "s", "m", "h", "D", "W", "M", "Y" 44 | 45 | 46 | def _get_dtype_codes( 47 | dtype: np.dtype[np.generic], 48 | module: types.ModuleType = _dtype_attr, 49 | ) -> tuple[frozenset[str], frozenset[str]]: 50 | try: 51 | strcode = dtype.str[1:] 52 | literal_name = getattr(module, f"{strcode}_name") 53 | literal_char = getattr(module, f"{strcode}_char") 54 | except AttributeError: 55 | literal_name = getattr(module, f"{dtype.char}_name") 56 | literal_char = getattr(module, f"{dtype.char}_char") 57 | 58 | names = frozenset(() if literal_name is Never else literal_name.__args__) 59 | chars = frozenset(() if literal_char is Never else literal_char.__args__) 60 | return names, chars 61 | 62 | 63 | def _normalized_dtype_name(dtype: np.dtype[np.generic]) -> str: 64 | return dtype.name.split("[")[0] # avoid e.g. "datetime64[ns]" 65 | 66 | 67 | @pytest.mark.parametrize( 68 | ("dtype", "names", "chars"), 69 | [(dtype, *_get_dtype_codes(dtype)) for dtype in _DTYPES], 70 | ) 71 | def test_dtype_has_codes( 72 | dtype: np.dtype[np.generic], 73 | names: frozenset[str], 74 | chars: frozenset[str], 75 | ) -> None: 76 | name = dtype.name 77 | 78 | assert name in names, (name, names) 79 | assert dtype.str in chars, (dtype.str, chars) 80 | assert dtype.str[1:] in chars, (dtype.str, chars) 81 | 82 | codes = names | chars 83 | out_names: set[str] = set() 84 | for code in codes: 85 | try: 86 | dtype_ = np.dtype(code) 87 | except TypeError: 88 | continue 89 | 90 | out_names.add(_normalized_dtype_name(dtype_)) 91 | 92 | assert len(out_names) == 1 93 | 94 | 95 | @pytest.mark.parametrize( 96 | ("dtype", "names", "chars"), 97 | [ 98 | (dtype, *_get_dtype_codes(dtype)) 99 | for dtype in [np.dtype(np.datetime64), np.dtype(np.timedelta64)] 100 | ], 101 | ) 102 | @pytest.mark.parametrize("unit", _TIME_UNITS) 103 | def test_time_units( 104 | unit: str, 105 | dtype: np.dtype[np.datetime64] | np.dtype[np.timedelta64], 106 | names: frozenset[str], 107 | chars: frozenset[str], 108 | ) -> None: 109 | name = f"{dtype.name}[{unit}]" 110 | str_ = f"{dtype.str}[{unit}]" 111 | 112 | assert name in names, (name, names) 113 | assert str_ in chars, (str_, chars) 114 | -------------------------------------------------------------------------------- /tests/numpy/test_array.py: -------------------------------------------------------------------------------- 1 | # pyright: reportUnnecessaryCast=false 2 | 3 | from collections.abc import Callable 4 | from typing import TypeAlias, TypeVar, cast 5 | 6 | import numpy as np 7 | import pytest 8 | 9 | import optype.numpy as onp 10 | 11 | _Shape0D: TypeAlias = tuple[()] 12 | _Shape1D: TypeAlias = tuple[int] 13 | _Shape2D: TypeAlias = tuple[int, int] 14 | 15 | _AnyCallable: TypeAlias = Callable[[], object] 16 | 17 | 18 | # Don't wake up, Neo... 19 | @pytest.mark.filterwarnings("ignore:the matrix .*:PendingDeprecationWarning") 20 | def test_can_array() -> None: 21 | dt = np.dtype(np.int8) 22 | 23 | val_x = dt.type(42) 24 | val_y: onp.CanArray[_Shape0D, np.dtype[np.int8]] = val_x 25 | assert isinstance(val_x, onp.CanArray) 26 | assert not isinstance(val_x.tolist(), onp.CanArray) 27 | 28 | nd0_x = np.empty((), dt) 29 | nd0_y: onp.CanArray[_Shape0D, np.dtype[np.int8]] = nd0_x 30 | assert isinstance(nd0_x, onp.CanArray) 31 | 32 | nd1_x = cast("np.ndarray[_Shape1D, np.dtype[np.int8]]", np.empty((42,), dt)) 33 | nd1_y: onp.CanArray[_Shape1D, np.dtype[np.int8]] = nd1_x 34 | assert isinstance(nd1_x, onp.CanArray) 35 | assert not isinstance(nd1_x.tolist(), onp.CanArray) 36 | 37 | nd2_x = cast( 38 | "np.ndarray[_Shape2D, np.dtype[np.int8]]", 39 | cast("object", np.empty((6, 7), dt)), 40 | ) 41 | nd2_y: onp.CanArray[_Shape2D, np.dtype[np.int8]] = nd2_x 42 | assert isinstance(nd2_x, onp.CanArray) 43 | assert not isinstance(nd2_x.tolist(), onp.CanArray) 44 | 45 | mat_x = np.matrix([[1, 0], [0, 1]], dt) 46 | mat_y: onp.CanArray[_Shape2D, np.dtype[np.int8]] = mat_x 47 | assert isinstance(mat_x, onp.CanArray) 48 | 49 | 50 | _T = TypeVar("_T", bound=np.generic) 51 | _Arr0D: TypeAlias = onp.Array[tuple[()], _T] 52 | _Arr1D: TypeAlias = onp.Array[tuple[int], _T] 53 | _Arr2D: TypeAlias = onp.Array[tuple[int, int], _T] 54 | 55 | 56 | def test_can_array_function() -> None: 57 | sct: type[np.generic] = np.uint8 58 | 59 | assert not isinstance(sct(42), onp.CanArrayFunction) 60 | 61 | arr_0d: onp.CanArrayFunction[_AnyCallable, _Arr0D[np.uint8]] = np.array(42, sct) 62 | assert isinstance(arr_0d, onp.CanArrayFunction) 63 | 64 | arr_1d: onp.CanArrayFunction[_AnyCallable, _Arr1D[np.uint8]] = np.array([42], sct) 65 | assert isinstance(arr_1d, onp.CanArrayFunction) 66 | 67 | arr_2d: onp.CanArrayFunction[_AnyCallable, _Arr2D[np.uint8]] = np.array([[42]], sct) 68 | assert isinstance(arr_2d, onp.CanArrayFunction) 69 | 70 | 71 | def test_can_array_finalize() -> None: 72 | sct: type[np.generic] = np.uint8 73 | 74 | arr_0d: onp.CanArrayFinalize[_Arr0D[np.uint8]] = np.array(42, sct) 75 | assert isinstance(arr_0d, onp.CanArrayFinalize) 76 | assert not isinstance(42, onp.CanArrayFinalize) 77 | 78 | arr_1d: onp.CanArrayFinalize[_Arr1D[np.uint8]] = np.array([42], sct) 79 | assert isinstance(arr_1d, onp.CanArrayFinalize) 80 | assert not isinstance([42], onp.CanArrayFinalize) 81 | 82 | arr_2d: onp.CanArrayFinalize[_Arr2D[np.uint8]] = np.array([[42]], sct) 83 | assert isinstance(arr_2d, onp.CanArrayFinalize) 84 | 85 | 86 | def test_can_array_wrap() -> None: 87 | sct: type[np.generic] = np.uint8 88 | 89 | arr_0d: onp.CanArrayWrap = np.array(42, sct) 90 | assert isinstance(arr_0d, onp.CanArrayWrap) 91 | assert not isinstance(42, onp.CanArrayWrap) 92 | 93 | arr_1d: onp.CanArrayWrap = np.array([42], sct) 94 | assert isinstance(arr_1d, onp.CanArrayWrap) 95 | assert not isinstance([42], onp.CanArrayWrap) 96 | 97 | arr_2d: onp.CanArrayWrap = np.array([[42]], sct) 98 | assert isinstance(arr_2d, onp.CanArrayWrap) 99 | 100 | 101 | def test_has_array_priority() -> None: 102 | sct: type[np.generic] = np.uint8 103 | 104 | scalar: onp.HasArrayPriority = sct(42) 105 | assert isinstance(scalar, onp.HasArrayPriority) 106 | 107 | arr_0d: onp.HasArrayPriority = np.array(42, sct) 108 | assert isinstance(arr_0d, onp.HasArrayPriority) 109 | assert not isinstance(42, onp.HasArrayPriority) 110 | 111 | arr_1d: onp.HasArrayPriority = np.array([42], sct) 112 | assert isinstance(arr_1d, onp.HasArrayPriority) 113 | assert not isinstance([42], onp.HasArrayPriority) 114 | 115 | arr_2d: onp.HasArrayPriority = np.array([[42]], sct) 116 | assert isinstance(arr_2d, onp.HasArrayPriority) 117 | 118 | 119 | def test_has_array_interface() -> None: 120 | sct: type[np.generic] = np.uint8 121 | 122 | scalar: onp.HasArrayInterface = sct(42) 123 | assert isinstance(scalar, onp.HasArrayInterface) 124 | 125 | arr_0d: onp.HasArrayInterface = np.array(42, sct) 126 | assert isinstance(arr_0d, onp.HasArrayInterface) 127 | assert not isinstance(42, onp.HasArrayInterface) 128 | 129 | arr_1d: onp.HasArrayInterface = np.array([42], sct) 130 | assert isinstance(arr_1d, onp.HasArrayInterface) 131 | assert not isinstance([42], onp.HasArrayInterface) 132 | 133 | arr_2d: onp.HasArrayInterface = np.array([[42]], sct) 134 | assert isinstance(arr_2d, onp.HasArrayInterface) 135 | -------------------------------------------------------------------------------- /tests/numpy/test_is.py: -------------------------------------------------------------------------------- 1 | # pyright: reportUnknownArgumentType=false 2 | # pyright: reportUnknownLambdaType=false 3 | # pyright: reportUnknownMemberType=false 4 | import operator 5 | from collections.abc import Callable 6 | 7 | import numpy as np 8 | import pytest 9 | 10 | import optype.numpy as onp 11 | 12 | if np.__version__ >= "2": 13 | CHARS = "?BbHhIiLlQqNnPpefdgFDGSUVOMm" 14 | else: 15 | CHARS = "?BbHhIiLlQqPpefdgFDGSUVOMm" # pyright: ignore[reportConstantRedefinition] 16 | 17 | DTYPES = [np.dtype(char) for char in CHARS] 18 | SCTYPES = {dtype.type for dtype in DTYPES if issubclass(dtype.type, np.generic)} 19 | NDARRAY_TYPES = np.ndarray, np.ma.MaskedArray 20 | 21 | 22 | @pytest.mark.parametrize("dtype", DTYPES, ids=str) 23 | def test_is_dtype(dtype: onp.DType) -> None: 24 | assert onp.is_dtype(dtype) 25 | assert not onp.is_dtype(type(dtype)) 26 | assert not onp.is_dtype(dtype.type) 27 | assert not onp.is_dtype(np.empty((), dtype)) 28 | assert not onp.is_dtype(np.empty((), dtype).item()) 29 | 30 | 31 | @pytest.mark.parametrize("sctype", SCTYPES, ids=operator.attrgetter("__name__")) 32 | def test_is_sctype(sctype: type[np.generic]) -> None: 33 | assert onp.is_sctype(sctype) 34 | assert not onp.is_sctype(np.empty((), sctype)) 35 | assert not onp.is_sctype(np.empty((), sctype).item()) 36 | assert not onp.is_sctype(np.dtype(sctype)) 37 | 38 | 39 | @pytest.mark.parametrize( 40 | "dtype_map", 41 | [ 42 | np.dtype, 43 | operator.attrgetter("type"), 44 | lambda dtype: dtype.type.mro()[1], 45 | lambda dtype: dtype.type.mro()[-2], 46 | ], 47 | ids=["_", "_.type", "_.type.mro()[1]", "_.type.mro()[-2]"], 48 | ) 49 | @pytest.mark.parametrize("dtype", DTYPES, ids=str) 50 | @pytest.mark.parametrize("ndtype", NDARRAY_TYPES) 51 | def test_is_array( 52 | ndtype: type[onp.Array], 53 | dtype: onp.DType, 54 | dtype_map: Callable[[onp.DType], onp.DType | type[np.generic]], 55 | ) -> None: 56 | arr = [np.empty(tuple(range(ndim)), dtype).view(ndtype) for ndim in range(5)] 57 | 58 | assert onp.is_array_nd(arr[0]) 59 | assert onp.is_array_0d(arr[0]) 60 | assert not onp.is_array_1d(arr[0]) 61 | assert not onp.is_array_2d(arr[0]) 62 | assert not onp.is_array_3d(arr[0]) 63 | 64 | assert onp.is_array_nd(arr[1]) 65 | assert not onp.is_array_0d(arr[1]) 66 | assert onp.is_array_1d(arr[1]) 67 | assert not onp.is_array_2d(arr[1]) 68 | assert not onp.is_array_3d(arr[1]) 69 | 70 | assert onp.is_array_nd(arr[2]) 71 | assert not onp.is_array_0d(arr[2]) 72 | assert not onp.is_array_1d(arr[2]) 73 | assert onp.is_array_2d(arr[2]) 74 | assert not onp.is_array_3d(arr[2]) 75 | 76 | assert onp.is_array_nd(arr[2].view(np.matrix)) 77 | assert not onp.is_array_0d(arr[2].view(np.matrix)) 78 | assert not onp.is_array_1d(arr[2].view(np.matrix)) 79 | assert onp.is_array_2d(arr[2].view(np.matrix)) 80 | assert not onp.is_array_3d(arr[2].view(np.matrix)) 81 | 82 | assert onp.is_array_nd(arr[3]) 83 | assert not onp.is_array_0d(arr[3]) 84 | assert not onp.is_array_1d(arr[3]) 85 | assert not onp.is_array_2d(arr[3]) 86 | assert onp.is_array_3d(arr[3]) 87 | 88 | assert onp.is_array_nd(arr[4]) 89 | assert not onp.is_array_0d(arr[4]) 90 | assert not onp.is_array_1d(arr[4]) 91 | assert not onp.is_array_2d(arr[4]) 92 | assert not onp.is_array_3d(arr[4]) 93 | 94 | dtype_is = dtype_map(dtype) 95 | assert onp.is_array_0d(arr[0], dtype=dtype_is) 96 | assert onp.is_array_1d(arr[1], dtype=dtype_is) 97 | assert onp.is_array_2d(arr[2], dtype=dtype_is) 98 | assert onp.is_array_3d(arr[3], dtype=dtype_is) 99 | assert onp.is_array_nd(arr[4], dtype=dtype_is) 100 | 101 | dtype_not = np.dtype("?") if dtype.char != "?" else np.dtype("B") 102 | assert not onp.is_array_0d(arr[0], dtype=dtype_not) 103 | assert not onp.is_array_1d(arr[1], dtype=dtype_not) 104 | assert not onp.is_array_2d(arr[2], dtype=dtype_not) 105 | assert not onp.is_array_3d(arr[3], dtype=dtype_not) 106 | assert not onp.is_array_nd(arr[4], dtype=dtype_not) 107 | -------------------------------------------------------------------------------- /tests/numpy/test_scalar.py: -------------------------------------------------------------------------------- 1 | # mypy: disable-error-code="unreachable" 2 | from typing import Literal, assert_type 3 | 4 | import numpy as np 5 | import pytest 6 | 7 | from optype.numpy import Scalar, _scalar as _sc 8 | 9 | NP2 = np.__version__.startswith("2.") 10 | 11 | 12 | def test_from_bool() -> None: 13 | x_py = True 14 | x_np = np.True_ 15 | 16 | s_py: Scalar[bool] = x_py # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 17 | assert not isinstance(x_py, Scalar) 18 | 19 | s_np_n: Scalar[bool] = x_np 20 | s_np_1: Scalar[bool, Literal[1]] = x_np 21 | 22 | s_np_wrong_n: Scalar[str] = x_np # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 23 | s_np_wrong_1: Scalar[str, Literal[1]] = x_np # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 24 | 25 | assert isinstance(x_np, Scalar) 26 | 27 | x_item: bool = x_np.item() 28 | s_item: bool = s_np_n.item() 29 | 30 | 31 | @pytest.mark.parametrize( 32 | "sctype", 33 | [np.int8, np.uint8, np.int16, np.uint16, np.int32, np.uint32, np.int64, np.uint64], 34 | ) 35 | def test_from_integer(sctype: type[_sc.integer]) -> None: 36 | x_py = 42 37 | x_np = sctype(x_py) 38 | 39 | s_py: Scalar[int] = x_py # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 40 | assert not isinstance(x_py, Scalar) 41 | 42 | s_np: Scalar[int] = x_np 43 | s_np_wrong: Scalar[str] = x_np # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 44 | s_np_wrong_contra: Scalar[bool] = x_np # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 45 | assert isinstance(x_np, Scalar) 46 | 47 | y_py = s_np.item() 48 | assert_type(y_py, int) 49 | assert_type(y_py, bool) # type: ignore[assert-type] # pyright: ignore[reportAssertTypeFailure] 50 | 51 | 52 | @pytest.mark.parametrize("sctype", [np.float16, np.float32, np.float64]) 53 | def test_from_floating(sctype: type[_sc.floating]) -> None: 54 | x_py = -1 / 12 55 | x_np = sctype(x_py) 56 | 57 | s_py: Scalar[float] = x_py # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 58 | assert not isinstance(x_py, Scalar) 59 | 60 | s_np: Scalar[float] = x_np 61 | s_np_wrong: Scalar[str] = x_np # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 62 | s_np_wrong_contra: Scalar[int] = x_np # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 63 | assert isinstance(x_np, Scalar) 64 | 65 | y_py = s_np.item() 66 | assert_type(y_py, float) 67 | assert_type(y_py, int) # type: ignore[assert-type] # pyright: ignore[reportAssertTypeFailure] 68 | 69 | 70 | @pytest.mark.parametrize("sctype", [np.complex64, np.complex128]) 71 | def test_from_complex(sctype: type[_sc.cfloating]) -> None: 72 | x_py = 3 - 4j 73 | x_np = sctype(x_py) 74 | 75 | s_py: Scalar[complex] = x_py # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 76 | assert not isinstance(x_py, Scalar) 77 | 78 | s_np: Scalar[complex] = x_np 79 | s_np_wrong: Scalar[str] = x_np # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 80 | s_np_wrong_contra: Scalar[float] = x_np # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 81 | assert isinstance(x_np, Scalar) 82 | 83 | y_py = s_np.item() 84 | assert_type(y_py, complex) 85 | assert_type(y_py, float) # type: ignore[assert-type] # pyright: ignore[reportAssertTypeFailure] 86 | -------------------------------------------------------------------------------- /tests/numpy/test_shape.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | if TYPE_CHECKING: 4 | import optype.numpy as onp 5 | 6 | 7 | def test_at_least_0d() -> None: 8 | s0: onp.AtLeast0D = () 9 | 10 | # bool <: int, and the variadic type params of a tuple are somehow 11 | # covariant (even though `TypeVarTuple`'s can only be invariant...) 12 | s1_bool: onp.AtLeast0D = (True,) 13 | 14 | s1_int: onp.AtLeast0D = (1,) 15 | s1_str: onp.AtLeast0D = ("1",) # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 16 | 17 | s2: onp.AtLeast0D = 1, 2 18 | s2_str1: onp.AtLeast0D = "1", 2 # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 19 | s2_str2: onp.AtLeast0D = 1, "2" # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 20 | 21 | 22 | def test_at_least_1d() -> None: 23 | s0: onp.AtLeast1D = () # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 24 | 25 | s1: onp.AtLeast1D = (0,) 26 | s1_str: onp.AtLeast1D = ("1",) # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 27 | 28 | s2: onp.AtLeast1D = 1, 2 29 | s2_str1: onp.AtLeast1D = "1", 2 # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 30 | s2_str2: onp.AtLeast1D = 1, "2" # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 31 | 32 | s3: onp.AtLeast1D = 1, 2, 3 33 | s3_str1: onp.AtLeast1D = "1", 2, 3 # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 34 | s3_str2: onp.AtLeast1D = 1, "2", 3 # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 35 | s3_str3: onp.AtLeast1D = 1, 2, "3" # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 36 | 37 | 38 | def test_at_least_2d() -> None: 39 | s0: onp.AtLeast2D = () # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 40 | s1: onp.AtLeast2D = (0,) # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 41 | 42 | s2: onp.AtLeast2D = 1, 2 43 | s2_str1: onp.AtLeast2D = "1", 2 # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 44 | s2_str2: onp.AtLeast2D = 1, "2" # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 45 | 46 | s3: onp.AtLeast2D = 1, 2, 3 47 | s3_str1: onp.AtLeast2D = "1", 2, 3 # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 48 | s3_str2: onp.AtLeast2D = 1, "2", 3 # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 49 | s3_str3: onp.AtLeast2D = 1, 2, "3" # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 50 | 51 | 52 | def test_at_least_3d() -> None: 53 | s0: onp.AtLeast3D = () # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 54 | s1: onp.AtLeast3D = (0,) # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 55 | s2: onp.AtLeast3D = 0, 1 # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 56 | s3: onp.AtLeast3D = 0, 1, 2 57 | s3_str: onp.AtLeast3D = 0, 1, "2" # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 58 | 59 | 60 | def test_at_most_0d() -> None: 61 | s_0: onp.AtMost0D = () 62 | s_1: onp.AtMost0D = (1,) # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 63 | 64 | 65 | def test_at_most_1d() -> None: 66 | s_0: onp.AtMost1D = () 67 | 68 | s_1: onp.AtMost1D = (1,) 69 | s_1_str: onp.AtMost1D = ("1",) # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 70 | 71 | s_2: onp.AtMost1D = 1, 2 # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 72 | 73 | 74 | def test_at_most_2d() -> None: 75 | s_0: onp.AtMost2D = () 76 | 77 | s_1: onp.AtMost2D = (1,) 78 | s_1_str: onp.AtMost2D = ("1",) # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 79 | 80 | s_2: onp.AtMost2D = 1, 2 81 | s_2_str1: onp.AtMost2D = "1", 2 # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 82 | s_2_str2: onp.AtMost2D = 1, "2" # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 83 | 84 | s_3: onp.AtMost2D = 1, 2, 3 # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 85 | 86 | 87 | def test_at_most_3d() -> None: 88 | s_0: onp.AtMost3D = () 89 | s_1: onp.AtMost3D = (1,) 90 | s_2: onp.AtMost3D = 1, 2 91 | s_3: onp.AtMost3D = 1, 2, 3 92 | 93 | s_3_str1: onp.AtMost3D = "1", 2, 3 # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 94 | s_3_str2: onp.AtMost3D = 1, "2", 3 # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 95 | s_3_str3: onp.AtMost3D = 1, 2, "3" # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 96 | 97 | s_4: onp.AtMost3D = 1, 2, 3, 4 # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 98 | -------------------------------------------------------------------------------- /tests/test_beartype.py: -------------------------------------------------------------------------------- 1 | from beartype import BeartypeConf 2 | from beartype.claw import beartype_package 3 | 4 | 5 | def test_beartype_package() -> None: 6 | beartype_package( 7 | "optype", 8 | conf=BeartypeConf(claw_is_pep526=False, is_debug=True), 9 | ) 10 | -------------------------------------------------------------------------------- /tests/test_copy.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from datetime import date 3 | from fractions import Fraction 4 | from typing import Final, cast 5 | 6 | import pytest 7 | 8 | import optype as op 9 | from optype.inspect import get_protocol_members, is_runtime_protocol 10 | 11 | require_py313: Final = pytest.mark.skipif( 12 | sys.version_info < (3, 13), 13 | reason="requires python>=3.13", 14 | ) 15 | 16 | 17 | def get_type_params(cls: type) -> tuple[object, ...]: 18 | return cast("tuple[object, ...]", getattr(cls, "__parameters__", ())) 19 | 20 | 21 | @pytest.mark.parametrize( 22 | "cls", 23 | [getattr(op.copy, k) for k in op.copy.__all__ if not k.endswith("Self")], 24 | ) 25 | def test_protocols(cls: type) -> None: 26 | # ensure correct name 27 | assert cls.__module__ == "optype.copy" 28 | assert cls.__name__ == cls.__qualname__ 29 | assert cls.__name__.startswith("Can") 30 | 31 | # ensure exported 32 | assert cls.__name__ in op.copy.__all__ 33 | 34 | # ensure each `Can{}` has a corresponding `Can{}Self` sub-protocol 35 | cls_self = cast("type", getattr(op.copy, f"{cls.__name__}Self")) 36 | assert cls_self is not cls 37 | assert cls_self.__name__ in op.copy.__all__ 38 | assert issubclass(cls_self, cls) 39 | assert len(get_type_params(cls)) == len(get_type_params(cls_self)) + 1 40 | 41 | # ensure single-method protocols 42 | assert len(get_protocol_members(cls)) == 1 43 | assert len(get_protocol_members(cls_self)) == 1 44 | 45 | # ensure @runtime_checkable 46 | assert is_runtime_protocol(cls) 47 | assert is_runtime_protocol(cls_self) 48 | 49 | 50 | def test_can_copy() -> None: 51 | a = Fraction(1, 137) 52 | 53 | a_copy: op.copy.CanCopy[Fraction] = a 54 | a_copy_self: op.copy.CanCopySelf = a 55 | 56 | assert isinstance(a, op.copy.CanCopy) 57 | assert isinstance(a, op.copy.CanCopySelf) 58 | 59 | 60 | def test_can_deepcopy() -> None: 61 | a = Fraction(1, 137) 62 | 63 | a_copy: op.copy.CanDeepcopy[Fraction] = a 64 | a_copy_self: op.copy.CanDeepcopySelf = a 65 | 66 | assert isinstance(a, op.copy.CanDeepcopy) 67 | assert isinstance(a, op.copy.CanDeepcopySelf) 68 | 69 | 70 | @require_py313 71 | def test_can_replace() -> None: 72 | d = date(2024, 10, 1) 73 | 74 | # this seemingly redundant `if` statement prevents pyright errors 75 | if sys.version_info >= (3, 13): 76 | d_replace: op.copy.CanReplace[op.CanIndex, date] = d 77 | d_copy_self: op.copy.CanReplaceSelf[op.CanIndex] = d 78 | 79 | assert isinstance(d, op.copy.CanReplace) 80 | assert isinstance(d, op.copy.CanReplaceSelf) 81 | -------------------------------------------------------------------------------- /tests/test_dlpack.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from optype.dlpack import CanDLPackCompat, CanDLPackDevice 4 | 5 | 6 | def test_ndarray_can_dlpack() -> None: 7 | x: np.ndarray[tuple[int], np.dtype[np.float64]] = np.empty(0) 8 | # NOTE: mypy doesn't understand covariance... 9 | x_dl: CanDLPackCompat = x 10 | 11 | assert isinstance(x, CanDLPackCompat) 12 | 13 | 14 | def test_ndarray_can_dlpack_device() -> None: 15 | x: np.ndarray[tuple[int], np.dtype[np.float64]] = np.empty(0) 16 | x_dl: CanDLPackDevice = x 17 | 18 | assert isinstance(x, CanDLPackDevice) 19 | -------------------------------------------------------------------------------- /tests/test_io.py: -------------------------------------------------------------------------------- 1 | # ruff: noqa: N802 2 | import io 3 | import os 4 | import pathlib 5 | from typing import IO 6 | 7 | import pytest 8 | 9 | import optype.io as oio 10 | from optype.inspect import get_args 11 | 12 | 13 | @pytest.mark.parametrize("pathlike", [pathlib.Path()]) 14 | def test_PathLike_to_CanFSPath(pathlike: os.PathLike[str]) -> None: 15 | p: oio.CanFSPath[str] = pathlike 16 | assert isinstance(pathlike, os.PathLike) 17 | assert isinstance(pathlike, oio.CanFSPath) 18 | 19 | 20 | @pytest.mark.parametrize("can_fspath", [pathlib.Path()]) 21 | def test_CanFSPath_to_PathLike(can_fspath: oio.CanFSPath[str]) -> None: 22 | p: os.PathLike[str] = can_fspath 23 | assert isinstance(can_fspath, oio.CanFSPath) 24 | assert isinstance(can_fspath, os.PathLike) 25 | 26 | 27 | @pytest.mark.parametrize("stream", [io.BytesIO()]) 28 | def test_IO_to_CanFileno(stream: IO[bytes] | IO[str]) -> None: 29 | f: oio.CanFileno = stream 30 | assert isinstance(stream, oio.CanFileno) 31 | 32 | 33 | @pytest.mark.parametrize("mode", get_args(oio.Mode_B_), ids=str) 34 | def test_mode_b(mode: str) -> None: 35 | assert len(mode) in {2, 3} 36 | assert "b" in mode 37 | assert "t" not in mode 38 | assert "+" in mode or len(mode) < 3 39 | 40 | 41 | @pytest.mark.parametrize("mode", get_args(oio.Mode_T_), ids=str) 42 | def test_mode_t(mode: str) -> None: 43 | assert len(mode) in {2, 3} or "t" not in mode 44 | assert "b" not in mode 45 | assert "t" in mode or len(mode) < (2 + ("+" in mode)) 46 | -------------------------------------------------------------------------------- /tests/test_json.pyi: -------------------------------------------------------------------------------- 1 | import optype as o 2 | 3 | def f_val(obj: o.json.Value, /) -> str: ... 4 | def f_aval(obj: o.json.AnyValue, /) -> str: ... 5 | 6 | val: o.json.Value 7 | val_arr_obj: o.json.Value | o.json.Array | o.json.Object 8 | aval: o.json.AnyValue 9 | aval_aarr_aobj: o.json.AnyValue | o.json.AnyArray | o.json.AnyObject 10 | aval_val: o.json.AnyValue | o.json.Value 11 | 12 | _ = f_val(val_arr_obj) 13 | 14 | _ = f_aval(val) 15 | _ = f_aval(aval_aarr_aobj) 16 | _ = f_aval(aval_val) 17 | -------------------------------------------------------------------------------- /tests/test_pickle.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import optype as op 4 | from optype.inspect import get_protocol_members, is_runtime_protocol 5 | 6 | 7 | @pytest.mark.parametrize( 8 | "cls", 9 | [getattr(op.pickle, k) for k in op.pickle.__all__], 10 | ) 11 | def test_protocols(cls: type) -> None: 12 | # ensure correct name 13 | assert cls.__module__ == "optype.pickle" 14 | assert cls.__name__ == cls.__qualname__ 15 | assert cls.__name__.startswith("Can") 16 | 17 | # ensure exported 18 | assert cls.__name__ in op.pickle.__all__ 19 | 20 | # ensure single-method protocols 21 | assert len(get_protocol_members(cls) - {"__new__"}) == 1 22 | 23 | # ensure @runtime_checkable 24 | assert is_runtime_protocol(cls) 25 | -------------------------------------------------------------------------------- /tests/test_string.py: -------------------------------------------------------------------------------- 1 | import string 2 | from typing import Final, LiteralString, Protocol, TypeVar 3 | 4 | import pytest 5 | 6 | import optype as op 7 | 8 | 9 | @pytest.mark.parametrize( 10 | ("opt_str", "std_str"), 11 | [ 12 | (op.string.DIGITS_OCT, string.octdigits), 13 | (op.string.DIGITS, string.digits), 14 | (op.string.DIGITS_HEX, string.hexdigits), 15 | (op.string.LETTERS_LOWER, string.ascii_lowercase), 16 | (op.string.LETTERS_UPPER, string.ascii_uppercase), 17 | (op.string.LETTERS, string.ascii_letters), 18 | (op.string.PUNCTUATION, string.punctuation), 19 | (op.string.WHITESPACE, string.whitespace), 20 | (op.string.PRINTABLE, string.printable), 21 | ], 22 | ) 23 | def test_optype_eq_stdlib( 24 | opt_str: tuple[LiteralString, ...], 25 | std_str: LiteralString, 26 | ) -> None: 27 | assert opt_str == tuple(std_str) 28 | 29 | 30 | _ArgT_co = TypeVar("_ArgT_co", bound=op.typing.AnyLiteral, covariant=True) 31 | 32 | 33 | # a `typing.Literal` stores it's values in `__args__` 34 | class _HasArgs(Protocol[_ArgT_co]): 35 | __args__: Final[tuple[_ArgT_co, ...]] # type: ignore[misc] 36 | 37 | 38 | @pytest.mark.parametrize( 39 | ("const", "lit"), 40 | [ 41 | (op.string.DIGITS_BIN, op.string.DigitBin), 42 | (op.string.DIGITS_OCT, op.string.DigitOct), 43 | (op.string.DIGITS, op.string.Digit), 44 | (op.string.DIGITS_HEX, op.string.DigitHex), 45 | (op.string.LETTERS_LOWER, op.string.LetterLower), 46 | (op.string.LETTERS_UPPER, op.string.LetterUpper), 47 | (op.string.LETTERS, op.string.Letter), 48 | (op.string.PUNCTUATION, op.string.Punctuation), 49 | (op.string.WHITESPACE, op.string.Whitespace), 50 | (op.string.PRINTABLE, op.string.Printable), 51 | ], 52 | ) 53 | def test_literal_args_eq_constant( 54 | const: tuple[LiteralString, ...], 55 | lit: _HasArgs[LiteralString], 56 | ) -> None: 57 | assert op.inspect.get_args(lit) == const 58 | -------------------------------------------------------------------------------- /tests/test_types.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if sys.version_info >= (3, 13): 4 | from warnings import deprecated 5 | else: 6 | from typing_extensions import deprecated 7 | 8 | import optype.types as opts 9 | 10 | 11 | @deprecated("Use f_new instead") 12 | def f_old() -> None: ... 13 | def f_new() -> None: ... 14 | 15 | 16 | @deprecated("Use New instead") 17 | class Old: ... 18 | 19 | 20 | class New: ... 21 | 22 | 23 | def test_deprecated_callable() -> None: 24 | assert isinstance(f_old, opts.Deprecated) # pyright: ignore[reportDeprecated] 25 | assert not isinstance(f_new, opts.Deprecated) 26 | 27 | 28 | def test_deprecated_type() -> None: 29 | assert isinstance(Old, opts.Deprecated) # pyright: ignore[reportDeprecated] 30 | assert not isinstance(New, opts.Deprecated) 31 | -------------------------------------------------------------------------------- /tests/test_typing.py: -------------------------------------------------------------------------------- 1 | import enum 2 | import sys 3 | from typing import Generic, Literal, final 4 | 5 | if sys.version_info >= (3, 13): 6 | from typing import TypeVar 7 | else: 8 | from typing_extensions import TypeVar 9 | 10 | import optype.typing as opt 11 | 12 | _IntT_co = TypeVar("_IntT_co", bound=int, covariant=True) 13 | 14 | 15 | # `Any*` type aliases 16 | 17 | 18 | @final 19 | class SimpleIndex(Generic[_IntT_co]): 20 | __slots__ = ("_x",) 21 | 22 | def __init__(self, x: _IntT_co, /) -> None: 23 | self._x = x 24 | 25 | def __index__(self, /) -> _IntT_co: 26 | return self._x 27 | 28 | 29 | @final 30 | class SimpleInt(Generic[_IntT_co]): 31 | __slots__ = ("_x",) 32 | 33 | def __init__(self, x: _IntT_co, /) -> None: 34 | self._x = x 35 | 36 | def __int__(self, /) -> _IntT_co: 37 | return self._x 38 | 39 | 40 | @final 41 | class SimpleFloat: 42 | __slots__ = ("_x",) 43 | 44 | def __init__(self, x: float, /) -> None: 45 | self._x = x 46 | 47 | def __float__(self, /) -> float: 48 | return self._x 49 | 50 | 51 | @final 52 | class SimpleComplex: 53 | __slots__ = ("_x",) 54 | 55 | def __init__(self, x: complex, /) -> None: 56 | self._x = x 57 | 58 | def __complex__(self, /) -> complex: 59 | return self._x 60 | 61 | 62 | _V_co = TypeVar("_V_co", covariant=True) 63 | 64 | 65 | @final 66 | class SequenceLike(Generic[_V_co]): 67 | def __init__(self, /, *values: _V_co) -> None: 68 | self._xs = values 69 | 70 | def __getitem__(self, index: int, /) -> _V_co: 71 | return self._xs[index] 72 | 73 | 74 | def test_any_int() -> None: 75 | p_bool: opt.AnyInt = True 76 | p_int: opt.AnyInt[Literal[2]] = 2 77 | p_index: opt.AnyInt[Literal[3]] = SimpleIndex(3) 78 | p_int_like: opt.AnyInt[Literal[4]] = SimpleInt(4) 79 | 80 | n_complex: opt.AnyInt = 5j # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 81 | n_str: opt.AnyInt = "6" # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 82 | 83 | 84 | def test_any_float() -> None: 85 | p_bool: opt.AnyFloat = True 86 | p_int: opt.AnyFloat = 1 87 | p_float: opt.AnyFloat = 2.0 88 | p_index: opt.AnyFloat = SimpleIndex(3) 89 | p_float_like: opt.AnyFloat = SimpleFloat(4.0) 90 | 91 | n_int_like: opt.AnyFloat = SimpleInt(5) # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 92 | n_complex: opt.AnyInt = 6j # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 93 | n_str: opt.AnyInt = "7" # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 94 | 95 | 96 | def test_any_complex() -> None: 97 | p_bool: opt.AnyComplex = True 98 | p_int: opt.AnyComplex = 1 99 | p_float: opt.AnyComplex = 2.0 100 | p_complex: opt.AnyComplex = 3j 101 | p_index: opt.AnyComplex = SimpleIndex(4) 102 | p_float_like: opt.AnyComplex = SimpleFloat(5.0) 103 | p_complex_like: opt.AnyComplex = SimpleComplex(6.0) 104 | 105 | n_int_like: opt.AnyComplex = SimpleInt(7) # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 106 | n_str: opt.AnyComplex = "8" # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 107 | 108 | 109 | def test_any_iterable() -> None: 110 | p_tuple: opt.AnyIterable[bool] = (False,) 111 | p_list: opt.AnyIterable[int] = [1] 112 | p_set: opt.AnyIterable[float] = {2.0} 113 | p_dict: opt.AnyIterable[str] = {"3": 4} 114 | p_range: opt.AnyIterable[complex] = range(5) 115 | p_generator: opt.AnyIterable[complex] = (i for i in range(6)) 116 | p_str: opt.AnyIterable[str] = "7" 117 | p_bytes: opt.AnyIterable[int] = b"8" 118 | p_sequence: opt.AnyIterable[bytes] = SequenceLike(b"9") 119 | 120 | 121 | class ColorChannel(enum.Enum): 122 | R, G, B = 0, 1, 2 123 | 124 | 125 | def test_any_literal() -> None: 126 | p_none: opt.AnyLiteral = None 127 | p_bool: opt.AnyLiteral = True 128 | p_int: opt.AnyLiteral = 1 129 | n_float: opt.AnyLiteral = 2.0 # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 130 | n_complex: opt.AnyLiteral = 3j # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 131 | p_str: opt.AnyLiteral = "4" 132 | p_bytes: opt.AnyLiteral = b"5" 133 | p_enum: opt.AnyLiteral = ColorChannel.R 134 | 135 | 136 | # `Empty*` type aliases 137 | 138 | 139 | def test_empty_string() -> None: 140 | empty: opt.EmptyString = "" 141 | not_empty: opt.EmptyString = "0" # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 142 | 143 | 144 | def test_empty_bytes() -> None: 145 | empty: opt.EmptyBytes = b"" 146 | not_empty: opt.EmptyBytes = b"0" # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 147 | 148 | 149 | def test_empty_tuple() -> None: 150 | empty: opt.EmptyTuple = () 151 | not_empty: opt.EmptyTuple = (0,) # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 152 | 153 | 154 | def test_empty_list() -> None: 155 | empty: opt.EmptyList = [] 156 | not_empty: opt.EmptyList = [0] # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 157 | 158 | 159 | def test_empty_set() -> None: 160 | empty: opt.EmptySet = set() 161 | not_empty: opt.EmptySet = {0} # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 162 | 163 | 164 | def test_empty_dict() -> None: 165 | empty: opt.EmptyDict = {} 166 | not_empty: opt.EmptyDict = {0: 0} # type: ignore[assignment,misc] # pyright: ignore[reportAssignmentType] 167 | 168 | 169 | def test_empty_iterable() -> None: 170 | empty: opt.EmptyIterable = () 171 | not_empty: opt.EmptyIterable = (0,) # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 172 | 173 | 174 | # `Literal*` type aliases 175 | 176 | 177 | def test_literal_bool() -> None: 178 | p_true: opt.LiteralBool = False 179 | p_false: opt.LiteralBool = True 180 | 181 | n_0: opt.LiteralBool = 0 # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 182 | n_none: opt.LiteralBool = None # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 183 | 184 | # mypy doesn't understand `Literal` aliases... 185 | assert len(opt.LiteralBool.__args__) == 2 # type: ignore[attr-defined] 186 | 187 | 188 | def test_literal_byte() -> None: 189 | p_0: opt.LiteralByte = 0 190 | p_255: opt.LiteralByte = 255 191 | 192 | n_256: opt.LiteralByte = 256 # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 193 | n_m1: opt.LiteralByte = -1 # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 194 | n_bool: opt.LiteralByte = False # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 195 | n_ix0: opt.LiteralByte = SimpleIndex(0) # type: ignore[assignment] # pyright: ignore[reportAssignmentType] 196 | 197 | # mypy doesn't understand `Literal` aliases... 198 | assert len(opt.LiteralByte.__args__) == 256 # type: ignore[attr-defined] 199 | -------------------------------------------------------------------------------- /tests/test_version.py: -------------------------------------------------------------------------------- 1 | import optype as op 2 | 3 | 4 | def test_version() -> None: 5 | assert op.__version__ 6 | assert all(map(str.isdigit, op.__version__.split(".")[:3])) 7 | --------------------------------------------------------------------------------