├── .cherry_picker.toml ├── .clabot ├── .codecov.yml ├── .coveragerc ├── .editorconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── actions │ └── cache-keys │ │ └── action.yml ├── codeql.yml ├── config.yml ├── dependabot.yml └── workflows │ ├── ci-cd.yml │ ├── codeql.yml │ ├── dependabot-auto-merge.yml │ ├── reusable-build-wheel.yml │ └── reusable-linters.yml ├── .gitignore ├── .lgtm.yml ├── .mypy.ini ├── .pre-commit-config.yaml ├── .pyup.yml ├── .readthedocs.yml ├── .yamllint ├── CHANGES.rst ├── CHANGES ├── .TEMPLATE.rst ├── .gitignore └── README.rst ├── CODE_OF_CONDUCT.md ├── CONTRIBUTORS.txt ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs ├── Makefile ├── changes.rst ├── conf.py ├── contributing │ ├── guidelines.rst │ └── release_guide.rst ├── index.rst ├── make.bat └── spelling_wordlist.txt ├── frozenlist ├── __init__.py ├── __init__.pyi ├── _frozenlist.pyx └── py.typed ├── packaging ├── README.md └── pep517_backend │ ├── __init__.py │ ├── __main__.py │ ├── _backend.py │ ├── _compat.py │ ├── _cython_configuration.py │ ├── _transformers.py │ ├── cli.py │ └── hooks.py ├── pyproject.toml ├── pytest.ini ├── requirements ├── cython.txt ├── dev.txt ├── doc.txt ├── lint.txt ├── test.txt └── towncrier.txt ├── setup.cfg ├── tests ├── conftest.py └── test_frozenlist.py ├── tools ├── drop_merged_branches.sh └── run_docker.sh ├── towncrier.toml └── tox.ini /.cherry_picker.toml: -------------------------------------------------------------------------------- 1 | team = "aio-libs" 2 | repo = "frozenlist" 3 | check_sha = "f382b5ffc445e45a110734f5396728da7914aeb6" 4 | fix_commit_msg = false 5 | -------------------------------------------------------------------------------- /.clabot: -------------------------------------------------------------------------------- 1 | { 2 | "contributors": ["dependabot", "dependabot-bot"] 3 | } 4 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | codecov: 4 | notify: 5 | after_n_builds: 20 # The number of test matrix+lint jobs uploading coverage 6 | wait_for_ci: false 7 | 8 | require_ci_to_pass: false 9 | 10 | token: >- # notsecret # repo-scoped, upload-only, stability in fork PRs 11 | 9294d8cc-5520-4c49-b524-5f9e30bdd09b 12 | 13 | comment: 14 | require_changes: true 15 | 16 | coverage: 17 | range: 100..100 18 | status: 19 | patch: 20 | default: 21 | target: 100% 22 | flags: 23 | - pytest 24 | project: 25 | default: 26 | target: 100% 27 | lib: 28 | flags: 29 | - pytest 30 | paths: 31 | - frozenlist/ 32 | target: 100% 33 | packaging: 34 | paths: 35 | - packaging/ 36 | target: 75.24% 37 | tests: 38 | flags: 39 | - pytest 40 | paths: 41 | - tests/ 42 | target: 100% 43 | typing: 44 | flags: 45 | - MyPy 46 | target: 100% 47 | 48 | ... 49 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [html] 2 | show_contexts = true 3 | skip_covered = false 4 | 5 | [paths] 6 | _site-packages-to-src-mapping = 7 | . 8 | */lib/pypy*/site-packages 9 | */lib/python*/site-packages 10 | *\Lib\site-packages 11 | 12 | [report] 13 | exclude_also = 14 | ^\s*@pytest\.mark\.xfail 15 | # fail_under = 98.95 16 | skip_covered = true 17 | skip_empty = true 18 | show_missing = true 19 | 20 | [run] 21 | branch = true 22 | cover_pylib = false 23 | # https://coverage.rtfd.io/en/latest/contexts.html#dynamic-contexts 24 | # dynamic_context = test_function # conflicts with `pytest-cov` if set here 25 | parallel = true 26 | plugins = Cython.Coverage 27 | # plugins = 28 | # covdefaults 29 | relative_files = true 30 | source = 31 | . 32 | source_pkgs = 33 | frozenlist 34 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | indent_style = space 11 | indent_size = 4 12 | trim_trailing_whitespace = true 13 | charset = utf-8 14 | 15 | [Makefile] 16 | indent_style = tab 17 | 18 | [*.{yml,yaml}] 19 | indent_size = 2 20 | 21 | [*.rst] 22 | max_line_length = 80 23 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # These are supported funding model platforms 3 | 4 | github: 5 | - asvetlov 6 | - webknjaz 7 | - Dreamsorcerer 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Long story short 2 | 3 | 4 | 5 | ## Expected behaviour 6 | 7 | 8 | 9 | ## Actual behaviour 10 | 11 | 12 | 13 | ## Steps to reproduce 14 | 15 | 18 | 19 | ## Your environment 20 | 21 | 25 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## What do these changes do? 4 | 5 | 6 | 7 | ## Are there changes in behavior for the user? 8 | 9 | 10 | 11 | ## Related issue number 12 | 13 | 14 | 15 | ## Checklist 16 | 17 | - [ ] I think the code is well written 18 | - [ ] Unit tests for the changes exist 19 | - [ ] Documentation reflects the changes 20 | - [ ] If you provide code modifications, please add yourself to `CONTRIBUTORS.txt` 21 | * The format is <Name> <Surname>. 22 | * Please keep the list in alphabetical order, the file is sorted by name. 23 | - [ ] Add a new news fragment into the `CHANGES` folder 24 | * name it `.` for example (588.bugfix) 25 | * if you don't have an `issue_id` change it to the pr id after creating the pr 26 | * ensure type is one of the following: 27 | * `.feature`: Signifying a new feature. 28 | * `.bugfix`: Signifying a bug fix. 29 | * `.doc`: Signifying a documentation improvement. 30 | * `.removal`: Signifying a deprecation or removal of public API. 31 | * `.misc`: A ticket has been closed, but it is not of interest to users. 32 | * Make sure to use full sentences with correct case and punctuation, for example: "Fix issue with non-ascii contents in doctest text files." 33 | -------------------------------------------------------------------------------- /.github/actions/cache-keys/action.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: placeholder 4 | description: placeholder 5 | 6 | outputs: 7 | cache-key-for-dep-files: 8 | description: >- 9 | A cache key string derived from the dependency declaration files. 10 | value: ${{ steps.calc-cache-key-files.outputs.files-hash-key }} 11 | 12 | runs: 13 | using: composite 14 | steps: 15 | - name: >- 16 | Calculate dependency files' combined hash value 17 | for use in the cache key 18 | id: calc-cache-key-files 19 | run: | 20 | from os import environ 21 | from pathlib import Path 22 | 23 | FILE_APPEND_MODE = 'a' 24 | 25 | files_derived_hash = '${{ 26 | hashFiles( 27 | 'tox.ini', 28 | 'pyproject.toml', 29 | '.pre-commit-config.yaml', 30 | 'pytest.ini', 31 | 'requirements/**' 32 | ) 33 | }}' 34 | 35 | print(f'Computed file-derived hash is {files_derived_hash}.') 36 | 37 | with Path(environ['GITHUB_OUTPUT']).open( 38 | mode=FILE_APPEND_MODE, 39 | ) as outputs_file: 40 | print( 41 | f'files-hash-key={files_derived_hash}', 42 | file=outputs_file, 43 | ) 44 | shell: python 45 | 46 | ... 47 | -------------------------------------------------------------------------------- /.github/codeql.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | query-filters: 4 | - exclude: 5 | id: py/unsafe-cyclic-import 6 | 7 | ... 8 | -------------------------------------------------------------------------------- /.github/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | chronographer: 4 | exclude: 5 | bots: 6 | - dependabot 7 | humans: 8 | - pyup-bot 9 | 10 | ... 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | version: 2 4 | updates: 5 | 6 | # Maintain dependencies for GitHub Actions 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | labels: 10 | - dependencies 11 | - autosquash 12 | schedule: 13 | interval: "daily" 14 | 15 | # Maintain dependencies for Python 16 | - package-ecosystem: pip 17 | directory: "/" 18 | labels: 19 | - dependencies 20 | - autosquash 21 | open-pull-requests-limit: 10 22 | schedule: 23 | interval: "daily" 24 | 25 | ... 26 | -------------------------------------------------------------------------------- /.github/workflows/ci-cd.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: CI/CD 4 | 5 | on: 6 | merge_group: 7 | push: 8 | branches: 9 | - master 10 | - >- 11 | [0-9].[0-9]+ 12 | tags: 13 | - v* 14 | pull_request: 15 | branches: 16 | - master 17 | - >- 18 | [0-9].[0-9]+ 19 | schedule: 20 | - cron: 0 6 * * * # Daily 6AM UTC build 21 | 22 | 23 | concurrency: 24 | group: ${{ github.ref }}-${{ github.workflow }} 25 | cancel-in-progress: true 26 | 27 | 28 | env: 29 | COLOR: >- # Supposedly, pytest or coveragepy use this 30 | yes 31 | FORCE_COLOR: 1 # Request colored output from CLI tools supporting it 32 | MYPY_FORCE_COLOR: 1 # MyPy's color enforcement 33 | PIP_DISABLE_PIP_VERSION_CHECK: 1 34 | PIP_NO_PYTHON_VERSION_WARNING: 1 35 | PIP_NO_WARN_SCRIPT_LOCATION: 1 36 | PRE_COMMIT_COLOR: always 37 | PROJECT_NAME: frozenlist 38 | PY_COLORS: 1 # Recognized by the `py` package, dependency of `pytest` 39 | PYTHONIOENCODING: utf-8 40 | PYTHONUTF8: 1 41 | PYTHON_LATEST: 3.x 42 | 43 | 44 | jobs: 45 | 46 | pre-setup: 47 | name: ⚙️ Pre-set global build settings 48 | runs-on: ubuntu-latest 49 | timeout-minutes: 1 50 | defaults: 51 | run: 52 | shell: python 53 | outputs: 54 | # NOTE: These aren't env vars because the `${{ env }}` context is 55 | # NOTE: inaccessible when passing inputs to reusable workflows. 56 | dists-artifact-name: python-package-distributions 57 | sdist-name: ${{ env.PROJECT_NAME }}-*.tar.gz 58 | wheel-name: ${{ env.PROJECT_NAME }}-*.whl 59 | steps: 60 | - run: >- 61 | print('No-op') 62 | 63 | 64 | build-pure-python-dists: 65 | name: 📦 Build distribution packages 66 | needs: 67 | - pre-setup 68 | runs-on: ubuntu-latest 69 | timeout-minutes: 1 70 | outputs: 71 | sdist-filename: >- 72 | ${{ steps.dist-filenames-detection.outputs.sdist-filename }} 73 | wheel-filename: >- 74 | ${{ steps.dist-filenames-detection.outputs.wheel-filename }} 75 | steps: 76 | - name: Checkout project 77 | uses: actions/checkout@v4 78 | - name: Set up Python 79 | uses: actions/setup-python@v5 80 | with: 81 | python-version: ${{ env.PYTHON_LATEST }} 82 | - name: >- 83 | Calculate dependency files' combined hash value 84 | for use in the cache key 85 | id: calc-cache-key-files 86 | uses: ./.github/actions/cache-keys 87 | - name: Set up pip cache 88 | uses: re-actors/cache-python-deps@release/v1 89 | with: 90 | cache-key-for-dependency-files: >- 91 | ${{ steps.calc-cache-key-files.outputs.cache-key-for-dep-files }} 92 | - name: Install core libraries for build 93 | run: python -Im pip install build 94 | - name: Build sdists and pure-python wheel 95 | run: python -Im build --config-setting=pure-python=true 96 | - name: Determine actual created filenames 97 | id: dist-filenames-detection 98 | run: >- 99 | { 100 | echo -n sdist-filename= 101 | ; 102 | basename "$(ls -1 dist/${{ needs.pre-setup.outputs.sdist-name }})" 103 | ; 104 | echo -n wheel-filename= 105 | ; 106 | basename "$(ls -1 dist/${{ needs.pre-setup.outputs.wheel-name }})" 107 | ; 108 | } 109 | >> "${GITHUB_OUTPUT}" 110 | - name: Upload built artifacts for testing 111 | uses: actions/upload-artifact@v4 112 | with: 113 | if-no-files-found: error 114 | name: ${{ needs.pre-setup.outputs.dists-artifact-name }} 115 | # NOTE: Exact expected file names are specified here 116 | # NOTE: as a safety measure — if anything weird ends 117 | # NOTE: up being in this dir or not all dists will be 118 | # NOTE: produced, this will fail the workflow. 119 | path: | 120 | dist/${{ steps.dist-filenames-detection.outputs.sdist-filename }} 121 | dist/${{ steps.dist-filenames-detection.outputs.wheel-filename }} 122 | retention-days: 15 123 | 124 | lint: 125 | uses: ./.github/workflows/reusable-linters.yml 126 | secrets: 127 | codecov-token: ${{ secrets.CODECOV_TOKEN }} 128 | 129 | build-wheels-for-tested-arches: 130 | name: >- # ${{ '' } is a hack to nest jobs under the same sidebar category 131 | 📦 Build wheels for tested arches${{ '' }} 132 | needs: 133 | - build-pure-python-dists 134 | - pre-setup # transitive, for accessing settings 135 | strategy: 136 | matrix: 137 | os: 138 | - ubuntu 139 | - windows 140 | - macos 141 | tag: 142 | - '' 143 | - 'musllinux' 144 | exclude: 145 | - os: windows 146 | tag: 'musllinux' 147 | - os: macos 148 | tag: 'musllinux' 149 | - os: ubuntu 150 | tag: >- 151 | ${{ 152 | (github.event_name != 'push' || github.ref_type != 'tag') 153 | && 'musllinux' || 'none' 154 | }} 155 | uses: ./.github/workflows/reusable-build-wheel.yml 156 | with: 157 | os: ${{ matrix.os }} 158 | tag: ${{ matrix.tag }} 159 | wheel-tags-to-skip: >- 160 | ${{ 161 | (github.event_name != 'push' || !contains(github.ref, 'refs/tags/')) 162 | && '*_i686 163 | *-macosx_universal2 164 | *-musllinux_* 165 | *-win32 166 | *_arm64 167 | pp*' 168 | || (matrix.tag == 'musllinux') && '*-manylinux_* pp*' 169 | || '*-musllinux_* pp*' 170 | }} 171 | source-tarball-name: >- 172 | ${{ needs.build-pure-python-dists.outputs.sdist-filename }} 173 | dists-artifact-name: ${{ needs.pre-setup.outputs.dists-artifact-name }} 174 | cython-tracing: >- # Cython line tracing for coverage collection 175 | ${{ 176 | ( 177 | github.event_name == 'push' 178 | && contains(github.ref, 'refs/tags/') 179 | ) 180 | && 'false' 181 | || 'true' 182 | }} 183 | 184 | test: 185 | name: Test 186 | needs: 187 | - build-pure-python-dists # transitive, for accessing settings 188 | - build-wheels-for-tested-arches 189 | - pre-setup # transitive, for accessing settings 190 | strategy: 191 | matrix: 192 | pyver: 193 | - 3.13t 194 | - 3.13 195 | - 3.12 196 | - 3.11 197 | - >- 198 | 3.10 199 | - 3.9 200 | no-extensions: ['', 'Y'] 201 | os: 202 | - ubuntu-latest 203 | - macos-latest 204 | - windows-latest 205 | experimental: [false] 206 | exclude: 207 | - os: macos-latest 208 | no-extensions: Y 209 | - os: windows-latest 210 | no-extensions: Y 211 | include: 212 | - pyver: pypy-3.10 213 | no-extensions: Y 214 | experimental: false 215 | os: ubuntu-latest 216 | - pyver: pypy-3.9 217 | no-extensions: Y 218 | experimental: false 219 | os: ubuntu-latest 220 | fail-fast: false 221 | runs-on: ${{ matrix.os }} 222 | timeout-minutes: 15 223 | continue-on-error: ${{ matrix.experimental }} 224 | steps: 225 | - name: Retrieve the project source from an sdist inside the GHA artifact 226 | uses: re-actors/checkout-python-sdist@release/v2 227 | with: 228 | source-tarball-name: >- 229 | ${{ needs.build-pure-python-dists.outputs.sdist-filename }} 230 | workflow-artifact-name: >- 231 | ${{ needs.pre-setup.outputs.dists-artifact-name }} 232 | - name: Download distributions 233 | uses: actions/download-artifact@v4 234 | with: 235 | path: dist 236 | pattern: ${{ needs.pre-setup.outputs.dists-artifact-name }}* 237 | merge-multiple: true 238 | 239 | - name: Setup Python ${{ matrix.pyver }} 240 | id: python-install 241 | uses: actions/setup-python@v5 242 | with: 243 | python-version: ${{ matrix.pyver }} 244 | allow-prereleases: true 245 | - name: >- 246 | Calculate dependency files' combined hash value 247 | for use in the cache key 248 | id: calc-cache-key-files 249 | uses: ./.github/actions/cache-keys 250 | - name: Set up pip cache 251 | uses: re-actors/cache-python-deps@release/v1 252 | with: 253 | cache-key-for-dependency-files: >- 254 | ${{ steps.calc-cache-key-files.outputs.cache-key-for-dep-files }} 255 | - name: Install dependencies 256 | uses: py-actions/py-dependency-install@v4 257 | with: 258 | path: requirements/test.txt 259 | - name: Determine pre-compiled compatible wheel 260 | env: 261 | # NOTE: When `pip` is forced to colorize output piped into `jq`, 262 | # NOTE: the latter can't parse it. So we're overriding the color 263 | # NOTE: preference here via https://no-color.org. 264 | # NOTE: Setting `FORCE_COLOR` to any value (including 0, an empty 265 | # NOTE: string, or a "YAML null" `~`) doesn't have any effect and 266 | # NOTE: `pip` (through its verndored copy of `rich`) treats the 267 | # NOTE: presence of the variable as "force-color" regardless. 268 | # 269 | # NOTE: This doesn't actually work either, so we'll resort to unsetting 270 | # NOTE: in the Bash script. 271 | # NOTE: Ref: https://github.com/Textualize/rich/issues/2622 272 | NO_COLOR: 1 273 | id: wheel-file 274 | run: > 275 | echo -n path= | tee -a "${GITHUB_OUTPUT}" 276 | 277 | 278 | unset FORCE_COLOR 279 | 280 | 281 | python 282 | -X utf8 283 | -u -I 284 | -m pip install 285 | --find-links=./dist 286 | --no-index 287 | '${{ env.PROJECT_NAME }}' 288 | --force-reinstall 289 | --no-color 290 | --no-deps 291 | --only-binary=:all: 292 | --dry-run 293 | --report=- 294 | --quiet 295 | | jq --raw-output .install[].download_info.url 296 | | tee -a "${GITHUB_OUTPUT}" 297 | shell: bash 298 | - name: Self-install 299 | run: python -Im pip install '${{ steps.wheel-file.outputs.path }}' 300 | - name: Produce the C-files for the Coverage.py Cython plugin 301 | if: >- # Only works if the dists were built with line tracing 302 | !matrix.no-extensions 303 | && ( 304 | github.event_name != 'push' 305 | || !contains(github.ref, 'refs/tags/') 306 | ) 307 | env: 308 | PYTHONPATH: packaging/ 309 | run: | 310 | set -eEuo pipefail 311 | 312 | python -Im pip install expandvars 313 | python -m pep517_backend.cli translate-cython 314 | shell: bash 315 | - name: Disable the Cython.Coverage Produce plugin 316 | if: >- # Only works if the dists were built with line tracing 317 | matrix.no-extensions 318 | || ( 319 | github.event_name == 'push' 320 | && contains(github.ref, 'refs/tags/') 321 | ) 322 | run: | 323 | set -eEuo pipefail 324 | sed -i.bak 's/^plugins = .*//g' .coveragerc 325 | shell: bash 326 | - name: Run unittests 327 | env: 328 | FROZENLIST_NO_EXTENSIONS: ${{ matrix.no-extensions }} 329 | run: >- 330 | python -Im 331 | pytest 332 | -v 333 | --cov-report xml 334 | --junitxml=.test-results/pytest/test.xml 335 | - name: Produce markdown test summary from JUnit 336 | if: >- 337 | !cancelled() 338 | uses: test-summary/action@v2.4 339 | with: 340 | paths: .test-results/pytest/test.xml 341 | - name: Append coverage results to Job Summary 342 | if: >- 343 | !cancelled() 344 | run: >- 345 | python -Im coverage report --format=markdown 346 | >> "${GITHUB_STEP_SUMMARY}" 347 | shell: bash 348 | - name: Re-run the failing tests with maximum verbosity 349 | if: >- 350 | !cancelled() 351 | && failure() 352 | env: 353 | FROZENLIST_NO_EXTENSIONS: ${{ matrix.no-extensions }} 354 | run: >- # `exit 1` makes sure that the job remains red with flaky runs 355 | python -Im 356 | pytest --no-cov -vvvvv --lf -rA 357 | && exit 1 358 | shell: bash 359 | - name: Send coverage data to Codecov 360 | if: >- 361 | !cancelled() 362 | uses: codecov/codecov-action@v5 363 | with: 364 | token: ${{ secrets.CODECOV_TOKEN }} 365 | files: ./coverage.xml 366 | flags: >- 367 | CI-GHA, 368 | pytest, 369 | OS-${{ runner.os }}, 370 | VM-${{ matrix.os }}, 371 | Py-${{ steps.python-install.outputs.python-version }} 372 | fail_ci_if_error: true 373 | 374 | test-summary: 375 | name: Test matrix status 376 | if: always() 377 | runs-on: ubuntu-latest 378 | timeout-minutes: 1 379 | needs: [lint, test] 380 | steps: 381 | - name: Decide whether the needed jobs succeeded or failed 382 | uses: re-actors/alls-green@release/v1 383 | with: 384 | jobs: ${{ toJSON(needs) }} 385 | 386 | pre-deploy: 387 | name: Pre-Deploy 388 | runs-on: ubuntu-latest 389 | timeout-minutes: 1 390 | needs: test-summary 391 | # Run only on pushing a tag 392 | if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') 393 | steps: 394 | - name: Dummy 395 | run: | 396 | echo "Predeploy step" 397 | 398 | build-wheels-for-odd-archs: 399 | name: >- # ${{ '' } is a hack to nest jobs under the same sidebar category 400 | 📦 Build wheels for odd arches${{ '' }} 401 | needs: 402 | - build-pure-python-dists 403 | - pre-deploy 404 | - pre-setup # transitive, for accessing settings 405 | strategy: 406 | matrix: 407 | qemu: 408 | - aarch64 409 | - ppc64le 410 | - s390x 411 | - armv7l 412 | tag: 413 | - '' 414 | - musllinux 415 | uses: ./.github/workflows/reusable-build-wheel.yml 416 | with: 417 | qemu: ${{ matrix.qemu }} 418 | tag: ${{ matrix.tag }} 419 | wheel-tags-to-skip: >- 420 | ${{ 421 | (matrix.tag == 'musllinux') 422 | && '*-manylinux_* pp*' 423 | || '*-musllinux_* pp*' 424 | }} 425 | source-tarball-name: >- 426 | ${{ needs.build-pure-python-dists.outputs.sdist-filename }} 427 | dists-artifact-name: ${{ needs.pre-setup.outputs.dists-artifact-name }} 428 | 429 | deploy: 430 | name: Deploy 431 | needs: 432 | - build-pure-python-dists 433 | - build-wheels-for-odd-archs 434 | - build-wheels-for-tested-arches 435 | - pre-setup # transitive, for accessing settings 436 | runs-on: ubuntu-latest 437 | timeout-minutes: 14 438 | 439 | permissions: 440 | contents: write # IMPORTANT: mandatory for making GitHub Releases 441 | id-token: write # IMPORTANT: mandatory for trusted publishing & sigstore 442 | 443 | environment: 444 | name: pypi 445 | url: https://pypi.org/p/${{ env.PROJECT_NAME }} 446 | 447 | steps: 448 | - name: Retrieve the project source from an sdist inside the GHA artifact 449 | uses: re-actors/checkout-python-sdist@release/v2 450 | with: 451 | source-tarball-name: >- 452 | ${{ needs.build-pure-python-dists.outputs.sdist-filename }} 453 | workflow-artifact-name: >- 454 | ${{ needs.pre-setup.outputs.dists-artifact-name }} 455 | 456 | - name: Download distributions 457 | uses: actions/download-artifact@v4 458 | with: 459 | path: dist 460 | pattern: ${{ needs.pre-setup.outputs.dists-artifact-name }}* 461 | merge-multiple: true 462 | - run: | 463 | tree 464 | - name: Login 465 | run: | 466 | echo "${{ secrets.GITHUB_TOKEN }}" | gh auth login --with-token 467 | - name: Make Release 468 | uses: aio-libs/create-release@v1.6.6 469 | with: 470 | changes_file: CHANGES.rst 471 | version_file: ${{ env.PROJECT_NAME }}/__init__.py 472 | github_token: ${{ secrets.GITHUB_TOKEN }} 473 | head_line: >- 474 | v{version}\n=+\n\n\*\({date}\)\*\n 475 | fix_issue_regex: >- 476 | :issue:`(\d+)` 477 | fix_issue_repl: >- 478 | #\1 479 | 480 | - name: >- 481 | Publish 🐍📦 to PyPI 482 | uses: pypa/gh-action-pypi-publish@release/v1 483 | 484 | - name: Sign the dists with Sigstore 485 | uses: sigstore/gh-action-sigstore-python@v3.0.0 486 | with: 487 | inputs: >- 488 | ./dist/${{ needs.build-pure-python-dists.outputs.sdist-filename }} 489 | ./dist/*.whl 490 | 491 | - name: Upload artifact signatures to GitHub Release 492 | # Confusingly, this action also supports updating releases, not 493 | # just creating them. This is what we want here, since we've manually 494 | # created the release above. 495 | uses: softprops/action-gh-release@v2 496 | with: 497 | # dist/ contains the built packages, which smoketest-artifacts/ 498 | # contains the signatures and certificates. 499 | files: dist/** 500 | 501 | ... 502 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: "CodeQL" 4 | 5 | on: 6 | push: 7 | branches: 8 | - master 9 | pull_request: 10 | branches: 11 | - master 12 | schedule: 13 | - cron: 14 13 * * 1 14 | 15 | jobs: 16 | analyze: 17 | name: Analyze 18 | runs-on: ubuntu-latest 19 | timeout-minutes: 4 20 | permissions: 21 | actions: read 22 | contents: read 23 | security-events: write 24 | 25 | strategy: 26 | fail-fast: false 27 | matrix: 28 | language: 29 | - python 30 | 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v4 34 | 35 | - name: Initialize CodeQL 36 | uses: github/codeql-action/init@v3 37 | with: 38 | languages: ${{ matrix.language }} 39 | config-file: ./.github/codeql.yml 40 | queries: +security-and-quality 41 | 42 | - name: Autobuild 43 | uses: github/codeql-action/autobuild@v3 44 | 45 | - name: Perform CodeQL Analysis 46 | uses: github/codeql-action/analyze@v3 47 | with: 48 | category: "/language:${{ matrix.language }}" 49 | 50 | ... 51 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-auto-merge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Dependabot auto-merge 4 | on: pull_request_target 5 | 6 | permissions: 7 | pull-requests: write 8 | contents: write 9 | 10 | jobs: 11 | dependabot: 12 | timeout-minutes: 1 13 | runs-on: ubuntu-latest 14 | if: ${{ github.actor == 'dependabot[bot]' }} 15 | steps: 16 | - name: Dependabot metadata 17 | id: metadata 18 | uses: dependabot/fetch-metadata@v2.4.0 19 | with: 20 | github-token: "${{ secrets.GITHUB_TOKEN }}" 21 | - name: Enable auto-merge for Dependabot PRs 22 | # only auto-approve direct deps that are minor or patch updates 23 | # dependency type is indirect, direct:development or direct:production 24 | # version-update is semver-major, semver-minor or semver-patch 25 | if: | 26 | steps.metadata.outputs.dependency-type != 'indirect' 27 | && steps.metadata.outputs.update-type != 'version-update:semver-major' 28 | run: | 29 | gh pr merge --auto --squash "$PR_URL" 30 | env: 31 | PR_URL: ${{github.event.pull_request.html_url}} 32 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 33 | 34 | ... 35 | -------------------------------------------------------------------------------- /.github/workflows/reusable-build-wheel.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Build wheel 4 | 5 | on: 6 | workflow_call: 7 | inputs: 8 | dists-artifact-name: 9 | description: Workflow artifact name containing dists 10 | required: true 11 | type: string 12 | cython-tracing: 13 | description: Whether to build Cython modules with line tracing 14 | default: '0' 15 | required: false 16 | type: string 17 | os: 18 | description: VM OS to use, without version suffix 19 | default: ubuntu 20 | required: false 21 | type: string 22 | qemu: 23 | description: Emulated QEMU architecture 24 | default: '' 25 | required: false 26 | type: string 27 | tag: 28 | description: Build platform tag wheels 29 | default: '' 30 | required: false 31 | type: string 32 | source-tarball-name: 33 | description: Sdist filename wildcard 34 | required: true 35 | type: string 36 | wheel-tags-to-skip: 37 | description: Wheel tags to skip building 38 | default: '' 39 | required: false 40 | type: string 41 | 42 | env: 43 | FORCE_COLOR: "1" # Make tools pretty. 44 | PIP_DISABLE_PIP_VERSION_CHECK: "1" 45 | PIP_NO_PYTHON_VERSION_WARNING: "1" 46 | 47 | jobs: 48 | 49 | build-wheel: 50 | name: >- 51 | Build ${{ inputs.tag }} wheels on ${{ inputs.os }} ${{ inputs.qemu }} 52 | runs-on: ${{ inputs.os }}-latest 53 | timeout-minutes: ${{ inputs.qemu && 25 || 11 }} 54 | steps: 55 | - name: Compute GHA artifact name ending 56 | id: gha-artifact-name 57 | run: | 58 | from hashlib import sha512 59 | from os import environ 60 | from pathlib import Path 61 | FILE_APPEND_MODE = 'a' 62 | inputs_json_str = """${{ toJSON(inputs) }}""" 63 | hash = sha512(inputs_json_str.encode()).hexdigest() 64 | with Path(environ['GITHUB_OUTPUT']).open( 65 | mode=FILE_APPEND_MODE, 66 | ) as outputs_file: 67 | print(f'hash={hash}', file=outputs_file) 68 | shell: python 69 | - name: Retrieve the project source from an sdist inside the GHA artifact 70 | uses: re-actors/checkout-python-sdist@release/v2 71 | with: 72 | source-tarball-name: ${{ inputs.source-tarball-name }} 73 | workflow-artifact-name: ${{ inputs.dists-artifact-name }} 74 | 75 | - name: Set up QEMU 76 | if: inputs.qemu 77 | uses: docker/setup-qemu-action@v3 78 | with: 79 | platforms: all 80 | id: qemu 81 | - name: Prepare emulation 82 | if: inputs.qemu 83 | run: | 84 | # Build emulated architectures only if QEMU is set, 85 | # use default "auto" otherwise 86 | echo "CIBW_ARCHS_LINUX=${{ inputs.qemu }}" >> "${GITHUB_ENV}" 87 | shell: bash 88 | 89 | - name: Skip building some wheel tags 90 | if: inputs.wheel-tags-to-skip 91 | run: | 92 | echo "CIBW_SKIP=${{ inputs.wheel-tags-to-skip }}" >> "${GITHUB_ENV}" 93 | shell: bash 94 | 95 | - name: Build wheels 96 | uses: pypa/cibuildwheel@v2.23.3 97 | env: 98 | CIBW_ARCHS_MACOS: x86_64 arm64 universal2 99 | CIBW_CONFIG_SETTINGS: >- # Cython line tracing for coverage collection 100 | pure-python=false 101 | with-cython-tracing=${{ inputs.cython-tracing }} 102 | 103 | - name: Upload built artifacts for testing and publishing 104 | uses: actions/upload-artifact@v4 105 | with: 106 | name: ${{ inputs.dists-artifact-name }}- 107 | ${{ inputs.os }}- 108 | ${{ inputs.qemu }}- 109 | ${{ inputs.tag }}- 110 | ${{ steps.gha-artifact-name.outputs.hash }} 111 | path: ./wheelhouse/*.whl 112 | 113 | ... 114 | -------------------------------------------------------------------------------- /.github/workflows/reusable-linters.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Linters 4 | 5 | on: 6 | workflow_call: 7 | secrets: 8 | codecov-token: 9 | description: Mandatory token for uploading to Codecov 10 | required: true 11 | 12 | env: 13 | COLOR: >- # Supposedly, pytest or coveragepy use this 14 | yes 15 | FORCE_COLOR: 1 # Request colored output from CLI tools supporting it 16 | MYPY_FORCE_COLOR: 1 # MyPy's color enforcement 17 | PIP_DISABLE_PIP_VERSION_CHECK: 1 18 | PIP_NO_PYTHON_VERSION_WARNING: 1 19 | PIP_NO_WARN_SCRIPT_LOCATION: 1 20 | PRE_COMMIT_COLOR: always 21 | PY_COLORS: 1 # Recognized by the `py` package, dependency of `pytest` 22 | PYTHONIOENCODING: utf-8 23 | PYTHONUTF8: 1 24 | PYTHON_LATEST: 3.x 25 | 26 | jobs: 27 | 28 | lint: 29 | name: Linter 30 | runs-on: ubuntu-latest 31 | timeout-minutes: 5 32 | steps: 33 | - name: Checkout 34 | uses: actions/checkout@v4 35 | - name: Setup Python ${{ env.PYTHON_LATEST }} 36 | uses: actions/setup-python@v5 37 | with: 38 | python-version: ${{ env.PYTHON_LATEST }} 39 | - name: >- 40 | Calculate dependency files' combined hash value 41 | for use in the cache key 42 | id: calc-cache-key-files 43 | uses: ./.github/actions/cache-keys 44 | - name: Set up pip cache 45 | uses: re-actors/cache-python-deps@release/v1 46 | with: 47 | cache-key-for-dependency-files: >- 48 | ${{ steps.calc-cache-key-files.outputs.cache-key-for-dep-files }} 49 | - name: Cache pre-commit.com virtualenvs 50 | uses: actions/cache@v4 51 | with: 52 | path: ~/.cache/pre-commit 53 | key: >- 54 | ${{ 55 | runner.os 56 | }}-pre-commit-${{ 57 | hashFiles('.pre-commit-config.yaml') 58 | }} 59 | - name: Install dependencies 60 | uses: py-actions/py-dependency-install@v4 61 | with: 62 | path: requirements/lint.txt 63 | - name: Self-install 64 | run: | 65 | pip install . --config-settings=pure-python=true 66 | - name: Run linters 67 | run: | 68 | make lint 69 | - name: Send coverage data to Codecov 70 | uses: codecov/codecov-action@v5 71 | with: 72 | token: ${{ secrets.codecov-token }} 73 | files: >- 74 | .tox/.tmp/.mypy/python-3.11/cobertura.xml 75 | flags: >- 76 | CI-GHA, 77 | MyPy 78 | fail_ci_if_error: true 79 | - name: Install spell checker 80 | run: | 81 | pip install -r requirements/doc.txt 82 | - name: Run docs spelling 83 | run: | 84 | towncrier build --yes --version 99.99.99 85 | make doc-spelling 86 | - name: Prepare twine checker 87 | run: | 88 | python -m build --config-setting=pure-python=true 89 | - name: Run twine checker 90 | run: | 91 | twine check --strict dist/* 92 | - name: Make sure that CONTRIBUTORS.txt remains sorted 93 | run: | 94 | LC_ALL=C sort -c CONTRIBUTORS.txt 95 | 96 | ... 97 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.bak 3 | *.egg 4 | *.egg-info 5 | *.eggs 6 | *.pyc 7 | *.pyd 8 | *.pyo 9 | *.so 10 | *.tar.gz 11 | *~ 12 | .DS_Store 13 | .Python 14 | .cache 15 | .coverage 16 | .coverage.* 17 | .direnv 18 | .envrc 19 | .idea 20 | .installed.cfg 21 | .noseids 22 | .tox 23 | .vimrc 24 | frozenlist/_frozenlist.c 25 | frozenlist/_frozenlist.html 26 | bin 27 | build 28 | htmlcov 29 | develop-eggs 30 | dist 31 | docs/_build/ 32 | eggs 33 | include/ 34 | lib/ 35 | man/ 36 | nosetests.xml 37 | parts 38 | pyvenv 39 | sources 40 | var/* 41 | venv 42 | virtualenv.py 43 | .install-cython 44 | .install-deps 45 | .develop 46 | .gitconfig 47 | .flake 48 | .python-version 49 | .pytest_cache 50 | .vscode 51 | .mypy_cache 52 | pip-wheel-metadata 53 | -------------------------------------------------------------------------------- /.lgtm.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | queries: 4 | - exclude: py/unsafe-cyclic-import 5 | 6 | ... 7 | -------------------------------------------------------------------------------- /.mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | files = frozenlist, packaging, tests 3 | check_untyped_defs = True 4 | follow_imports_for_stubs = True 5 | disallow_any_decorated = True 6 | disallow_any_generics = True 7 | disallow_any_unimported = True 8 | disallow_incomplete_defs = True 9 | disallow_subclassing_any = True 10 | disallow_untyped_calls = True 11 | disallow_untyped_decorators = True 12 | disallow_untyped_defs = True 13 | # TODO(PY312): explicit-override 14 | enable_error_code = ignore-without-code, possibly-undefined, redundant-expr, redundant-self, truthy-bool, truthy-iterable, unused-awaitable 15 | extra_checks = True 16 | implicit_reexport = False 17 | no_implicit_optional = True 18 | pretty = True 19 | show_column_numbers = True 20 | show_error_codes = True 21 | show_error_code_links = True 22 | strict_equality = True 23 | warn_incomplete_stub = True 24 | warn_redundant_casts = True 25 | warn_return_any = True 26 | warn_unreachable = True 27 | warn_unused_ignores = True 28 | 29 | [mypy-Cython.*] 30 | ignore_missing_imports = true 31 | 32 | [mypy-expandvars.*] 33 | ignore_missing_imports = true 34 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ci: 4 | autoupdate_schedule: quarterly 5 | skip: 6 | - actionlint-docker 7 | 8 | repos: 9 | - repo: https://github.com/pre-commit/pre-commit-hooks 10 | rev: 'v5.0.0' 11 | hooks: 12 | - id: check-merge-conflict 13 | - repo: https://github.com/asottile/yesqa 14 | rev: v1.5.0 15 | hooks: 16 | - id: yesqa 17 | additional_dependencies: 18 | - wemake-python-styleguide 19 | - repo: https://github.com/PyCQA/isort 20 | rev: '6.0.1' 21 | hooks: 22 | - id: isort 23 | - repo: https://github.com/psf/black 24 | rev: '25.1.0' 25 | hooks: 26 | - id: black 27 | language_version: python3 # Should be a command that runs python3 28 | 29 | - repo: https://github.com/python-jsonschema/check-jsonschema.git 30 | rev: 0.32.1 31 | hooks: 32 | - id: check-github-workflows 33 | files: ^\.github/workflows/[^/]+$ 34 | types: 35 | - yaml 36 | - id: check-jsonschema 37 | alias: check-github-workflows-timeout 38 | name: Check GitHub Workflows set timeout-minutes 39 | args: 40 | - --builtin-schema 41 | - github-workflows-require-timeout 42 | files: ^\.github/workflows/[^/]+$ 43 | types: 44 | - yaml 45 | - id: check-readthedocs 46 | 47 | - repo: https://github.com/pre-commit/pre-commit-hooks 48 | rev: 'v5.0.0' 49 | hooks: 50 | - id: end-of-file-fixer 51 | exclude: >- 52 | ^docs/[^/]*\.svg$ 53 | - id: requirements-txt-fixer 54 | exclude: >- 55 | ^requirements/constraints[.]txt$ 56 | - id: trailing-whitespace 57 | - id: file-contents-sorter 58 | args: ['--ignore-case'] 59 | files: | 60 | CONTRIBUTORS.txt| 61 | docs/spelling_wordlist.txt| 62 | .gitignore| 63 | .gitattributes 64 | - id: check-case-conflict 65 | - id: check-json 66 | - id: check-xml 67 | - id: check-executables-have-shebangs 68 | - id: check-toml 69 | - id: check-yaml 70 | - id: debug-statements 71 | - id: check-added-large-files 72 | - id: check-symlinks 73 | - id: fix-byte-order-marker 74 | - id: fix-encoding-pragma 75 | args: ['--remove'] 76 | - id: detect-aws-credentials 77 | args: ['--allow-missing-credentials'] 78 | - id: detect-private-key 79 | exclude: ^examples/ 80 | - repo: https://github.com/asottile/pyupgrade 81 | rev: 'v3.19.1' 82 | hooks: 83 | - id: pyupgrade 84 | args: ['--py36-plus'] 85 | - repo: https://github.com/PyCQA/flake8 86 | rev: '7.2.0' 87 | hooks: 88 | - id: flake8 89 | exclude: "^docs/" 90 | 91 | - repo: https://github.com/codespell-project/codespell.git 92 | rev: v2.4.1 93 | hooks: 94 | - id: codespell 95 | 96 | - repo: https://github.com/adrienverge/yamllint.git 97 | rev: v1.37.0 98 | hooks: 99 | - id: yamllint 100 | args: 101 | - --strict 102 | 103 | - repo: https://github.com/MarcoGorelli/cython-lint.git 104 | rev: v0.16.6 105 | hooks: 106 | - id: cython-lint 107 | 108 | - repo: https://github.com/Lucas-C/pre-commit-hooks-markup 109 | rev: v1.0.1 110 | hooks: 111 | - id: rst-linter 112 | files: >- 113 | ^[^/]+[.]rst$ 114 | exclude: >- 115 | ^CHANGES\.rst$ 116 | 117 | - repo: local 118 | hooks: 119 | - id: changelogs-rst 120 | name: changelog filenames 121 | language: fail 122 | entry: >- 123 | Changelog files must be named 124 | ####.( 125 | bugfix 126 | | feature 127 | | deprecation 128 | | breaking 129 | | doc 130 | | packaging 131 | | contrib 132 | | misc 133 | )(.#)?(.rst)? 134 | exclude: >- 135 | (?x) 136 | ^ 137 | CHANGES/( 138 | \.gitignore 139 | |(\d+|[0-9a-f]{8}|[0-9a-f]{7}|[0-9a-f]{40})\.( 140 | bugfix 141 | |feature 142 | |deprecation 143 | |breaking 144 | |doc 145 | |packaging 146 | |contrib 147 | |misc 148 | )(\.\d+)?(\.rst)? 149 | |README\.rst 150 | |\.TEMPLATE\.rst 151 | ) 152 | $ 153 | files: ^CHANGES/ 154 | types: [] 155 | types_or: 156 | - file 157 | - symlink 158 | - id: changelogs-user-role 159 | name: Changelog files should use a non-broken :user:`name` role 160 | language: pygrep 161 | entry: :user:([^`]+`?|`[^`]+[\s,]) 162 | pass_filenames: true 163 | types: 164 | - file 165 | - rst 166 | 167 | - repo: https://github.com/pre-commit/mirrors-mypy.git 168 | rev: v1.15.0 169 | hooks: 170 | - id: mypy 171 | alias: mypy-py311 172 | name: MyPy, for Python 3.11 173 | additional_dependencies: 174 | - lxml # dep of `--txt-report`, `--cobertura-xml-report` & `--html-report` 175 | - pytest 176 | - tomli # requirement of packaging/pep517_backend/ 177 | - types-setuptools # requirement of packaging/pep517_backend/ 178 | args: 179 | - --install-types 180 | - --non-interactive 181 | - --python-version=3.11 182 | - --txt-report=.tox/.tmp/.mypy/python-3.11 183 | - --cobertura-xml-report=.tox/.tmp/.mypy/python-3.11 184 | - --html-report=.tox/.tmp/.mypy/python-3.11 185 | pass_filenames: false 186 | 187 | - repo: https://github.com/rhysd/actionlint 188 | rev: v1.7.7 189 | hooks: 190 | - id: actionlint-docker 191 | args: 192 | - -ignore 193 | - >- # https://github.com/rhysd/actionlint/issues/384 194 | ^type of expression at "float number value" must be number 195 | but found type string$ 196 | - -ignore 197 | - 'SC2155:' 198 | - -ignore 199 | - 'SC2157:' 200 | - -ignore 201 | - 'SC2086:' 202 | - -ignore 203 | - 'SC1004:' 204 | exclude: >- 205 | ^[.]github/actions/.*$ 206 | 207 | ... 208 | -------------------------------------------------------------------------------- /.pyup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Label PRs with `deps-update` label 4 | label_prs: deps-update 5 | 6 | schedule: every week 7 | 8 | ... 9 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | version: 2 4 | 5 | build: 6 | os: ubuntu-22.04 7 | tools: 8 | python: '3.10' 9 | 10 | python: 11 | install: 12 | - requirements: requirements/doc.txt 13 | 14 | sphinx: 15 | builder: dirhtml 16 | configuration: docs/conf.py 17 | fail_on_warning: true 18 | 19 | ... 20 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | extends: default 4 | 5 | rules: 6 | indentation: 7 | level: error 8 | indent-sequences: false 9 | truthy: 10 | allowed-values: 11 | - >- 12 | false 13 | - >- 14 | true 15 | - >- # Allow "on" key name in GHA CI/CD workflow definitions 16 | on 17 | 18 | ... 19 | -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Changelog 3 | ========= 4 | 5 | .. 6 | You should *NOT* be adding new change log entries to this file, this 7 | file is managed by towncrier. You *may* edit previous change logs to 8 | fix problems like typo corrections or such. 9 | To add a new change log entry, please see 10 | https://pip.pypa.io/en/latest/development/contributing/#news-entries 11 | we named the news folder "changes". 12 | 13 | WARNING: Don't drop the next directive! 14 | 15 | .. towncrier release notes start 16 | 17 | v1.6.1 18 | ====== 19 | 20 | *(2025-06-02)* 21 | 22 | 23 | Bug fixes 24 | --------- 25 | 26 | - Correctly use ``cimport`` for including ``PyBool_FromLong`` -- by :user:`lysnikolaou`. 27 | 28 | *Related issues and pull requests on GitHub:* 29 | :issue:`653`. 30 | 31 | 32 | Packaging updates and notes for downstreams 33 | ------------------------------------------- 34 | 35 | - Exclude ``_frozenlist.cpp`` from bdists/wheels -- by :user:`musicinmybrain`. 36 | 37 | *Related issues and pull requests on GitHub:* 38 | :issue:`649`. 39 | 40 | - Updated to use Cython 3.1 universally across the build path -- by :user:`lysnikolaou`. 41 | 42 | *Related issues and pull requests on GitHub:* 43 | :issue:`654`. 44 | 45 | 46 | ---- 47 | 48 | 49 | v1.6.0 50 | ====== 51 | 52 | *(2025-04-17)* 53 | 54 | 55 | Bug fixes 56 | --------- 57 | 58 | - Stopped implicitly allowing the use of Cython pre-release versions when 59 | building the distribution package -- by :user:`ajsanchezsanz` and 60 | :user:`markgreene74`. 61 | 62 | *Related commits on GitHub:* 63 | :commit:`41591f2`. 64 | 65 | 66 | Features 67 | -------- 68 | 69 | - Implemented support for the free-threaded build of CPython 3.13 -- by :user:`lysnikolaou`. 70 | 71 | *Related issues and pull requests on GitHub:* 72 | :issue:`618`. 73 | 74 | - Started building armv7l wheels -- by :user:`bdraco`. 75 | 76 | *Related issues and pull requests on GitHub:* 77 | :issue:`642`. 78 | 79 | 80 | Packaging updates and notes for downstreams 81 | ------------------------------------------- 82 | 83 | - Stopped implicitly allowing the use of Cython pre-release versions when 84 | building the distribution package -- by :user:`ajsanchezsanz` and 85 | :user:`markgreene74`. 86 | 87 | *Related commits on GitHub:* 88 | :commit:`41591f2`. 89 | 90 | - Started building wheels for the free-threaded build of CPython 3.13 -- by :user:`lysnikolaou`. 91 | 92 | *Related issues and pull requests on GitHub:* 93 | :issue:`618`. 94 | 95 | - The packaging metadata switched to including an SPDX license identifier introduced in :pep:`639` -- by :user:`cdce8p`. 96 | 97 | *Related issues and pull requests on GitHub:* 98 | :issue:`639`. 99 | 100 | 101 | Contributor-facing changes 102 | -------------------------- 103 | 104 | - GitHub Actions CI/CD is now configured to manage caching pip-ecosystem 105 | dependencies using `re-actors/cache-python-deps`_ -- an action by 106 | :user:`webknjaz` that takes into account ABI stability and the exact 107 | version of Python runtime. 108 | 109 | .. _`re-actors/cache-python-deps`: 110 | https://github.com/marketplace/actions/cache-python-deps 111 | 112 | *Related issues and pull requests on GitHub:* 113 | :issue:`633`. 114 | 115 | - Organized dependencies into test and lint dependencies so that no 116 | unnecessary ones are installed during CI runs -- by :user:`lysnikolaou`. 117 | 118 | *Related issues and pull requests on GitHub:* 119 | :issue:`636`. 120 | 121 | 122 | ---- 123 | 124 | 125 | 1.5.0 (2024-10-22) 126 | ================== 127 | 128 | Bug fixes 129 | --------- 130 | 131 | - An incorrect signature of the ``__class_getitem__`` class method 132 | has been fixed, adding a missing ``class_item`` argument under 133 | Python 3.8 and older. 134 | 135 | This change also improves the code coverage of this method that 136 | was previously missing -- by :user:`webknjaz`. 137 | 138 | 139 | *Related issues and pull requests on GitHub:* 140 | :issue:`567`, :issue:`571`. 141 | 142 | 143 | Improved documentation 144 | ---------------------- 145 | 146 | - Rendered issue, PR, and commit links now lead to 147 | ``frozenlist``'s repo instead of ``yarl``'s repo. 148 | 149 | 150 | *Related issues and pull requests on GitHub:* 151 | :issue:`573`. 152 | 153 | - On the :doc:`Contributing docs ` page, 154 | a link to the ``Towncrier philosophy`` has been fixed. 155 | 156 | 157 | *Related issues and pull requests on GitHub:* 158 | :issue:`574`. 159 | 160 | 161 | Packaging updates and notes for downstreams 162 | ------------------------------------------- 163 | 164 | - A name of a temporary building directory now reflects 165 | that it's related to ``frozenlist``, not ``yarl``. 166 | 167 | 168 | *Related issues and pull requests on GitHub:* 169 | :issue:`573`. 170 | 171 | - Declared Python 3.13 supported officially in the distribution package metadata. 172 | 173 | 174 | *Related issues and pull requests on GitHub:* 175 | :issue:`595`. 176 | 177 | 178 | ---- 179 | 180 | 181 | 1.4.1 (2023-12-15) 182 | ================== 183 | 184 | Packaging updates and notes for downstreams 185 | ------------------------------------------- 186 | 187 | - Declared Python 3.12 and PyPy 3.8-3.10 supported officially 188 | in the distribution package metadata. 189 | 190 | 191 | *Related issues and pull requests on GitHub:* 192 | :issue:`553`. 193 | 194 | - Replaced the packaging is replaced from an old-fashioned :file:`setup.py` to an 195 | in-tree :pep:`517` build backend -- by :user:`webknjaz`. 196 | 197 | Whenever the end-users or downstream packagers need to build ``frozenlist`` 198 | from source (a Git checkout or an sdist), they may pass a ``config_settings`` 199 | flag ``pure-python``. If this flag is not set, a C-extension will be built 200 | and included into the distribution. 201 | 202 | Here is how this can be done with ``pip``: 203 | 204 | .. code-block:: console 205 | 206 | $ python3 -m pip install . --config-settings=pure-python= 207 | 208 | This will also work with ``-e | --editable``. 209 | 210 | The same can be achieved via ``pypa/build``: 211 | 212 | .. code-block:: console 213 | 214 | $ python3 -m build --config-setting=pure-python= 215 | 216 | Adding ``-w | --wheel`` can force ``pypa/build`` produce a wheel from source 217 | directly, as opposed to building an ``sdist`` and then building from it. 218 | 219 | 220 | *Related issues and pull requests on GitHub:* 221 | :issue:`560`. 222 | 223 | 224 | Contributor-facing changes 225 | -------------------------- 226 | 227 | - It is now possible to request line tracing in Cython builds using the 228 | ``with-cython-tracing`` :pep:`517` config setting 229 | -- :user:`webknjaz`. 230 | 231 | This can be used in CI and development environment to measure coverage 232 | on Cython modules, but is not normally useful to the end-users or 233 | downstream packagers. 234 | 235 | Here's a usage example: 236 | 237 | .. code-block:: console 238 | 239 | $ python3 -Im pip install . --config-settings=with-cython-tracing=true 240 | 241 | For editable installs, this setting is on by default. Otherwise, it's 242 | off unless requested explicitly. 243 | 244 | The following produces C-files required for the Cython coverage 245 | plugin to map the measurements back to the PYX-files: 246 | 247 | .. code-block:: console 248 | 249 | $ python -Im pip install -e . 250 | 251 | Alternatively, the ``FROZENLIST_CYTHON_TRACING=1`` environment variable 252 | can be set to do the same as the :pep:`517` config setting. 253 | 254 | 255 | *Related issues and pull requests on GitHub:* 256 | :issue:`560`. 257 | 258 | - Coverage collection has been implemented for the Cython modules 259 | -- by :user:`webknjaz`. 260 | 261 | It will also be reported to Codecov from any non-release CI jobs. 262 | 263 | 264 | *Related issues and pull requests on GitHub:* 265 | :issue:`561`. 266 | 267 | - A step-by-step :doc:`Release Guide ` guide has 268 | been added, describing how to release *frozenlist* -- by :user:`webknjaz`. 269 | 270 | This is primarily targeting the maintainers. 271 | 272 | 273 | *Related issues and pull requests on GitHub:* 274 | :issue:`563`. 275 | 276 | - Detailed :doc:`Contributing Guidelines ` on 277 | authoring the changelog fragments have been published in the 278 | documentation -- by :user:`webknjaz`. 279 | 280 | 281 | *Related issues and pull requests on GitHub:* 282 | :issue:`564`. 283 | 284 | 285 | ---- 286 | 287 | 288 | 1.4.0 (2023-07-12) 289 | ================== 290 | 291 | The published source distribution package became buildable 292 | under Python 3.12. 293 | 294 | 295 | ---- 296 | 297 | 298 | Bugfixes 299 | -------- 300 | 301 | - Removed an unused :py:data:`typing.Tuple` import 302 | `#411 `_ 303 | 304 | 305 | Deprecations and Removals 306 | ------------------------- 307 | 308 | - Dropped Python 3.7 support. 309 | `#413 `_ 310 | 311 | 312 | Misc 313 | ---- 314 | 315 | - `#410 `_, `#433 `_ 316 | 317 | 318 | ---- 319 | 320 | 321 | 1.3.3 (2022-11-08) 322 | ================== 323 | 324 | - Fixed CI runs when creating a new release, where new towncrier versions 325 | fail when the current version section is already present. 326 | 327 | 328 | ---- 329 | 330 | 331 | 1.3.2 (2022-11-08) 332 | ================== 333 | 334 | Misc 335 | ---- 336 | 337 | - Updated the CI runs to better check for test results and to avoid deprecated syntax. `#327 `_ 338 | 339 | 340 | ---- 341 | 342 | 343 | 1.3.1 (2022-08-02) 344 | ================== 345 | 346 | The published source distribution package became buildable 347 | under Python 3.11. 348 | 349 | 350 | ---- 351 | 352 | 353 | 1.3.0 (2022-01-18) 354 | ================== 355 | 356 | Bugfixes 357 | -------- 358 | 359 | - Do not install C sources with binary distributions. 360 | `#250 `_ 361 | 362 | 363 | Deprecations and Removals 364 | ------------------------- 365 | 366 | - Dropped Python 3.6 support 367 | `#274 `_ 368 | 369 | 370 | ---- 371 | 372 | 373 | 1.2.0 (2021-10-16) 374 | ================== 375 | 376 | Features 377 | -------- 378 | 379 | - ``FrozenList`` now supports being used as a generic type as per PEP 585, e.g. ``frozen_int_list: FrozenList[int]`` (requires Python 3.9 or newer). 380 | `#172 `_ 381 | - Added support for Python 3.10. 382 | `#227 `_ 383 | - Started shipping platform-specific wheels with the ``musl`` tag targeting typical Alpine Linux runtimes. 384 | `#227 `_ 385 | - Started shipping platform-specific arm64 wheels for Apple Silicon. 386 | `#227 `_ 387 | 388 | 389 | ---- 390 | 391 | 392 | 1.1.1 (2020-11-14) 393 | ================== 394 | 395 | Bugfixes 396 | -------- 397 | 398 | - Provide x86 Windows wheels. 399 | `#169 `_ 400 | 401 | 402 | ---- 403 | 404 | 405 | 1.1.0 (2020-10-13) 406 | ================== 407 | 408 | Features 409 | -------- 410 | 411 | - Add support for hashing of a frozen list. 412 | `#136 `_ 413 | 414 | - Support Python 3.8 and 3.9. 415 | 416 | - Provide wheels for ``aarch64``, ``i686``, ``ppc64le``, ``s390x`` architectures on 417 | Linux as well as ``x86_64``. 418 | 419 | 420 | ---- 421 | 422 | 423 | 1.0.0 (2019-11-09) 424 | ================== 425 | 426 | Deprecations and Removals 427 | ------------------------- 428 | 429 | - Dropped support for Python 3.5; only 3.6, 3.7 and 3.8 are supported going forward. 430 | `#24 `_ 431 | -------------------------------------------------------------------------------- /CHANGES/.TEMPLATE.rst: -------------------------------------------------------------------------------- 1 | {# TOWNCRIER TEMPLATE #} 2 | 3 | *({{ versiondata.date }})* 4 | 5 | {% for section, _ in sections.items() %} 6 | {% set underline = underlines[0] %}{% if section %}{{section}} 7 | {{ underline * section|length }}{% set underline = underlines[1] %} 8 | 9 | {% endif %} 10 | 11 | {% if sections[section] %} 12 | {% for category, val in definitions.items() if category in sections[section]%} 13 | {{ definitions[category]['name'] }} 14 | {{ underline * definitions[category]['name']|length }} 15 | 16 | {% if definitions[category]['showcontent'] %} 17 | {% for text, change_note_refs in sections[section][category].items() %} 18 | - {{ text }} 19 | 20 | {{- '\n' * 2 -}} 21 | 22 | {#- 23 | NOTE: Replacing 'e' with 'f' is a hack that prevents Jinja's `int` 24 | NOTE: filter internal implementation from treating the input as an 25 | NOTE: infinite float when it looks like a scientific notation (with a 26 | NOTE: single 'e' char in between digits), raising an `OverflowError`, 27 | NOTE: subsequently. 'f' is still a hex letter so it won't affect the 28 | NOTE: check for whether it's a (short or long) commit hash or not. 29 | Ref: https://github.com/pallets/jinja/issues/1921 30 | -#} 31 | {%- 32 | set pr_issue_numbers = change_note_refs 33 | | map('lower') 34 | | map('replace', 'e', 'f') 35 | | map('int', default=None) 36 | | select('integer') 37 | | map('string') 38 | | list 39 | -%} 40 | {%- set arbitrary_refs = [] -%} 41 | {%- set commit_refs = [] -%} 42 | {%- with -%} 43 | {%- set commit_ref_candidates = change_note_refs | reject('in', pr_issue_numbers) -%} 44 | {%- for cf in commit_ref_candidates -%} 45 | {%- if cf | length in (7, 8, 40) and cf | int(default=None, base=16) is not none -%} 46 | {%- set _ = commit_refs.append(cf) -%} 47 | {%- else -%} 48 | {%- set _ = arbitrary_refs.append(cf) -%} 49 | {%- endif -%} 50 | {%- endfor -%} 51 | {%- endwith -%} 52 | 53 | {% if pr_issue_numbers %} 54 | *Related issues and pull requests on GitHub:* 55 | :issue:`{{ pr_issue_numbers | join('`, :issue:`') }}`. 56 | {{- '\n' * 2 -}} 57 | {%- endif -%} 58 | 59 | {% if commit_refs %} 60 | *Related commits on GitHub:* 61 | :commit:`{{ commit_refs | join('`, :commit:`') }}`. 62 | {{- '\n' * 2 -}} 63 | {%- endif -%} 64 | 65 | {% if arbitrary_refs %} 66 | *Unlinked references:* 67 | {{ arbitrary_refs | join(', ') }}. 68 | {{- '\n' * 2 -}} 69 | {%- endif -%} 70 | 71 | {% endfor %} 72 | {% else %} 73 | - {{ sections[section][category]['']|join(', ') }} 74 | 75 | {% endif %} 76 | {% if sections[section][category]|length == 0 %} 77 | No significant changes. 78 | 79 | {% else %} 80 | {% endif %} 81 | 82 | {% endfor %} 83 | {% else %} 84 | No significant changes. 85 | 86 | 87 | {% endif %} 88 | {% endfor %} 89 | ---- 90 | {{ '\n' * 2 }} 91 | -------------------------------------------------------------------------------- /CHANGES/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.TEMPLATE.rst 3 | !.gitignore 4 | !README.rst 5 | !*.bugfix 6 | !*.bugfix.rst 7 | !*.bugfix.*.rst 8 | !*.breaking 9 | !*.breaking.rst 10 | !*.breaking.*.rst 11 | !*.contrib 12 | !*.contrib.rst 13 | !*.contrib.*.rst 14 | !*.deprecation 15 | !*.deprecation.rst 16 | !*.deprecation.*.rst 17 | !*.doc 18 | !*.doc.rst 19 | !*.doc.*.rst 20 | !*.feature 21 | !*.feature.rst 22 | !*.feature.*.rst 23 | !*.misc 24 | !*.misc.rst 25 | !*.misc.*.rst 26 | !*.packaging 27 | !*.packaging.rst 28 | !*.packaging.*.rst 29 | -------------------------------------------------------------------------------- /CHANGES/README.rst: -------------------------------------------------------------------------------- 1 | .. _Adding change notes with your PRs: 2 | 3 | Adding change notes with your PRs 4 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 5 | 6 | It is very important to maintain a log for news of how 7 | updating to the new version of the software will affect 8 | end-users. This is why we enforce collection of the change 9 | fragment files in pull requests as per `Towncrier philosophy`_. 10 | 11 | The idea is that when somebody makes a change, they must record 12 | the bits that would affect end-users only including information 13 | that would be useful to them. Then, when the maintainers publish 14 | a new release, they'll automatically use these records to compose 15 | a change log for the respective version. It is important to 16 | understand that including unnecessary low-level implementation 17 | related details generates noise that is not particularly useful 18 | to the end-users most of the time. And so such details should be 19 | recorded in the Git history rather than a changelog. 20 | 21 | Alright! So how to add a news fragment? 22 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 23 | 24 | ``frozenlist`` uses `towncrier `_ 25 | for changelog management. 26 | To submit a change note about your PR, add a text file into the 27 | ``CHANGES/`` folder. It should contain an 28 | explanation of what applying this PR will change in the way 29 | end-users interact with the project. One sentence is usually 30 | enough but feel free to add as many details as you feel necessary 31 | for the users to understand what it means. 32 | 33 | **Use the past tense** for the text in your fragment because, 34 | combined with others, it will be a part of the "news digest" 35 | telling the readers **what changed** in a specific version of 36 | the library *since the previous version*. You should also use 37 | *reStructuredText* syntax for highlighting code (inline or block), 38 | linking parts of the docs or external sites. 39 | However, you do not need to reference the issue or PR numbers here 40 | as *towncrier* will automatically add a reference to all of the 41 | affected issues when rendering the news file. 42 | If you wish to sign your change, feel free to add ``-- by 43 | :user:`github-username``` at the end (replace ``github-username`` 44 | with your own!). 45 | 46 | Finally, name your file following the convention that Towncrier 47 | understands: it should start with the number of an issue or a 48 | PR followed by a dot, then add a patch type, like ``feature``, 49 | ``doc``, ``contrib`` etc., and add ``.rst`` as a suffix. If you 50 | need to add more than one fragment, you may add an optional 51 | sequence number (delimited with another period) between the type 52 | and the suffix. 53 | 54 | In general the name will follow ``..rst`` pattern, 55 | where the categories are: 56 | 57 | - ``bugfix``: A bug fix for something we deemed an improper undesired 58 | behavior that got corrected in the release to match pre-agreed 59 | expectations. 60 | - ``feature``: A new behavior, public APIs. That sort of stuff. 61 | - ``deprecation``: A declaration of future API removals and breaking 62 | changes in behavior. 63 | - ``breaking``: When something public gets removed in a breaking way. 64 | Could be deprecated in an earlier release. 65 | - ``doc``: Notable updates to the documentation structure or build 66 | process. 67 | - ``packaging``: Notes for downstreams about unobvious side effects 68 | and tooling. Changes in the test invocation considerations and 69 | runtime assumptions. 70 | - ``contrib``: Stuff that affects the contributor experience. e.g. 71 | Running tests, building the docs, setting up the development 72 | environment. 73 | - ``misc``: Changes that are hard to assign to any of the above 74 | categories. 75 | 76 | A pull request may have more than one of these components, for example 77 | a code change may introduce a new feature that deprecates an old 78 | feature, in which case two fragments should be added. It is not 79 | necessary to make a separate documentation fragment for documentation 80 | changes accompanying the relevant code changes. 81 | 82 | Examples for adding changelog entries to your Pull Requests 83 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 84 | 85 | File :file:`CHANGES/603.removal.1.rst`: 86 | 87 | .. code-block:: rst 88 | 89 | Dropped Python 3.5 support; Python 3.6 is the minimal supported Python 90 | version -- by :user:`webknjaz`. 91 | 92 | File :file:`CHANGES/550.bugfix.rst`: 93 | 94 | .. code-block:: rst 95 | 96 | Started shipping Windows wheels for the x86 architecture 97 | -- by :user:`Dreamsorcerer`. 98 | 99 | File :file:`CHANGES/553.feature.rst`: 100 | 101 | .. code-block:: rst 102 | 103 | Added support for ``GenericAliases`` (``MultiDict[str]``) under Python 3.9 104 | and higher -- by :user:`mjpieters`. 105 | 106 | .. tip:: 107 | 108 | See :file:`towncrier.toml` for all available categories 109 | (``tool.towncrier.type``). 110 | 111 | .. _Towncrier philosophy: 112 | https://towncrier.readthedocs.io/en/stable/#philosophy 113 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at andrew.svetlov@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTORS.txt: -------------------------------------------------------------------------------- 1 | - Contributors - 2 | ---------------- 3 | Andrew Svetlov 4 | Edgar Ramírez-Mondragón 5 | Marcin Konowalczyk 6 | Martijn Pieters 7 | Pau Freixes 8 | Paul Colomiets 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2013-2019 Nikolay Kim and Andrew Svetlov 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include .coveragerc 2 | include pyproject.toml 3 | include pytest.ini 4 | include LICENSE 5 | include CHANGES.rst 6 | include README.rst 7 | include CONTRIBUTORS.txt 8 | graft .github/actions 9 | graft frozenlist 10 | graft packaging 11 | graft docs 12 | graft requirements 13 | graft tests 14 | global-exclude *.pyc 15 | global-exclude *.pyd 16 | global-exclude *.so 17 | global-exclude *.lib 18 | global-exclude *.dll 19 | global-exclude *.a 20 | global-exclude *.obj 21 | exclude frozenlist/*.c 22 | exclude frozenlist/*.html 23 | prune docs/_build 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Some simple testing tasks (sorry, UNIX only). 2 | 3 | PYXS = $(wildcard frozenlist/*.pyx) 4 | SRC = frozenlist tests setup.py 5 | 6 | all: test 7 | 8 | .install-cython: 9 | pip install -r requirements/cython.txt 10 | touch .install-cython 11 | 12 | frozenlist/%.c: frozenlist/%.pyx 13 | cython -3 -o $@ $< -I frozenlist 14 | 15 | cythonize: .install-cython $(PYXS:.pyx=.c) 16 | 17 | .install-deps: $(shell find requirements -type f) 18 | pip install -r requirements/lint.txt -r requirements/test.txt 19 | ifndef CI 20 | pre-commit install 21 | endif 22 | @touch .install-deps 23 | 24 | lint: .install-deps 25 | ifndef CI 26 | python -Im pre_commit run --all-files --show-diff-on-failure 27 | else 28 | python -Im pre_commit run --all-files 29 | endif 30 | 31 | .develop: .install-deps $(shell find frozenlist -type f) lint 32 | pip install -e . --config-settings=pure-python=false 33 | @touch .develop 34 | 35 | test: .develop 36 | @pytest -rxXs -q 37 | 38 | vtest: .develop 39 | @pytest -rxXs -s -v 40 | 41 | cov cover coverage: 42 | tox 43 | 44 | cov-dev: .develop 45 | @pytest -rxXs --cov-report=html 46 | @echo "open file://`pwd`/htmlcov/index.html" 47 | 48 | cov-ci-run: .develop 49 | @echo "Regular run" 50 | @pytest -rxXs --cov-report=html 51 | 52 | cov-dev-full: cov-ci-run 53 | @echo "open file://`pwd`/htmlcov/index.html" 54 | 55 | clean: 56 | @rm -rf `find . -name __pycache__` 57 | @rm -f `find . -type f -name '*.py[co]' ` 58 | @rm -f `find . -type f -name '*~' ` 59 | @rm -f `find . -type f -name '.*~' ` 60 | @rm -f `find . -type f -name '@*' ` 61 | @rm -f `find . -type f -name '#*#' ` 62 | @rm -f `find . -type f -name '*.orig' ` 63 | @rm -f `find . -type f -name '*.rej' ` 64 | @rm -f .coverage 65 | @rm -rf htmlcov 66 | @rm -rf build 67 | @rm -rf cover 68 | @make -C docs clean 69 | @python setup.py clean 70 | @rm -f frozenlist/_frozenlist.html 71 | @rm -f frozenlist/_frozenlist.c 72 | @rm -f frozenlist/_frozenlist.*.so 73 | @rm -f frozenlist/_frozenlist.*.pyd 74 | @rm -rf .tox 75 | @rm -f .develop 76 | @rm -f .flake 77 | @rm -f .install-deps 78 | @rm -rf frozenlist.egg-info 79 | 80 | doc: 81 | @make -C docs html SPHINXOPTS="-W --keep-going -n -E" 82 | @echo "open file://`pwd`/docs/_build/html/index.html" 83 | 84 | doc-spelling: 85 | @make -C docs spelling SPHINXOPTS="-W --keep-going -n -E" 86 | 87 | install: 88 | @pip install -U 'pip' 89 | @pip install -Ur requirements/dev.txt 90 | 91 | install-dev: .develop 92 | 93 | .PHONY: all build test vtest cov clean doc lint 94 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | frozenlist 2 | ========== 3 | 4 | .. image:: https://github.com/aio-libs/frozenlist/workflows/CI/badge.svg 5 | :target: https://github.com/aio-libs/frozenlist/actions 6 | :alt: GitHub status for master branch 7 | 8 | .. image:: https://codecov.io/gh/aio-libs/frozenlist/branch/master/graph/badge.svg?flag=pytest 9 | :target: https://codecov.io/gh/aio-libs/frozenlist?flags[]=pytest 10 | :alt: codecov.io status for master branch 11 | 12 | .. image:: https://img.shields.io/pypi/v/frozenlist.svg?logo=Python&logoColor=white 13 | :target: https://pypi.org/project/frozenlist 14 | :alt: frozenlist @ PyPI 15 | 16 | .. image:: https://readthedocs.org/projects/frozenlist/badge/?version=latest 17 | :target: https://frozenlist.aio-libs.org 18 | :alt: Read The Docs build status badge 19 | 20 | .. image:: https://img.shields.io/matrix/aio-libs:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat 21 | :target: https://matrix.to/#/%23aio-libs:matrix.org 22 | :alt: Matrix Room — #aio-libs:matrix.org 23 | 24 | .. image:: https://img.shields.io/matrix/aio-libs-space:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs-space%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat 25 | :target: https://matrix.to/#/%23aio-libs-space:matrix.org 26 | :alt: Matrix Space — #aio-libs-space:matrix.org 27 | 28 | Introduction 29 | ------------ 30 | 31 | ``frozenlist.FrozenList`` is a list-like structure which implements 32 | ``collections.abc.MutableSequence``. The list is *mutable* until ``FrozenList.freeze`` 33 | is called, after which list modifications raise ``RuntimeError``: 34 | 35 | 36 | >>> from frozenlist import FrozenList 37 | >>> fl = FrozenList([17, 42]) 38 | >>> fl.append('spam') 39 | >>> fl.append('Vikings') 40 | >>> fl 41 | 42 | >>> fl.freeze() 43 | >>> fl 44 | 45 | >>> fl.frozen 46 | True 47 | >>> fl.append("Monty") 48 | Traceback (most recent call last): 49 | File "", line 1, in 50 | File "frozenlist/_frozenlist.pyx", line 97, in frozenlist._frozenlist.FrozenList.append 51 | self._check_frozen() 52 | File "frozenlist/_frozenlist.pyx", line 19, in frozenlist._frozenlist.FrozenList._check_frozen 53 | raise RuntimeError("Cannot modify frozen list.") 54 | RuntimeError: Cannot modify frozen list. 55 | 56 | 57 | FrozenList is also hashable, but only when frozen. Otherwise it also throws a RuntimeError: 58 | 59 | 60 | >>> fl = FrozenList([17, 42, 'spam']) 61 | >>> hash(fl) 62 | Traceback (most recent call last): 63 | File "", line 1, in 64 | File "frozenlist/_frozenlist.pyx", line 111, in frozenlist._frozenlist.FrozenList.__hash__ 65 | raise RuntimeError("Cannot hash unfrozen list.") 66 | RuntimeError: Cannot hash unfrozen list. 67 | >>> fl.freeze() 68 | >>> hash(fl) 69 | 3713081631934410656 70 | >>> dictionary = {fl: 'Vikings'} # frozen fl can be a dict key 71 | >>> dictionary 72 | {: 'Vikings'} 73 | 74 | 75 | Installation 76 | ------------ 77 | 78 | :: 79 | 80 | $ pip install frozenlist 81 | 82 | 83 | Documentation 84 | ------------- 85 | 86 | https://frozenlist.aio-libs.org 87 | 88 | Communication channels 89 | ---------------------- 90 | 91 | We have a *Matrix Space* `#aio-libs-space:matrix.org 92 | `_ which is 93 | also accessible via Gitter. 94 | 95 | License 96 | ------- 97 | 98 | ``frozenlist`` is offered under the Apache 2 license. 99 | 100 | Source code 101 | ----------- 102 | 103 | The project is hosted on GitHub_ 104 | 105 | Please file an issue in the `bug tracker 106 | `_ if you have found a bug 107 | or have some suggestions to improve the library. 108 | 109 | .. _GitHub: https://github.com/aio-libs/frozenlist 110 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/frozenlist.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/frozenlist.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/frozenlist" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/frozenlist" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | 179 | spelling: 180 | $(SPHINXBUILD) -b spelling $(ALLSPHINXOPTS) $(BUILDDIR)/spelling 181 | @echo 182 | @echo "Build finished." 183 | -------------------------------------------------------------------------------- /docs/changes.rst: -------------------------------------------------------------------------------- 1 | .. _frozenlist_changes: 2 | 3 | ========= 4 | Changelog 5 | ========= 6 | 7 | .. only:: not is_release 8 | 9 | To be included in v\ |release| (if present) 10 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 11 | 12 | .. towncrier-draft-entries:: |release| [UNRELEASED DRAFT] 13 | 14 | Released versions 15 | ^^^^^^^^^^^^^^^^^ 16 | 17 | .. include:: ../CHANGES.rst 18 | :start-after: .. towncrier release notes start 19 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # frozenlist documentation build configuration file, created by 4 | # sphinx-quickstart on Wed Mar 5 12:35:35 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import os 16 | import re 17 | from contextlib import suppress 18 | from pathlib import Path 19 | 20 | PROJECT_ROOT_DIR = Path(__file__).parents[1].resolve() 21 | IS_RELEASE_ON_RTD = ( 22 | os.getenv("READTHEDOCS", "False") == "True" 23 | and os.environ["READTHEDOCS_VERSION_TYPE"] == "tag" 24 | ) 25 | if IS_RELEASE_ON_RTD: 26 | tags.add("is_release") 27 | 28 | 29 | _docs_path = Path(__file__).parent 30 | _version_path = _docs_path / ".." / "frozenlist" / "__init__.py" 31 | 32 | 33 | with _version_path.open(encoding="utf-8") as fp: 34 | try: 35 | _version_info = re.search( 36 | r'^__version__ = "' 37 | r"(?P\d+)" 38 | r"\.(?P\d+)" 39 | r"\.(?P\d+)" 40 | r'(?P.*)?"$', 41 | fp.read(), 42 | re.M, 43 | ).groupdict() 44 | except IndexError: 45 | raise RuntimeError("Unable to determine version.") 46 | 47 | 48 | # -- General configuration ------------------------------------------------ 49 | 50 | # If your documentation needs a minimal Sphinx version, state it here. 51 | # needs_sphinx = '1.0' 52 | 53 | # Add any Sphinx extension module names here, as strings. They can be 54 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 55 | # ones. 56 | extensions = [ 57 | # stdlib-party extensions: 58 | "sphinx.ext.extlinks", 59 | "sphinx.ext.viewcode", 60 | "sphinx.ext.intersphinx", 61 | # Third-party extensions: 62 | "sphinxcontrib.towncrier.ext", # provides `towncrier-draft-entries` directive 63 | ] 64 | 65 | 66 | with suppress(ImportError): 67 | # spelling extension is optional, only add it when installed 68 | import sphinxcontrib.spelling # noqa # type: ignore 69 | 70 | extensions.append("sphinxcontrib.spelling") 71 | 72 | 73 | intersphinx_mapping = { 74 | "python": ("https://docs.python.org/3", None), 75 | "aiohttp": ("https://docs.aiohttp.org/en/stable/", None), 76 | } 77 | 78 | # Add any paths that contain templates here, relative to this directory. 79 | templates_path = ["_templates"] 80 | 81 | # The suffix of source filenames. 82 | source_suffix = ".rst" 83 | 84 | # The encoding of source files. 85 | # source_encoding = 'utf-8-sig' 86 | 87 | # The master toctree document. 88 | master_doc = "index" 89 | 90 | # General information about the project. 91 | 92 | github_url = "https://github.com" 93 | github_repo_org = "aio-libs" 94 | github_repo_name = "frozenlist" 95 | github_repo_slug = f"{github_repo_org}/{github_repo_name}" 96 | github_repo_url = f"{github_url}/{github_repo_slug}" 97 | github_sponsors_url = f"{github_url}/sponsors" 98 | 99 | project = github_repo_name 100 | copyright = "2013, frozenlist contributors" 101 | 102 | # The version info for the project you're documenting, acts as replacement for 103 | # |version| and |release|, also used in various other places throughout the 104 | # built documents. 105 | # 106 | # The short X.Y version. 107 | version = "{major}.{minor}".format(**_version_info) 108 | # The full version, including alpha/beta/rc tags. 109 | release = "{major}.{minor}.{patch}{tag}".format(**_version_info) 110 | 111 | rst_epilog = f""" 112 | .. |project| replace:: {project} 113 | """ # pylint: disable=invalid-name 114 | 115 | # The language for content autogenerated by Sphinx. Refer to documentation 116 | # for a list of supported languages. 117 | # language = None 118 | 119 | # There are two options for replacing |today|: either, you set today to some 120 | # non-false value, then it is used: 121 | # today = '' 122 | # Else, today_fmt is used as the format for a strftime call. 123 | # today_fmt = '%B %d, %Y' 124 | 125 | # List of patterns, relative to source directory, that match files and 126 | # directories to ignore when looking for source files. 127 | exclude_patterns = ["_build"] 128 | 129 | # The reST default role (used for this markup: `text`) to use for all 130 | # documents. 131 | # default_role = None 132 | 133 | # If true, '()' will be appended to :func: etc. cross-reference text. 134 | # add_function_parentheses = True 135 | 136 | # If true, the current module name will be prepended to all description 137 | # unit titles (such as .. function::). 138 | # add_module_names = True 139 | 140 | # If true, sectionauthor and moduleauthor directives will be shown in the 141 | # output. They are ignored by default. 142 | # show_authors = False 143 | 144 | # The name of the Pygments (syntax highlighting) style to use. 145 | # pygments_style = 'sphinx' 146 | 147 | # The default language to highlight source code in. 148 | highlight_language = "python3" 149 | 150 | # A list of ignored prefixes for module index sorting. 151 | # modindex_common_prefix = [] 152 | 153 | # If true, keep warnings as "system message" paragraphs in the built documents. 154 | # keep_warnings = False 155 | 156 | 157 | # -- Extension configuration ------------------------------------------------- 158 | 159 | # -- Options for extlinks extension --------------------------------------- 160 | extlinks = { 161 | "issue": (f"{github_repo_url}/issues/%s", "#%s"), 162 | "pr": (f"{github_repo_url}/pull/%s", "PR #%s"), 163 | "commit": (f"{github_repo_url}/commit/%s", "%s"), 164 | "gh": (f"{github_url}/%s", "GitHub: %s"), 165 | "user": (f"{github_sponsors_url}/%s", "@%s"), 166 | } 167 | 168 | 169 | # -- Options for HTML output ---------------------------------------------- 170 | 171 | # The theme to use for HTML and HTML Help pages. See the documentation for 172 | # a list of builtin themes. 173 | html_theme = "aiohttp_theme" 174 | 175 | # Theme options are theme-specific and customize the look and feel of a theme 176 | # further. For a list of options available for each theme, see the 177 | # documentation. 178 | html_theme_options = { 179 | "logo": None, 180 | "description": ( 181 | "A list-like structure which implements collections.abc.MutableSequence" 182 | ), 183 | "canonical_url": "https://frozenlist.aio-libs.org/en/stable/", 184 | "github_user": github_repo_org, 185 | "github_repo": github_repo_name, 186 | "github_button": True, 187 | "github_type": "star", 188 | "github_banner": True, 189 | "badges": [ 190 | { 191 | "image": f"{github_repo_url}/workflows/CI/badge.svg", 192 | "target": f"{github_repo_url}/actions", 193 | "height": "20", 194 | "alt": "Github CI status for master branch", 195 | }, 196 | { 197 | "image": ( 198 | f"https://codecov.io/github/{github_repo_slug}/coverage.svg" 199 | "?branch=master&flag=pytest" 200 | ), 201 | "target": f"https://codecov.io/github/{github_repo_slug}?flags[]=pytest", 202 | "height": "20", 203 | "alt": "Code coverage status", 204 | }, 205 | { 206 | "image": f"https://badge.fury.io/py/{github_repo_name}.svg", 207 | "target": f"https://badge.fury.io/py/{github_repo_name}", 208 | "height": "20", 209 | "alt": "Latest PyPI package version", 210 | }, 211 | { 212 | "image": "https://img.shields.io/matrix/aio-libs-space:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs-space%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat", 213 | "target": "https://matrix.to/#/%23aio-libs-space:matrix.org", 214 | "height": "20", 215 | "alt": "Matrix Space", 216 | }, 217 | ], 218 | } 219 | 220 | # Add any paths that contain custom themes here, relative to this directory. 221 | # html_theme_path = [alabaster.get_path()] 222 | 223 | # The name for this set of Sphinx documents. If None, it defaults to 224 | # " v documentation". 225 | # html_title = None 226 | 227 | # A shorter title for the navigation bar. Default is the same as html_title. 228 | # html_short_title = None 229 | 230 | # The name of an image file (relative to this directory) to place at the top 231 | # of the sidebar. 232 | # html_logo = 'frozenlist-icon.svg' 233 | 234 | # The name of an image file (within the static path) to use as favicon of the 235 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 236 | # pixels large. 237 | # html_favicon = 'favicon.ico' 238 | 239 | # Add any paths that contain custom static files (such as style sheets) here, 240 | # relative to this directory. They are copied after the builtin static files, 241 | # so a file named "default.css" will overwrite the builtin "default.css". 242 | # html_static_path = [] 243 | 244 | # Add any extra paths that contain custom files (such as robots.txt or 245 | # .htaccess) here, relative to this directory. These files are copied 246 | # directly to the root of the documentation. 247 | # html_extra_path = [] 248 | 249 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 250 | # using the given strftime format. 251 | # html_last_updated_fmt = '%b %d, %Y' 252 | 253 | # If true, SmartyPants will be used to convert quotes and dashes to 254 | # typographically correct entities. 255 | # html_use_smartypants = True 256 | 257 | # Custom sidebar templates, maps document names to template names. 258 | html_sidebars = { 259 | "**": [ 260 | "about.html", 261 | "navigation.html", 262 | "searchbox.html", 263 | ] 264 | } 265 | 266 | # Additional templates that should be rendered to pages, maps page names to 267 | # template names. 268 | # html_additional_pages = {} 269 | 270 | # If false, no module index is generated. 271 | # html_domain_indices = True 272 | 273 | # If false, no index is generated. 274 | # html_use_index = True 275 | 276 | # If true, the index is split into individual pages for each letter. 277 | # html_split_index = False 278 | 279 | # If true, links to the reST sources are added to the pages. 280 | # html_show_sourcelink = True 281 | 282 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 283 | # html_show_sphinx = True 284 | 285 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 286 | # html_show_copyright = True 287 | 288 | # If true, an OpenSearch description file will be output, and all pages will 289 | # contain a tag referring to it. The value of this option must be the 290 | # base URL from which the finished HTML is served. 291 | # html_use_opensearch = '' 292 | 293 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 294 | # html_file_suffix = None 295 | 296 | # Output file base name for HTML help builder. 297 | htmlhelp_basename = f"{github_repo_name}doc" 298 | 299 | 300 | # -- Options for LaTeX output --------------------------------------------- 301 | 302 | latex_elements = { 303 | # The paper size ('letterpaper' or 'a4paper'). 304 | # 'papersize': 'letterpaper', 305 | # The font size ('10pt', '11pt' or '12pt'). 306 | # 'pointsize': '10pt', 307 | # Additional stuff for the LaTeX preamble. 308 | # 'preamble': '', 309 | } 310 | 311 | # Grouping the document tree into LaTeX files. List of tuples 312 | # (source start file, target name, title, 313 | # author, documentclass [howto, manual, or own class]). 314 | latex_documents = [ 315 | ( 316 | "index", 317 | f"{github_repo_name}.tex", 318 | f"{github_repo_name} Documentation", 319 | f"{github_repo_name} contributors", 320 | "manual", 321 | ), 322 | ] 323 | 324 | # The name of an image file (relative to this directory) to place at the top of 325 | # the title page. 326 | # latex_logo = None 327 | 328 | # For "manual" documents, if this is true, then toplevel headings are parts, 329 | # not chapters. 330 | # latex_use_parts = False 331 | 332 | # If true, show page references after internal links. 333 | # latex_show_pagerefs = False 334 | 335 | # If true, show URL addresses after external links. 336 | # latex_show_urls = False 337 | 338 | # Documents to append as an appendix to all manuals. 339 | # latex_appendices = [] 340 | 341 | # If false, no module index is generated. 342 | # latex_domain_indices = True 343 | 344 | 345 | # -- Options for manual page output --------------------------------------- 346 | 347 | # One entry per manual page. List of tuples 348 | # (source start file, name, description, authors, manual section). 349 | man_pages = [ 350 | ( 351 | "index", 352 | github_repo_name, 353 | f"{github_repo_name} Documentation", 354 | [github_repo_name], 355 | 1, 356 | ) 357 | ] 358 | 359 | # If true, show URL addresses after external links. 360 | # man_show_urls = False 361 | 362 | 363 | # -- Options for Texinfo output ------------------------------------------- 364 | 365 | # Grouping the document tree into Texinfo files. List of tuples 366 | # (source start file, target name, title, author, 367 | # dir menu entry, description, category) 368 | texinfo_documents = [ 369 | ( 370 | "index", 371 | github_repo_name, 372 | f"{github_repo_name} Documentation", 373 | f"{github_repo_name} contributors", 374 | github_repo_name, 375 | "One line description of project.", 376 | "Miscellaneous", 377 | ), 378 | ] 379 | 380 | # Documents to append as an appendix to all manuals. 381 | # texinfo_appendices = [] 382 | 383 | # If false, no module index is generated. 384 | # texinfo_domain_indices = True 385 | 386 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 387 | # texinfo_show_urls = 'footnote' 388 | 389 | # If true, do not generate a @detailmenu in the "Top" node's menu. 390 | # texinfo_no_detailmenu = False 391 | 392 | # -- Strictness options -------------------------------------------------- 393 | nitpicky = True 394 | nitpick_ignore = [] 395 | 396 | # -- Options for towncrier_draft extension ----------------------------------- 397 | 398 | towncrier_draft_autoversion_mode = "draft" # or: 'sphinx-version', 'sphinx-release' 399 | towncrier_draft_include_empty = True 400 | towncrier_draft_working_directory = PROJECT_ROOT_DIR 401 | -------------------------------------------------------------------------------- /docs/contributing/guidelines.rst: -------------------------------------------------------------------------------- 1 | ----------------- 2 | Contributing docs 3 | ----------------- 4 | 5 | We use Sphinx_ to generate our docs website. You can trigger 6 | the process locally by executing: 7 | 8 | .. code-block:: shell-session 9 | 10 | $ make doc 11 | 12 | It is also integrated with `Read The Docs`_ that builds and 13 | publishes each commit to the main branch and generates live 14 | docs previews for each pull request. 15 | 16 | The sources of the Sphinx_ documents use reStructuredText as a 17 | de-facto standard. But in order to make contributing docs more 18 | beginner-friendly, we've integrated `MyST parser`_ allowing us 19 | to also accept new documents written in an extended version of 20 | Markdown that supports using Sphinx directives and roles. `Read 21 | the docs `_ to learn more on how to use it. 22 | 23 | .. _MyST docs: https://myst-parser.readthedocs.io/en/latest/using/intro.html#writing-myst-in-sphinx 24 | .. _MyST parser: https://pypi.org/project/myst-parser/ 25 | .. _Read The Docs: https://readthedocs.org 26 | .. _Sphinx: https://www.sphinx-doc.org 27 | 28 | .. include:: ../../CHANGES/README.rst 29 | -------------------------------------------------------------------------------- /docs/contributing/release_guide.rst: -------------------------------------------------------------------------------- 1 | ************* 2 | Release Guide 3 | ************* 4 | 5 | Welcome to the |project| Release Guide! 6 | 7 | This page contains information on how to release a new version 8 | of |project| using the automated Continuous Delivery pipeline. 9 | 10 | .. tip:: 11 | 12 | The intended audience for this document is maintainers 13 | and core contributors. 14 | 15 | 16 | Pre-release activities 17 | ====================== 18 | 19 | 1. Check if there are any open Pull Requests that could be 20 | desired in the upcoming release. If there are any — merge 21 | them. If some are incomplete, try to get them ready. 22 | Don't forget to review the enclosed change notes per our 23 | guidelines. 24 | 2. Visually inspect the draft section of the :ref:`Changelog` 25 | page. Make sure the content looks consistent, uses the same 26 | writing style, targets the end-users and adheres to our 27 | documented guidelines. 28 | Most of the changelog sections will typically use the past 29 | tense or another way to relay the effect of the changes for 30 | the users, since the previous release. 31 | It should not target core contributors as the information 32 | they are normally interested in is already present in the 33 | Git history. 34 | Update the changelog fragments if you see any problems with 35 | this changelog section. 36 | 3. Optionally, test the previously published nightlies, that are 37 | available through GitHub Actions CI/CD artifacts, locally. 38 | 4. If you are satisfied with the above, inspect the changelog 39 | section categories in the draft. Presence of the breaking 40 | changes or features will hint you what version number 41 | segment to bump for the release. 42 | 5. Update the hardcoded version string in :file:`frozenlist/__init__.py`. 43 | Generate a new changelog from the fragments, and commit it 44 | along with the fragments removal and the Python module changes. 45 | Use the following commands, don't prepend a leading-``v`` before 46 | the version number. Just use the raw version number as per 47 | :pep:`440`. 48 | 49 | .. code-block:: shell-session 50 | 51 | [dir:frozenlist] $ frozenlist/__init__.py 52 | [dir:frozenlist] $ python -m towncrier build \ 53 | -- --version 'VERSION_WITHOUT_LEADING_V' 54 | [dir:frozenlist] $ git commit -v CHANGES{.rst,/} frozenlist/__init__.py 55 | 56 | .. seealso:: 57 | 58 | :ref:`Adding change notes with your PRs` 59 | Writing beautiful changelogs for humans 60 | 61 | 62 | The release stage 63 | ================= 64 | 65 | 1. Tag the commit with version and changelog changes, created 66 | during the preparation stage. If possible, make it GPG-signed. 67 | Prepend a leading ``v`` before the version number for the tag 68 | name. Add an extra sentence describing the release contents, 69 | in a few words. 70 | 71 | .. code-block:: shell-session 72 | 73 | [dir:frozenlist] $ git tag \ 74 | -s 'VERSION_WITH_LEADING_V' \ 75 | -m 'VERSION_WITH_LEADING_V' \ 76 | -m 'This release does X and Y.' 77 | 78 | 79 | 2. Push that tag to the upstream repository, which ``origin`` is 80 | considered to be in the example below. 81 | 82 | .. code-block:: shell-session 83 | 84 | [dir:frozenlist] $ git push origin 'VERSION_WITH_LEADING_V' 85 | 86 | 3. You can open the `GitHub Actions CI/CD workflow page `_ in your web browser to monitor the 88 | progress. But generally, you don't need to babysit the CI. 89 | 4. Check that web page or your email inbox for the notification 90 | with an approval request. GitHub will send it when it reaches 91 | the final "publishing" job. 92 | 5. Approve the deployment and wait for the CD workflow to complete. 93 | 6. Verify that the following things got created: 94 | - a PyPI release 95 | - a Git tag 96 | - a GitHub Releases page 97 | 7. Tell everyone you released a new version of |project| :) 98 | Depending on your mental capacity and the burnout stage, you 99 | are encouraged to post the updates in issues asking for the 100 | next release, contributed PRs, Bluesky, Twitter etc. You can 101 | also call out prominent contributors and thank them! 102 | 103 | 104 | .. _GitHub Actions CI/CD workflow: 105 | https://github.com/aio-libs/frozenlist/actions/workflows/ci-cd.yml 106 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | frozenlist 2 | ========== 3 | 4 | A list-like structure which implements 5 | :class:`collections.abc.MutableSequence`. 6 | 7 | The list is *mutable* until :meth:`~frozenlist.FrozenList.freeze` is called, 8 | after which list modifications raise :exc:`RuntimeError`. A 9 | :class:`~frozenlist.FrozenList` instance is hashable, but only when frozen. 10 | Attempts to hash a non-frozen instance will result in a :exc:`RuntimeError` 11 | exception. 12 | 13 | API 14 | --- 15 | 16 | .. class:: frozenlist.FrozenList(items) 17 | 18 | Construct a new *non-frozen* list from *items* iterable. 19 | 20 | The list implements all :class:`collections.abc.MutableSequence` 21 | methods plus two additional APIs. 22 | 23 | .. attribute:: frozen 24 | 25 | A read-only property, ``True`` is the list is *frozen* 26 | (modifications are forbidden). 27 | 28 | .. method:: freeze() 29 | 30 | Freeze the list. There is no way to *thaw* it back. 31 | 32 | 33 | Installation 34 | ------------ 35 | 36 | .. code-block:: bash 37 | 38 | $ pip install frozenlist 39 | 40 | 41 | Documentation 42 | ============= 43 | 44 | https://frozenlist.aio-libs.org 45 | 46 | Communication channels 47 | ====================== 48 | 49 | We have a *Matrix Space* `#aio-libs-space:matrix.org 50 | `_ which is 51 | also accessible via Gitter. 52 | 53 | License 54 | ======= 55 | 56 | ``frozenlist`` is offered under the Apache 2 license. 57 | 58 | Source code 59 | =========== 60 | 61 | The project is hosted on GitHub_ 62 | 63 | Please file an issue in the `bug tracker 64 | `_ if you have found a bug 65 | or have some suggestions to improve the library. 66 | 67 | Authors and License 68 | =================== 69 | 70 | The ``frozenlist`` package was originally part of the 71 | :doc:`aiohttp project `, written by Nikolay Kim and Andrew Svetlov. 72 | It is now being maintained by Martijn Pieters. 73 | 74 | It's *Apache 2* licensed and freely available. 75 | 76 | Feel free to improve this package and send a pull request to GitHub_. 77 | 78 | 79 | .. toctree:: 80 | :maxdepth: 2 81 | 82 | .. toctree:: 83 | :caption: What's new 84 | 85 | changes 86 | 87 | .. toctree:: 88 | :caption: Contributing 89 | 90 | contributing/guidelines 91 | 92 | .. toctree:: 93 | :caption: Maintenance 94 | 95 | contributing/release_guide 96 | 97 | 98 | Indices and tables 99 | ================== 100 | 101 | * :ref:`genindex` 102 | * :ref:`search` 103 | 104 | 105 | .. _GitHub: https://github.com/aio-libs/frozenlist 106 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\frozenlist.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\frozenlist.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /docs/spelling_wordlist.txt: -------------------------------------------------------------------------------- 1 | abc 2 | aiodns 3 | aioes 4 | aiohttp 5 | aiohttpdemo 6 | aiohttp’s 7 | aiopg 8 | alives 9 | api 10 | api’s 11 | app 12 | apps 13 | app’s 14 | arg 15 | armv 16 | Arsenic 17 | async 18 | asyncio 19 | auth 20 | autocalculated 21 | autodetection 22 | autogenerates 23 | autogeneration 24 | awaitable 25 | backend 26 | backends 27 | Backport 28 | Backporting 29 | backport 30 | backports 31 | BaseEventLoop 32 | basename 33 | BasicAuth 34 | bdist 35 | bdists 36 | Bluesky 37 | BodyPartReader 38 | boolean 39 | botocore 40 | Bugfixes 41 | buildable 42 | builtin 43 | bugfix 44 | BytesIO 45 | cchardet 46 | cChardet 47 | changelog 48 | changelogs 49 | Changelog 50 | charset 51 | charsetdetect 52 | chunked 53 | chunking 54 | CIMultiDict 55 | ClientSession 56 | cls 57 | cmd 58 | codec 59 | Codecov 60 | Codings 61 | committer 62 | committers 63 | config 64 | Config 65 | configs 66 | conjunction 67 | contextmanager 68 | CookieJar 69 | coroutine 70 | Coroutine 71 | coroutines 72 | cpu 73 | CPython 74 | css 75 | ctor 76 | Ctrl 77 | Cython 78 | cythonized 79 | Cythonize 80 | de 81 | deduplicate 82 | # de-facto: 83 | deprecations 84 | DER 85 | dev 86 | Dev 87 | dict 88 | Dict 89 | Discord 90 | django 91 | Django 92 | dns 93 | DNSResolver 94 | docstring 95 | downstreams 96 | Dup 97 | elasticsearch 98 | encodings 99 | env 100 | environ 101 | eof 102 | epoll 103 | Facebook 104 | facto 105 | fallback 106 | fallbacks 107 | filename 108 | finalizers 109 | frontend 110 | frozenlist 111 | getall 112 | gethostbyname 113 | github 114 | Gitter 115 | google 116 | GPG 117 | gunicorn 118 | gunicorn’s 119 | gzipped 120 | hackish 121 | hardcoded 122 | hashable 123 | highlevel 124 | hostnames 125 | HTTPException 126 | HttpProcessingError 127 | httpretty 128 | https 129 | impl 130 | incapsulates 131 | Indices 132 | infos 133 | initializer 134 | inline 135 | intaking 136 | io 137 | ip 138 | IP 139 | ipdb 140 | IPv 141 | ish 142 | iterable 143 | iterables 144 | javascript 145 | Jinja 146 | json 147 | keepalive 148 | keepalived 149 | keepalives 150 | keepaliving 151 | kwarg 152 | latin 153 | linux 154 | localhost 155 | Locator 156 | login 157 | lookup 158 | lookups 159 | lossless 160 | Mako 161 | Martijn 162 | manylinux 163 | metadata 164 | microservice 165 | middleware 166 | middlewares 167 | miltidict 168 | misbehaviors 169 | Mixcloud 170 | Mongo 171 | msg 172 | MsgType 173 | multi 174 | multidict 175 | multidicts 176 | multidict’s 177 | Multidicts 178 | multipart 179 | Multipart 180 | Nagle 181 | Nagle’s 182 | namedtuple 183 | nameservers 184 | namespace 185 | nginx 186 | Nginx 187 | nightlies 188 | Nikolay 189 | noop 190 | nowait 191 | OAuth 192 | Online 193 | optimizations 194 | os 195 | outcoming 196 | Overridable 197 | Paolini 198 | param 199 | params 200 | pathlib 201 | peername 202 | Pieters 203 | ping 204 | pipelining 205 | pluggable 206 | plugin 207 | poller 208 | pong 209 | Postgres 210 | pre 211 | programmatically 212 | proxied 213 | PRs 214 | pubsub 215 | Punycode 216 | py 217 | pyenv 218 | pyflakes 219 | pytest 220 | Pytest 221 | Quickstart 222 | quote’s 223 | readonly 224 | readpayload 225 | rebase 226 | redirections 227 | Redis 228 | refactor 229 | Refactor 230 | refactored 231 | refactoring 232 | reStructuredText 233 | regex 234 | regexps 235 | regexs 236 | reloader 237 | renderer 238 | renderers 239 | repo 240 | repr 241 | repr’s 242 | RequestContextManager 243 | request’s 244 | Request’s 245 | requote 246 | requoting 247 | resolvehost 248 | resolvers 249 | reusage 250 | reuseconn 251 | Runit 252 | runtimes 253 | sa 254 | Satisfiable 255 | schemas 256 | sdist 257 | sdists 258 | sendfile 259 | serializable 260 | shourtcuts 261 | skipuntil 262 | Skyscanner 263 | SocketSocketTransport 264 | ssl 265 | SSLContext 266 | startup 267 | subapplication 268 | subclasses 269 | submodules 270 | subpackage 271 | subprotocol 272 | subprotocols 273 | subtype 274 | supervisord 275 | Supervisord 276 | Svetlov 277 | symlink 278 | symlinks 279 | syscall 280 | syscalls 281 | Systemd 282 | tarball 283 | TCP 284 | TLS 285 | teardown 286 | Teardown 287 | TestClient 288 | Testsuite 289 | Tf 290 | timestamps 291 | toolbar 292 | toplevel 293 | towncrier 294 | tp 295 | tuples 296 | UI 297 | un 298 | unawaited 299 | unclosed 300 | unicode 301 | unittest 302 | Unittest 303 | unix 304 | unsets 305 | unstripped 306 | upstr 307 | url 308 | urldispatcher 309 | urlencoded 310 | urls 311 | url’s 312 | utf 313 | utils 314 | uvloop 315 | vcvarsall 316 | waituntil 317 | webapp 318 | websocket 319 | websockets 320 | websocket’s 321 | Websockets 322 | wildcard 323 | Workflow 324 | ws 325 | wsgi 326 | WSMessage 327 | WSMsgType 328 | wss 329 | www 330 | xxx 331 | -------------------------------------------------------------------------------- /frozenlist/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import types 3 | from collections.abc import MutableSequence 4 | from functools import total_ordering 5 | 6 | __version__ = "1.6.1" 7 | 8 | __all__ = ("FrozenList", "PyFrozenList") # type: Tuple[str, ...] 9 | 10 | 11 | NO_EXTENSIONS = bool(os.environ.get("FROZENLIST_NO_EXTENSIONS")) # type: bool 12 | 13 | 14 | @total_ordering 15 | class FrozenList(MutableSequence): 16 | __slots__ = ("_frozen", "_items") 17 | __class_getitem__ = classmethod(types.GenericAlias) 18 | 19 | def __init__(self, items=None): 20 | self._frozen = False 21 | if items is not None: 22 | items = list(items) 23 | else: 24 | items = [] 25 | self._items = items 26 | 27 | @property 28 | def frozen(self): 29 | return self._frozen 30 | 31 | def freeze(self): 32 | self._frozen = True 33 | 34 | def __getitem__(self, index): 35 | return self._items[index] 36 | 37 | def __setitem__(self, index, value): 38 | if self._frozen: 39 | raise RuntimeError("Cannot modify frozen list.") 40 | self._items[index] = value 41 | 42 | def __delitem__(self, index): 43 | if self._frozen: 44 | raise RuntimeError("Cannot modify frozen list.") 45 | del self._items[index] 46 | 47 | def __len__(self): 48 | return self._items.__len__() 49 | 50 | def __iter__(self): 51 | return self._items.__iter__() 52 | 53 | def __reversed__(self): 54 | return self._items.__reversed__() 55 | 56 | def __eq__(self, other): 57 | return list(self) == other 58 | 59 | def __le__(self, other): 60 | return list(self) <= other 61 | 62 | def insert(self, pos, item): 63 | if self._frozen: 64 | raise RuntimeError("Cannot modify frozen list.") 65 | self._items.insert(pos, item) 66 | 67 | def __repr__(self): 68 | return f"" 69 | 70 | def __hash__(self): 71 | if self._frozen: 72 | return hash(tuple(self)) 73 | else: 74 | raise RuntimeError("Cannot hash unfrozen list.") 75 | 76 | 77 | PyFrozenList = FrozenList 78 | 79 | 80 | if not NO_EXTENSIONS: 81 | try: 82 | from ._frozenlist import FrozenList as CFrozenList # type: ignore 83 | except ImportError: # pragma: no cover 84 | pass 85 | else: 86 | FrozenList = CFrozenList # type: ignore 87 | -------------------------------------------------------------------------------- /frozenlist/__init__.pyi: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | Generic, 3 | Iterable, 4 | Iterator, 5 | List, 6 | MutableSequence, 7 | Optional, 8 | TypeVar, 9 | Union, 10 | overload, 11 | ) 12 | 13 | _T = TypeVar("_T") 14 | _Arg = Union[List[_T], Iterable[_T]] 15 | 16 | class FrozenList(MutableSequence[_T], Generic[_T]): 17 | def __init__(self, items: Optional[_Arg[_T]] = None) -> None: ... 18 | @property 19 | def frozen(self) -> bool: ... 20 | def freeze(self) -> None: ... 21 | @overload 22 | def __getitem__(self, i: int) -> _T: ... 23 | @overload 24 | def __getitem__(self, s: slice) -> FrozenList[_T]: ... 25 | @overload 26 | def __setitem__(self, i: int, o: _T) -> None: ... 27 | @overload 28 | def __setitem__(self, s: slice, o: Iterable[_T]) -> None: ... 29 | @overload 30 | def __delitem__(self, i: int) -> None: ... 31 | @overload 32 | def __delitem__(self, i: slice) -> None: ... 33 | def __len__(self) -> int: ... 34 | def __iter__(self) -> Iterator[_T]: ... 35 | def __reversed__(self) -> Iterator[_T]: ... 36 | def __eq__(self, other: object) -> bool: ... 37 | def __le__(self, other: FrozenList[_T]) -> bool: ... 38 | def __ne__(self, other: object) -> bool: ... 39 | def __lt__(self, other: FrozenList[_T]) -> bool: ... 40 | def __ge__(self, other: FrozenList[_T]) -> bool: ... 41 | def __gt__(self, other: FrozenList[_T]) -> bool: ... 42 | def insert(self, pos: int, item: _T) -> None: ... 43 | def __repr__(self) -> str: ... 44 | def __hash__(self) -> int: ... 45 | 46 | # types for C accelerators are the same 47 | CFrozenList = PyFrozenList = FrozenList 48 | -------------------------------------------------------------------------------- /frozenlist/_frozenlist.pyx: -------------------------------------------------------------------------------- 1 | # cython: freethreading_compatible = True 2 | # distutils: language = c++ 3 | 4 | from cpython.bool cimport PyBool_FromLong 5 | from libcpp.atomic cimport atomic 6 | 7 | import types 8 | from collections.abc import MutableSequence 9 | 10 | 11 | cdef class FrozenList: 12 | __class_getitem__ = classmethod(types.GenericAlias) 13 | 14 | cdef atomic[bint] _frozen 15 | cdef list _items 16 | 17 | def __init__(self, items=None): 18 | self._frozen.store(False) 19 | if items is not None: 20 | items = list(items) 21 | else: 22 | items = [] 23 | self._items = items 24 | 25 | @property 26 | def frozen(self): 27 | return PyBool_FromLong(self._frozen.load()) 28 | 29 | cdef object _check_frozen(self): 30 | if self._frozen.load(): 31 | raise RuntimeError("Cannot modify frozen list.") 32 | 33 | cdef inline object _fast_len(self): 34 | return len(self._items) 35 | 36 | def freeze(self): 37 | self._frozen.store(True) 38 | 39 | def __getitem__(self, index): 40 | return self._items[index] 41 | 42 | def __setitem__(self, index, value): 43 | self._check_frozen() 44 | self._items[index] = value 45 | 46 | def __delitem__(self, index): 47 | self._check_frozen() 48 | del self._items[index] 49 | 50 | def __len__(self): 51 | return self._fast_len() 52 | 53 | def __iter__(self): 54 | return self._items.__iter__() 55 | 56 | def __reversed__(self): 57 | return self._items.__reversed__() 58 | 59 | def __richcmp__(self, other, op): 60 | if op == 0: # < 61 | return list(self) < other 62 | if op == 1: # <= 63 | return list(self) <= other 64 | if op == 2: # == 65 | return list(self) == other 66 | if op == 3: # != 67 | return list(self) != other 68 | if op == 4: # > 69 | return list(self) > other 70 | if op == 5: # => 71 | return list(self) >= other 72 | 73 | def insert(self, pos, item): 74 | self._check_frozen() 75 | self._items.insert(pos, item) 76 | 77 | def __contains__(self, item): 78 | return item in self._items 79 | 80 | def __iadd__(self, items): 81 | self._check_frozen() 82 | self._items += list(items) 83 | return self 84 | 85 | def index(self, item): 86 | return self._items.index(item) 87 | 88 | def remove(self, item): 89 | self._check_frozen() 90 | self._items.remove(item) 91 | 92 | def clear(self): 93 | self._check_frozen() 94 | self._items.clear() 95 | 96 | def extend(self, items): 97 | self._check_frozen() 98 | self._items += list(items) 99 | 100 | def reverse(self): 101 | self._check_frozen() 102 | self._items.reverse() 103 | 104 | def pop(self, index=-1): 105 | self._check_frozen() 106 | return self._items.pop(index) 107 | 108 | def append(self, item): 109 | self._check_frozen() 110 | return self._items.append(item) 111 | 112 | def count(self, item): 113 | return self._items.count(item) 114 | 115 | def __repr__(self): 116 | return ''.format(self._frozen.load(), 117 | self._items) 118 | 119 | def __hash__(self): 120 | if self._frozen.load(): 121 | return hash(tuple(self._items)) 122 | else: 123 | raise RuntimeError("Cannot hash unfrozen list.") 124 | 125 | 126 | MutableSequence.register(FrozenList) 127 | -------------------------------------------------------------------------------- /frozenlist/py.typed: -------------------------------------------------------------------------------- 1 | Marker 2 | -------------------------------------------------------------------------------- /packaging/README.md: -------------------------------------------------------------------------------- 1 | # `pep517_backend` in-tree build backend 2 | 3 | The `pep517_backend.hooks` importable exposes callables declared by PEP 517 4 | and PEP 660 and is integrated into `pyproject.toml`'s 5 | `[build-system].build-backend` through `[build-system].backend-path`. 6 | 7 | # Design considerations 8 | 9 | `__init__.py` is to remain empty, leaving `hooks.py` the only entrypoint 10 | exposing the callables. The logic is contained in private modules. This is 11 | to prevent import-time side effects. 12 | -------------------------------------------------------------------------------- /packaging/pep517_backend/__init__.py: -------------------------------------------------------------------------------- 1 | """PEP 517 build backend for optionally pre-building Cython.""" 2 | -------------------------------------------------------------------------------- /packaging/pep517_backend/__main__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from . import cli 4 | 5 | if __name__ == "__main__": 6 | sys.exit(cli.run_main_program(argv=sys.argv)) 7 | -------------------------------------------------------------------------------- /packaging/pep517_backend/_backend.py: -------------------------------------------------------------------------------- 1 | # fmt: off 2 | """PEP 517 build backend wrapper for pre-building Cython for wheel.""" 3 | 4 | from __future__ import annotations 5 | 6 | import os 7 | from contextlib import contextmanager, nullcontext, suppress 8 | from functools import partial 9 | from pathlib import Path 10 | from shutil import copytree 11 | from sys import implementation as _system_implementation 12 | from sys import stderr as _standard_error_stream 13 | from tempfile import TemporaryDirectory 14 | from typing import Dict, Iterator, List, Union 15 | from warnings import warn as _warn_that 16 | 17 | from setuptools.build_meta import build_sdist as _setuptools_build_sdist 18 | from setuptools.build_meta import build_wheel as _setuptools_build_wheel 19 | from setuptools.build_meta import ( 20 | get_requires_for_build_wheel as _setuptools_get_requires_for_build_wheel, 21 | ) 22 | from setuptools.build_meta import ( 23 | prepare_metadata_for_build_wheel as _setuptools_prepare_metadata_for_build_wheel, 24 | ) 25 | 26 | try: 27 | from setuptools.build_meta import build_editable as _setuptools_build_editable 28 | except ImportError: 29 | _setuptools_build_editable = None # type: ignore[assignment] 30 | 31 | 32 | # isort: split 33 | from distutils.command.install import install as _distutils_install_cmd 34 | from distutils.core import Distribution as _DistutilsDistribution 35 | from distutils.dist import DistributionMetadata as _DistutilsDistributionMetadata 36 | 37 | with suppress(ImportError): 38 | # NOTE: Only available for wheel builds that bundle C-extensions. Declared 39 | # NOTE: by `get_requires_for_build_wheel()` and 40 | # NOTE: `get_requires_for_build_editable()`, when `pure-python` 41 | # NOTE: is not passed. 42 | from Cython.Build.Cythonize import main as _cythonize_cli_cmd 43 | 44 | from ._compat import chdir_cm 45 | from ._cython_configuration import get_local_cython_config as _get_local_cython_config 46 | from ._cython_configuration import ( 47 | make_cythonize_cli_args_from_config as _make_cythonize_cli_args_from_config, 48 | ) 49 | from ._cython_configuration import patched_env as _patched_cython_env 50 | from ._transformers import sanitize_rst_roles 51 | 52 | __all__ = ( # noqa: WPS410 53 | 'build_sdist', 54 | 'build_wheel', 55 | 'get_requires_for_build_wheel', 56 | 'prepare_metadata_for_build_wheel', 57 | *( 58 | () if _setuptools_build_editable is None # type: ignore[redundant-expr] 59 | else ( 60 | 'build_editable', 61 | 'get_requires_for_build_editable', 62 | 'prepare_metadata_for_build_editable', 63 | ) 64 | ), 65 | ) 66 | 67 | _ConfigDict = Dict[str, Union[str, List[str], None]] 68 | 69 | 70 | CYTHON_TRACING_CONFIG_SETTING = 'with-cython-tracing' 71 | """Config setting name toggle to include line tracing to C-exts.""" 72 | 73 | CYTHON_TRACING_ENV_VAR = 'FROZENLIST_CYTHON_TRACING' 74 | """Environment variable name toggle used to opt out of making C-exts.""" 75 | 76 | PURE_PYTHON_CONFIG_SETTING = 'pure-python' 77 | """Config setting name toggle that is used to opt out of making C-exts.""" 78 | 79 | PURE_PYTHON_ENV_VAR = 'FROZENLIST_NO_EXTENSIONS' 80 | """Environment variable name toggle used to opt out of making C-exts.""" 81 | 82 | IS_CPYTHON = _system_implementation.name == "cpython" 83 | """A flag meaning that the current interpreter implementation is CPython.""" 84 | 85 | PURE_PYTHON_MODE_CLI_FALLBACK = not IS_CPYTHON 86 | """A fallback for ``pure-python`` is not set.""" 87 | 88 | 89 | def _is_truthy_setting_value(setting_value: str) -> bool: 90 | truthy_values = {'', None, 'true', '1', 'on'} 91 | return setting_value.lower() in truthy_values 92 | 93 | 94 | def _get_setting_value( 95 | config_settings: _ConfigDict | None = None, 96 | config_setting_name: str | None = None, 97 | env_var_name: str | None = None, 98 | *, 99 | default: bool = False, 100 | ) -> bool: 101 | user_provided_setting_sources = ( 102 | (config_settings, config_setting_name, (KeyError, TypeError)), 103 | (os.environ, env_var_name, KeyError), 104 | ) 105 | for src_mapping, src_key, lookup_errors in user_provided_setting_sources: 106 | if src_key is None: 107 | continue 108 | 109 | with suppress(lookup_errors): # type: ignore[arg-type] 110 | return _is_truthy_setting_value(src_mapping[src_key]) # type: ignore[arg-type,index] 111 | 112 | return default 113 | 114 | 115 | def _make_pure_python(config_settings: _ConfigDict | None = None) -> bool: 116 | return _get_setting_value( 117 | config_settings, 118 | PURE_PYTHON_CONFIG_SETTING, 119 | PURE_PYTHON_ENV_VAR, 120 | default=PURE_PYTHON_MODE_CLI_FALLBACK, 121 | ) 122 | 123 | 124 | def _include_cython_line_tracing( 125 | config_settings: _ConfigDict | None = None, 126 | *, 127 | default: bool = False, 128 | ) -> bool: 129 | return _get_setting_value( 130 | config_settings, 131 | CYTHON_TRACING_CONFIG_SETTING, 132 | CYTHON_TRACING_ENV_VAR, 133 | default=default, 134 | ) 135 | 136 | 137 | @contextmanager 138 | def patched_distutils_cmd_install() -> Iterator[None]: 139 | """Make `install_lib` of `install` cmd always use `platlib`. 140 | 141 | :yields: None 142 | """ 143 | # Without this, build_lib puts stuff under `*.data/purelib/` folder 144 | orig_finalize = _distutils_install_cmd.finalize_options 145 | 146 | def new_finalize_options(self: _distutils_install_cmd) -> None: # noqa: WPS430 147 | self.install_lib = self.install_platlib 148 | orig_finalize(self) 149 | 150 | _distutils_install_cmd.finalize_options = new_finalize_options # type: ignore[method-assign] 151 | try: 152 | yield 153 | finally: 154 | _distutils_install_cmd.finalize_options = orig_finalize # type: ignore[method-assign] 155 | 156 | 157 | @contextmanager 158 | def patched_dist_has_ext_modules() -> Iterator[None]: 159 | """Make `has_ext_modules` of `Distribution` always return `True`. 160 | 161 | :yields: None 162 | """ 163 | # Without this, build_lib puts stuff under `*.data/platlib/` folder 164 | orig_func = _DistutilsDistribution.has_ext_modules 165 | 166 | _DistutilsDistribution.has_ext_modules = lambda *args, **kwargs: True # type: ignore[method-assign] 167 | try: 168 | yield 169 | finally: 170 | _DistutilsDistribution.has_ext_modules = orig_func # type: ignore[method-assign] 171 | 172 | 173 | @contextmanager 174 | def patched_dist_get_long_description() -> Iterator[None]: 175 | """Make `has_ext_modules` of `Distribution` always return `True`. 176 | 177 | :yields: None 178 | """ 179 | # Without this, build_lib puts stuff under `*.data/platlib/` folder 180 | _orig_func = _DistutilsDistributionMetadata.get_long_description 181 | 182 | def _get_sanitized_long_description(self: _DistutilsDistributionMetadata) -> str: 183 | assert self.long_description is not None 184 | return sanitize_rst_roles(self.long_description) 185 | 186 | _DistutilsDistributionMetadata.get_long_description = ( # type: ignore[method-assign] 187 | _get_sanitized_long_description 188 | ) 189 | try: 190 | yield 191 | finally: 192 | _DistutilsDistributionMetadata.get_long_description = _orig_func # type: ignore[method-assign] 193 | 194 | 195 | def _exclude_dir_path( 196 | excluded_dir_path: Path, 197 | visited_directory: str, 198 | _visited_dir_contents: list[str], 199 | ) -> list[str]: 200 | """Prevent recursive directory traversal.""" 201 | # This stops the temporary directory from being copied 202 | # into self recursively forever. 203 | # Ref: https://github.com/aio-libs/yarl/issues/992 204 | visited_directory_subdirs_to_ignore = [ 205 | subdir 206 | for subdir in _visited_dir_contents 207 | if excluded_dir_path == Path(visited_directory) / subdir 208 | ] 209 | if visited_directory_subdirs_to_ignore: 210 | print( 211 | f'Preventing `{excluded_dir_path !s}` from being ' 212 | 'copied into itself recursively...', 213 | file=_standard_error_stream, 214 | ) 215 | return visited_directory_subdirs_to_ignore 216 | 217 | 218 | @contextmanager 219 | def _in_temporary_directory(src_dir: Path) -> Iterator[None]: 220 | with TemporaryDirectory(prefix='.tmp-frozenlist-pep517-') as tmp_dir: 221 | tmp_dir_path = Path(tmp_dir) 222 | root_tmp_dir_path = tmp_dir_path.parent 223 | _exclude_tmpdir_parent = partial(_exclude_dir_path, root_tmp_dir_path) 224 | 225 | with chdir_cm(tmp_dir): 226 | tmp_src_dir = tmp_dir_path / 'src' 227 | copytree( 228 | src_dir, 229 | tmp_src_dir, 230 | ignore=_exclude_tmpdir_parent, 231 | symlinks=True, 232 | ) 233 | os.chdir(tmp_src_dir) 234 | yield 235 | 236 | 237 | @contextmanager 238 | def maybe_prebuild_c_extensions( 239 | line_trace_cython_when_unset: bool = False, 240 | build_inplace: bool = False, 241 | config_settings: _ConfigDict | None = None, 242 | ) -> Iterator[None]: 243 | """Pre-build C-extensions in a temporary directory, when needed. 244 | 245 | This context manager also patches metadata, setuptools and distutils. 246 | 247 | :param build_inplace: Whether to copy and chdir to a temporary location. 248 | :param config_settings: :pep:`517` config settings mapping. 249 | 250 | """ 251 | cython_line_tracing_requested = _include_cython_line_tracing( 252 | config_settings, 253 | default=line_trace_cython_when_unset, 254 | ) 255 | is_pure_python_build = _make_pure_python(config_settings) 256 | 257 | if is_pure_python_build: 258 | print("*********************", file=_standard_error_stream) 259 | print("* Pure Python build *", file=_standard_error_stream) 260 | print("*********************", file=_standard_error_stream) 261 | 262 | if cython_line_tracing_requested: 263 | _warn_that( 264 | f'The `{CYTHON_TRACING_CONFIG_SETTING !s}` setting requesting ' 265 | 'Cython line tracing is set, but building C-extensions is not. ' 266 | 'This option will not have any effect for in the pure-python ' 267 | 'build mode.', 268 | RuntimeWarning, 269 | stacklevel=999, 270 | ) 271 | 272 | yield 273 | return 274 | 275 | print("**********************", file=_standard_error_stream) 276 | print("* Accelerated build *", file=_standard_error_stream) 277 | print("**********************", file=_standard_error_stream) 278 | if not IS_CPYTHON: 279 | _warn_that( 280 | 'Building C-extensions under the runtimes other than CPython is ' 281 | 'unsupported and will likely fail. Consider passing the ' 282 | f'`{PURE_PYTHON_CONFIG_SETTING !s}` PEP 517 config setting.', 283 | RuntimeWarning, 284 | stacklevel=999, 285 | ) 286 | 287 | build_dir_ctx = ( 288 | nullcontext() if build_inplace 289 | else _in_temporary_directory(src_dir=Path.cwd().resolve()) 290 | ) 291 | with build_dir_ctx: 292 | config = _get_local_cython_config() 293 | 294 | cythonize_args = _make_cythonize_cli_args_from_config(config) 295 | with _patched_cython_env(config['env'], cython_line_tracing_requested): 296 | _cythonize_cli_cmd(cythonize_args) 297 | with patched_distutils_cmd_install(): 298 | with patched_dist_has_ext_modules(): 299 | yield 300 | 301 | 302 | @patched_dist_get_long_description() 303 | def build_wheel( 304 | wheel_directory: str, 305 | config_settings: _ConfigDict | None = None, 306 | metadata_directory: str | None = None, 307 | ) -> str: 308 | """Produce a built wheel. 309 | 310 | This wraps the corresponding ``setuptools``' build backend hook. 311 | 312 | :param wheel_directory: Directory to put the resulting wheel in. 313 | :param config_settings: :pep:`517` config settings mapping. 314 | :param metadata_directory: :file:`.dist-info` directory path. 315 | 316 | """ 317 | with maybe_prebuild_c_extensions( 318 | line_trace_cython_when_unset=False, 319 | build_inplace=False, 320 | config_settings=config_settings, 321 | ): 322 | return _setuptools_build_wheel( 323 | wheel_directory=wheel_directory, 324 | config_settings=config_settings, 325 | metadata_directory=metadata_directory, 326 | ) 327 | 328 | 329 | @patched_dist_get_long_description() 330 | def build_editable( 331 | wheel_directory: str, 332 | config_settings: _ConfigDict | None = None, 333 | metadata_directory: str | None = None, 334 | ) -> str: 335 | """Produce a built wheel for editable installs. 336 | 337 | This wraps the corresponding ``setuptools``' build backend hook. 338 | 339 | :param wheel_directory: Directory to put the resulting wheel in. 340 | :param config_settings: :pep:`517` config settings mapping. 341 | :param metadata_directory: :file:`.dist-info` directory path. 342 | 343 | """ 344 | with maybe_prebuild_c_extensions( 345 | line_trace_cython_when_unset=True, 346 | build_inplace=True, 347 | config_settings=config_settings, 348 | ): 349 | return _setuptools_build_editable( 350 | wheel_directory=wheel_directory, 351 | config_settings=config_settings, 352 | metadata_directory=metadata_directory, 353 | ) 354 | 355 | 356 | def get_requires_for_build_wheel( 357 | config_settings: _ConfigDict | None = None, 358 | ) -> list[str]: 359 | """Determine additional requirements for building wheels. 360 | 361 | :param config_settings: :pep:`517` config settings mapping. 362 | 363 | """ 364 | is_pure_python_build = _make_pure_python(config_settings) 365 | 366 | if not is_pure_python_build and not IS_CPYTHON: 367 | _warn_that( 368 | 'Building C-extensions under the runtimes other than CPython is ' 369 | 'unsupported and will likely fail. Consider passing the ' 370 | f'`{PURE_PYTHON_CONFIG_SETTING !s}` PEP 517 config setting.', 371 | RuntimeWarning, 372 | stacklevel=999, 373 | ) 374 | 375 | if is_pure_python_build: 376 | c_ext_build_deps = [] 377 | else: 378 | c_ext_build_deps = ['Cython >= 3.1.1'] 379 | 380 | return _setuptools_get_requires_for_build_wheel( 381 | config_settings=config_settings, 382 | ) + c_ext_build_deps 383 | 384 | 385 | build_sdist = patched_dist_get_long_description()(_setuptools_build_sdist) 386 | get_requires_for_build_editable = get_requires_for_build_wheel 387 | prepare_metadata_for_build_wheel = patched_dist_get_long_description()( 388 | _setuptools_prepare_metadata_for_build_wheel, 389 | ) 390 | prepare_metadata_for_build_editable = prepare_metadata_for_build_wheel 391 | -------------------------------------------------------------------------------- /packaging/pep517_backend/_compat.py: -------------------------------------------------------------------------------- 1 | """Cross-python stdlib shims.""" 2 | 3 | import os 4 | import sys 5 | from contextlib import contextmanager 6 | from pathlib import Path 7 | from typing import Iterator 8 | 9 | if sys.version_info >= (3, 11): 10 | from contextlib import chdir as chdir_cm 11 | from tomllib import loads as load_toml_from_string 12 | else: 13 | from tomli import loads as load_toml_from_string 14 | 15 | @contextmanager # type: ignore[no-redef] 16 | def chdir_cm(path: "os.PathLike[str]") -> Iterator[None]: 17 | """Temporarily change the current directory, recovering on exit.""" 18 | original_wd = Path.cwd() 19 | os.chdir(path) 20 | try: 21 | yield 22 | finally: 23 | os.chdir(original_wd) 24 | 25 | 26 | __all__ = ("chdir_cm", "load_toml_from_string") # noqa: WPS410 27 | -------------------------------------------------------------------------------- /packaging/pep517_backend/_cython_configuration.py: -------------------------------------------------------------------------------- 1 | # fmt: off 2 | 3 | from __future__ import annotations 4 | 5 | import os 6 | from contextlib import contextmanager 7 | from pathlib import Path 8 | from sys import version_info as _python_version_tuple 9 | from typing import Iterator, TypedDict 10 | 11 | from expandvars import expandvars 12 | 13 | from ._compat import load_toml_from_string 14 | from ._transformers import get_cli_kwargs_from_config, get_enabled_cli_flags_from_config 15 | 16 | 17 | class Config(TypedDict): 18 | env: dict[str, str] 19 | flags: dict[str, bool] 20 | kwargs: dict[str, str] 21 | src: list[str] 22 | 23 | 24 | def get_local_cython_config() -> Config: 25 | """Grab optional build dependencies from pyproject.toml config. 26 | 27 | :returns: config section from ``pyproject.toml`` 28 | :rtype: dict 29 | 30 | This basically reads entries from:: 31 | 32 | [tool.local.cythonize] 33 | # Env vars provisioned during cythonize call 34 | src = ["src/**/*.pyx"] 35 | 36 | [tool.local.cythonize.env] 37 | # Env vars provisioned during cythonize call 38 | LDFLAGS = "-lssh" 39 | 40 | [tool.local.cythonize.flags] 41 | # This section can contain the following booleans: 42 | # * annotate — generate annotated HTML page for source files 43 | # * build — build extension modules using distutils 44 | # * inplace — build extension modules in place using distutils (implies -b) 45 | # * force — force recompilation 46 | # * quiet — be less verbose during compilation 47 | # * lenient — increase Python compat by ignoring some compile time errors 48 | # * keep-going — compile as much as possible, ignore compilation failures 49 | annotate = false 50 | build = false 51 | inplace = true 52 | force = true 53 | quiet = false 54 | lenient = false 55 | keep-going = false 56 | 57 | [tool.local.cythonize.kwargs] 58 | # This section can contain args that have values: 59 | # * exclude=PATTERN exclude certain file patterns from the compilation 60 | # * parallel=N run builds in N parallel jobs (default: calculated per system) 61 | exclude = "**.py" 62 | parallel = 12 63 | 64 | [tool.local.cythonize.kwargs.directives] 65 | # This section can contain compiler directives 66 | # NAME = "VALUE" 67 | 68 | [tool.local.cythonize.kwargs.compile-time-env] 69 | # This section can contain compile time env vars 70 | # NAME = "VALUE" 71 | 72 | [tool.local.cythonize.kwargs.options] 73 | # This section can contain cythonize options 74 | # NAME = "VALUE" 75 | """ 76 | config_toml_txt = (Path.cwd().resolve() / 'pyproject.toml').read_text() 77 | config_mapping = load_toml_from_string(config_toml_txt) 78 | return config_mapping['tool']['local']['cythonize'] # type: ignore[no-any-return] 79 | 80 | 81 | def make_cythonize_cli_args_from_config(config: Config) -> list[str]: 82 | py_ver_arg = f'-{_python_version_tuple.major!s}' 83 | 84 | cli_flags = get_enabled_cli_flags_from_config(config['flags']) 85 | cli_kwargs = get_cli_kwargs_from_config(config['kwargs']) 86 | 87 | return cli_flags + [py_ver_arg] + cli_kwargs + ['--'] + config['src'] 88 | 89 | 90 | @contextmanager 91 | def patched_env(env: dict[str, str], cython_line_tracing_requested: bool) -> Iterator[None]: 92 | """Temporary set given env vars. 93 | 94 | :param env: tmp env vars to set 95 | :type env: dict 96 | 97 | :yields: None 98 | """ 99 | orig_env = os.environ.copy() 100 | expanded_env = {name: expandvars(var_val) for name, var_val in env.items()} 101 | os.environ.update(expanded_env) 102 | 103 | if cython_line_tracing_requested: 104 | os.environ['CFLAGS'] = ' '.join(( 105 | os.getenv('CFLAGS', ''), 106 | '-DCYTHON_TRACE_NOGIL=1', # Implies CYTHON_TRACE=1 107 | )).strip() 108 | try: 109 | yield 110 | finally: 111 | os.environ.clear() 112 | os.environ.update(orig_env) 113 | -------------------------------------------------------------------------------- /packaging/pep517_backend/_transformers.py: -------------------------------------------------------------------------------- 1 | """Data conversion helpers for the in-tree PEP 517 build backend.""" 2 | 3 | from itertools import chain 4 | from re import sub as _substitute_with_regexp 5 | from typing import Dict, Iterable, Iterator, List, Mapping, Tuple, Union 6 | 7 | 8 | def _emit_opt_pairs(opt_pair: Tuple[str, Union[str, Dict[str, str]]]) -> Iterator[str]: 9 | flag, flag_value = opt_pair 10 | flag_opt = f"--{flag!s}" 11 | if isinstance(flag_value, dict): 12 | sub_pairs: Iterable[Tuple[str, ...]] = flag_value.items() 13 | else: 14 | sub_pairs = ((flag_value,),) 15 | 16 | yield from ("=".join(map(str, (flag_opt,) + pair)) for pair in sub_pairs) 17 | 18 | 19 | def get_cli_kwargs_from_config(kwargs_map: Mapping[str, str]) -> List[str]: 20 | """Make a list of options with values from config.""" 21 | return list(chain.from_iterable(map(_emit_opt_pairs, kwargs_map.items()))) 22 | 23 | 24 | def get_enabled_cli_flags_from_config(flags_map: Mapping[str, bool]) -> List[str]: 25 | """Make a list of enabled boolean flags from config.""" 26 | return [f"--{flag}" for flag, is_enabled in flags_map.items() if is_enabled] 27 | 28 | 29 | def sanitize_rst_roles(rst_source_text: str) -> str: 30 | """Replace RST roles with inline highlighting.""" 31 | pep_role_regex = r"""(?x) 32 | :pep:`(?P\d+)` 33 | """ 34 | pep_substitution_pattern = ( 35 | r"`PEP \g >`__" 36 | ) 37 | 38 | user_role_regex = r"""(?x) 39 | :user:`(?P[^`]+)(?:\s+(.*))?` 40 | """ 41 | user_substitution_pattern = ( 42 | r"`@\g " 43 | r">`__" 44 | ) 45 | 46 | issue_role_regex = r"""(?x) 47 | :issue:`(?P[^`]+)(?:\s+(.*))?` 48 | """ 49 | issue_substitution_pattern = ( 50 | r"`#\g " 51 | r">`__" 52 | ) 53 | 54 | pr_role_regex = r"""(?x) 55 | :pr:`(?P[^`]+)(?:\s+(.*))?` 56 | """ 57 | pr_substitution_pattern = ( 58 | r"`PR #\g " 59 | r">`__" 60 | ) 61 | 62 | commit_role_regex = r"""(?x) 63 | :commit:`(?P[^`]+)(?:\s+(.*))?` 64 | """ 65 | commit_substitution_pattern = ( 66 | r"`\g " 67 | r">`__" 68 | ) 69 | 70 | gh_role_regex = r"""(?x) 71 | :gh:`(?P[^`]+)(?:\s+(.*))?` 72 | """ 73 | gh_substitution_pattern = ( 74 | r"`GitHub: \g >`__" 75 | ) 76 | 77 | meth_role_regex = r"""(?x) 78 | (?::py)?:meth:`~?(?P[^`<]+)(?:\s+([^`]*))?` 79 | """ 80 | meth_substitution_pattern = r"``\g()``" 81 | 82 | role_regex = r"""(?x) 83 | (?::\w+)?:\w+:`(?P[^`<]+)(?:\s+([^`]*))?` 84 | """ 85 | substitution_pattern = r"``\g``" 86 | 87 | substitutions = ( 88 | (pep_role_regex, pep_substitution_pattern), 89 | (user_role_regex, user_substitution_pattern), 90 | (issue_role_regex, issue_substitution_pattern), 91 | (pr_role_regex, pr_substitution_pattern), 92 | (commit_role_regex, commit_substitution_pattern), 93 | (gh_role_regex, gh_substitution_pattern), 94 | (meth_role_regex, meth_substitution_pattern), 95 | (role_regex, substitution_pattern), 96 | ) 97 | 98 | rst_source_normalized_text = rst_source_text 99 | for regex, substitution in substitutions: 100 | rst_source_normalized_text = _substitute_with_regexp( 101 | regex, 102 | substitution, 103 | rst_source_normalized_text, 104 | ) 105 | 106 | return rst_source_normalized_text 107 | -------------------------------------------------------------------------------- /packaging/pep517_backend/cli.py: -------------------------------------------------------------------------------- 1 | # fmt: off 2 | 3 | from __future__ import annotations 4 | 5 | import sys 6 | from itertools import chain 7 | from pathlib import Path 8 | from typing import Sequence 9 | 10 | from Cython.Compiler.Main import compile as _translate_cython_cli_cmd 11 | from Cython.Compiler.Main import parse_command_line as _split_cython_cli_args 12 | 13 | from ._cython_configuration import get_local_cython_config as _get_local_cython_config 14 | from ._cython_configuration import ( 15 | make_cythonize_cli_args_from_config as _make_cythonize_cli_args_from_config, 16 | ) 17 | from ._cython_configuration import patched_env as _patched_cython_env 18 | 19 | _PROJECT_PATH = Path(__file__).parents[2] 20 | 21 | 22 | def run_main_program(argv: Sequence[str]) -> int | str: 23 | """Invoke ``translate-cython`` or fail.""" 24 | if len(argv) != 2: 25 | return 'This program only accepts one argument -- "translate-cython"' 26 | 27 | if argv[1] != 'translate-cython': 28 | return 'This program only implements the "translate-cython" subcommand' 29 | 30 | config = _get_local_cython_config() 31 | config['flags'] = {'keep-going': config['flags']['keep-going']} 32 | config['src'] = list( 33 | map( 34 | str, 35 | chain.from_iterable( 36 | map(_PROJECT_PATH.glob, config['src']), 37 | ), 38 | ), 39 | ) 40 | translate_cython_cli_args = _make_cythonize_cli_args_from_config(config) 41 | 42 | cython_options, cython_sources = _split_cython_cli_args( 43 | translate_cython_cli_args, 44 | ) 45 | 46 | with _patched_cython_env(config['env'], cython_line_tracing_requested=True): 47 | return _translate_cython_cli_cmd( # type: ignore[no-any-return] 48 | cython_sources, 49 | cython_options, 50 | ).num_errors 51 | 52 | 53 | if __name__ == '__main__': 54 | sys.exit(run_main_program(argv=sys.argv)) 55 | -------------------------------------------------------------------------------- /packaging/pep517_backend/hooks.py: -------------------------------------------------------------------------------- 1 | """PEP 517 build backend for optionally pre-building Cython.""" 2 | 3 | from contextlib import suppress as _suppress 4 | 5 | from setuptools.build_meta import * # Re-exporting PEP 517 hooks # pylint: disable=unused-wildcard-import,wildcard-import # noqa: F401, F403 6 | 7 | # Re-exporting PEP 517 hooks 8 | from ._backend import ( # type: ignore[assignment] 9 | build_sdist, 10 | build_wheel, 11 | get_requires_for_build_wheel, 12 | prepare_metadata_for_build_wheel, 13 | ) 14 | 15 | with _suppress(ImportError): # Only succeeds w/ setuptools implementing PEP 660 16 | # Re-exporting PEP 660 hooks 17 | from ._backend import ( # type: ignore[assignment] 18 | build_editable, 19 | get_requires_for_build_editable, 20 | prepare_metadata_for_build_editable, 21 | ) 22 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | # NOTE: The following build dependencies are necessary for initial 4 | # NOTE: provisioning of the in-tree build backend located under 5 | # NOTE: `packaging/pep517_backend/`. 6 | "expandvars", 7 | "setuptools >= 47", # Minimum required for `version = attr:` 8 | "tomli; python_version < '3.11'", 9 | ] 10 | backend-path = ["packaging"] # requires `pip >= 20` or `pep517 >= 0.6.0` 11 | build-backend = "pep517_backend.hooks" # wraps `setuptools.build_meta` 12 | 13 | [tool.local.cythonize] 14 | # This attr can contain multiple globs 15 | src = ["frozenlist/*.pyx"] 16 | 17 | [tool.local.cythonize.env] 18 | # Env vars provisioned during cythonize call 19 | #CFLAGS = "-DCYTHON_TRACE=1 ${CFLAGS}" 20 | #LDFLAGS = "${LDFLAGS}" 21 | 22 | [tool.local.cythonize.flags] 23 | # This section can contain the following booleans: 24 | # * annotate — generate annotated HTML page for source files 25 | # * build — build extension modules using distutils 26 | # * inplace — build extension modules in place using distutils (implies -b) 27 | # * force — force recompilation 28 | # * quiet — be less verbose during compilation 29 | # * lenient — increase Python compat by ignoring some compile time errors 30 | # * keep-going — compile as much as possible, ignore compilation failures 31 | annotate = false 32 | build = false 33 | inplace = true 34 | force = true 35 | quiet = false 36 | lenient = false 37 | keep-going = false 38 | 39 | [tool.local.cythonize.kwargs] 40 | # This section can contain args that have values: 41 | # * exclude=PATTERN exclude certain file patterns from the compilation 42 | # * parallel=N run builds in N parallel jobs (default: calculated per system) 43 | # exclude = "**.py" 44 | # parallel = 12 45 | 46 | [tool.local.cythonize.kwargs.directive] 47 | # This section can contain compiler directives. Ref: 48 | # https://cython.rtfd.io/en/latest/src/userguide/source_files_and_compilation.html#compiler-directives 49 | embedsignature = "True" 50 | emit_code_comments = "True" 51 | linetrace = "True" # Implies `profile=True` 52 | 53 | [tool.local.cythonize.kwargs.compile-time-env] 54 | # This section can contain compile time env vars 55 | 56 | [tool.local.cythonize.kwargs.option] 57 | # This section can contain cythonize options 58 | # Ref: https://github.com/cython/cython/blob/d6e6de9/Cython/Compiler/Options.py#L694-L730 59 | #docstrings = "True" 60 | #embed_pos_in_docstring = "True" 61 | #warning_errors = "True" 62 | #error_on_unknown_names = "True" 63 | #error_on_uninitialized = "True" 64 | 65 | [tool.cibuildwheel] 66 | enable = ["cpython-freethreading"] 67 | build-frontend = "build" 68 | before-test = [ 69 | # NOTE: Attempt to have pip pre-compile PyYAML wheel with our build 70 | # NOTE: constraints unset. The hope is that pip will cache that wheel 71 | # NOTE: and the test env provisioning stage will pick up PyYAML from 72 | # NOTE: said cache rather than attempting to build it with a conflicting. 73 | # NOTE: Version of Cython. 74 | # Ref: https://github.com/pypa/cibuildwheel/issues/1666 75 | "PIP_CONSTRAINT= pip install PyYAML", 76 | ] 77 | # test-requires = "-r requirements/ci-wheel.txt" 78 | # test-command = "pytest -v --no-cov {project}/tests" 79 | 80 | # don't build PyPy wheels, install from source instead 81 | skip = "pp*" 82 | 83 | [tool.cibuildwheel.environment] 84 | COLOR = "yes" 85 | FORCE_COLOR = "1" 86 | MYPY_FORCE_COLOR = "1" 87 | # PIP_CONSTRAINT = "requirements/cython.txt" 88 | PRE_COMMIT_COLOR = "always" 89 | PY_COLORS = "1" 90 | 91 | [tool.cibuildwheel.config-settings] 92 | pure-python = "false" 93 | 94 | [tool.cibuildwheel.windows] 95 | before-test = [] # Windows cmd has different syntax and pip chooses wheels 96 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = 3 | # `pytest-xdist`: 4 | # --numprocesses=auto 5 | # NOTE: the plugin disabled because it's slower with so few tests 6 | # --numprocesses=0 7 | 8 | # Show 10 slowest invocations: 9 | --durations=10 10 | 11 | # Report all the things == -rxXs: 12 | -ra 13 | 14 | # Show values of the local vars in errors/tracebacks: 15 | --showlocals 16 | 17 | # Autocollect and invoke the doctests from all modules: 18 | # https://docs.pytest.org/en/stable/doctest.html 19 | --doctest-modules 20 | 21 | # Pre-load the `pytest-cov` plugin early: 22 | -p pytest_cov 23 | 24 | # `pytest-cov`: 25 | --cov 26 | --cov-config=.coveragerc 27 | --cov-context=test 28 | 29 | # Fail on config parsing warnings: 30 | # --strict-config 31 | 32 | # Fail on non-existing markers: 33 | # * Deprecated since v6.2.0 but may be reintroduced later covering a 34 | # broader scope: 35 | # --strict 36 | # * Exists since v4.5.0 (advised to be used instead of `--strict`): 37 | --strict-markers 38 | 39 | doctest_optionflags = ALLOW_UNICODE ELLIPSIS 40 | 41 | # Marks tests with an empty parameterset as xfail(run=False) 42 | empty_parameter_set_mark = xfail 43 | 44 | faulthandler_timeout = 30 45 | 46 | filterwarnings = 47 | error 48 | 49 | # https://github.com/pytest-dev/pytest/issues/10977 and https://github.com/pytest-dev/pytest/pull/10894 50 | ignore:ast\.(Num|NameConstant|Str) is deprecated and will be removed in Python 3\.14; use ast\.Constant instead:DeprecationWarning:_pytest 51 | ignore:Attribute s is deprecated and will be removed in Python 3\.14; use value instead:DeprecationWarning:_pytest 52 | 53 | # https://docs.pytest.org/en/stable/usage.html#creating-junitxml-format-files 54 | junit_duration_report = call 55 | # xunit1 contains more metadata than xunit2 so it's better for CI UIs: 56 | junit_family = xunit1 57 | junit_logging = all 58 | junit_log_passing_tests = true 59 | junit_suite_name = frozenlist_test_suite 60 | 61 | # A mapping of markers to their descriptions allowed in strict mode: 62 | markers = 63 | 64 | minversion = 3.8.2 65 | 66 | # Optimize pytest's lookup by restricting potentially deep dir tree scan: 67 | norecursedirs = 68 | build 69 | dist 70 | docs 71 | requirements 72 | venv 73 | virtualenv 74 | frozenlist.egg-info 75 | .cache 76 | .eggs 77 | .git 78 | .github 79 | .tox 80 | *.egg 81 | 82 | testpaths = tests/ 83 | 84 | xfail_strict = true 85 | -------------------------------------------------------------------------------- /requirements/cython.txt: -------------------------------------------------------------------------------- 1 | cython==3.1.1 2 | -------------------------------------------------------------------------------- /requirements/dev.txt: -------------------------------------------------------------------------------- 1 | -e . --config-settings=pure-python=false 2 | -r test.txt 3 | -r lint.txt 4 | tox==4.11.4 5 | -------------------------------------------------------------------------------- /requirements/doc.txt: -------------------------------------------------------------------------------- 1 | -r towncrier.txt 2 | aiohttp-theme==0.1.6 3 | sphinx==7.2.6 4 | sphinxcontrib-spelling==8.0.0; platform_system!="Windows" 5 | sphinxcontrib-towncrier 6 | -------------------------------------------------------------------------------- /requirements/lint.txt: -------------------------------------------------------------------------------- 1 | build 2 | pre-commit==3.5.0 3 | twine 4 | types-setuptools==75.8.0.20250210 5 | -------------------------------------------------------------------------------- /requirements/test.txt: -------------------------------------------------------------------------------- 1 | -r cython.txt 2 | coverage==7.6.1 3 | pytest==7.4.3 4 | pytest-cov==4.1.0 5 | -------------------------------------------------------------------------------- /requirements/towncrier.txt: -------------------------------------------------------------------------------- 1 | towncrier==23.11.0 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = frozenlist 3 | version = attr: frozenlist.__version__ 4 | url = https://github.com/aio-libs/frozenlist 5 | project_urls = 6 | Chat: Matrix = https://matrix.to/#/#aio-libs:matrix.org 7 | Chat: Matrix Space = https://matrix.to/#/#aio-libs-space:matrix.org 8 | CI: Github Actions = https://github.com/aio-libs/frozenlist/actions 9 | Code of Conduct = https://github.com/aio-libs/.github/blob/master/CODE_OF_CONDUCT.md 10 | Coverage: codecov = https://codecov.io/github/aio-libs/frozenlist 11 | Docs: Changelog = https://github.com/aio-libs/frozenlist/blob/master/CHANGES.rst#changelog 12 | Docs: RTD = https://frozenlist.aio-libs.org 13 | GitHub: issues = https://github.com/aio-libs/frozenlist/issues 14 | GitHub: repo = https://github.com/aio-libs/frozenlist 15 | description = A list-like structure which implements collections.abc.MutableSequence 16 | long_description = file: README.rst, CHANGES.rst 17 | long_description_content_type = text/x-rst 18 | maintainer = aiohttp team 19 | maintainer_email = team@aiohttp.org 20 | license = Apache-2.0 21 | license_files = 22 | LICENSE 23 | classifiers = 24 | Development Status :: 5 - Production/Stable 25 | 26 | Intended Audience :: Developers 27 | 28 | Operating System :: POSIX 29 | Operating System :: MacOS :: MacOS X 30 | Operating System :: Microsoft :: Windows 31 | 32 | Programming Language :: Cython 33 | Programming Language :: Python 34 | Programming Language :: Python :: 3 35 | Programming Language :: Python :: 3.9 36 | Programming Language :: Python :: 3.10 37 | Programming Language :: Python :: 3.11 38 | Programming Language :: Python :: 3.12 39 | Programming Language :: Python :: 3.13 40 | Programming Language :: Python :: Implementation :: CPython 41 | Programming Language :: Python :: Implementation :: PyPy 42 | 43 | [options] 44 | python_requires = >=3.9 45 | packages = find: 46 | # https://setuptools.readthedocs.io/en/latest/setuptools.html#setting-the-zip-safe-flag 47 | zip_safe = False 48 | include_package_data = True 49 | 50 | [options.exclude_package_data] 51 | * = 52 | *.cpp 53 | *.c 54 | *.h 55 | 56 | [options.package_data] 57 | # Ref: 58 | # https://setuptools.readthedocs.io/en/latest/setuptools.html#options 59 | # (see notes for the asterisk/`*` meaning) 60 | * = 61 | *.so 62 | 63 | [pep8] 64 | max-line-length=88 65 | 66 | [flake8] 67 | extend-select = B950 68 | ignore = E226,E252,E501,N801,N802,N803,W503,W504 69 | max-line-length = 88 70 | 71 | # Allow certain violations in certain files: 72 | per-file-ignores = 73 | 74 | # F401 imported but unused 75 | packaging/pep517_backend/hooks.py: F401 76 | 77 | [isort] 78 | profile=black 79 | multi_line_output=3 80 | include_trailing_comma=True 81 | force_grid_wrap=0 82 | use_parentheses=True 83 | 84 | known_third_party=pytest 85 | known_first_party=frozenlist 86 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aio-libs/frozenlist/b71742c67fd23625e79ec1618ce18aaeb76ea0bd/tests/conftest.py -------------------------------------------------------------------------------- /tests/test_frozenlist.py: -------------------------------------------------------------------------------- 1 | # FIXME: 2 | # mypy: disable-error-code="misc" 3 | 4 | from collections.abc import MutableSequence 5 | 6 | import pytest 7 | 8 | from frozenlist import FrozenList, PyFrozenList 9 | 10 | 11 | class FrozenListMixin: 12 | FrozenList = NotImplemented 13 | 14 | SKIP_METHODS = { 15 | "__abstractmethods__", 16 | "__slots__", 17 | "__static_attributes__", 18 | "__firstlineno__", 19 | } 20 | 21 | def test___class_getitem__(self) -> None: 22 | assert self.FrozenList[str] is not None 23 | 24 | def test_subclass(self) -> None: 25 | assert issubclass(self.FrozenList, MutableSequence) 26 | 27 | def test_iface(self) -> None: 28 | for name in set(dir(MutableSequence)) - self.SKIP_METHODS: 29 | if name.startswith("_") and not name.endswith("_"): 30 | continue 31 | assert hasattr(self.FrozenList, name) 32 | 33 | def test_ctor_default(self) -> None: 34 | _list = self.FrozenList([]) 35 | assert not _list.frozen 36 | 37 | def test_ctor(self) -> None: 38 | _list = self.FrozenList([1]) 39 | assert not _list.frozen 40 | 41 | def test_ctor_copy_list(self) -> None: 42 | orig = [1] 43 | _list = self.FrozenList(orig) 44 | del _list[0] 45 | assert _list != orig 46 | 47 | def test_freeze(self) -> None: 48 | _list = self.FrozenList() 49 | _list.freeze() 50 | assert _list.frozen 51 | 52 | def test_repr(self) -> None: 53 | _list = self.FrozenList([1]) 54 | assert repr(_list) == "" 55 | _list.freeze() 56 | assert repr(_list) == "" 57 | 58 | def test_getitem(self) -> None: 59 | _list = self.FrozenList([1, 2]) 60 | assert _list[1] == 2 61 | 62 | def test_setitem(self) -> None: 63 | _list = self.FrozenList([1, 2]) 64 | _list[1] = 3 65 | assert _list[1] == 3 66 | 67 | def test_delitem(self) -> None: 68 | _list = self.FrozenList([1, 2]) 69 | del _list[0] 70 | assert len(_list) == 1 71 | assert _list[0] == 2 72 | 73 | def test_len(self) -> None: 74 | _list = self.FrozenList([1]) 75 | assert len(_list) == 1 76 | 77 | def test_iter(self) -> None: 78 | _list = self.FrozenList([1, 2]) 79 | assert list(iter(_list)) == [1, 2] 80 | 81 | def test_reversed(self) -> None: 82 | _list = self.FrozenList([1, 2]) 83 | assert list(reversed(_list)) == [2, 1] 84 | 85 | def test_eq(self) -> None: 86 | _list = self.FrozenList([1]) 87 | assert _list == [1] 88 | 89 | def test_ne(self) -> None: 90 | _list = self.FrozenList([1]) 91 | assert _list != [2] 92 | 93 | def test_le(self) -> None: 94 | _list = self.FrozenList([1]) 95 | assert _list <= [1] 96 | 97 | def test_lt(self) -> None: 98 | _list = self.FrozenList([1]) 99 | assert _list < [3] 100 | 101 | def test_ge(self) -> None: 102 | _list = self.FrozenList([1]) 103 | assert _list >= [1] 104 | 105 | def test_gt(self) -> None: 106 | _list = self.FrozenList([2]) 107 | assert _list > [1] 108 | 109 | def test_insert(self) -> None: 110 | _list = self.FrozenList([2]) 111 | _list.insert(0, 1) 112 | assert _list == [1, 2] 113 | 114 | def test_frozen_setitem(self) -> None: 115 | _list = self.FrozenList([1]) 116 | _list.freeze() 117 | with pytest.raises(RuntimeError): 118 | _list[0] = 2 119 | 120 | def test_frozen_delitem(self) -> None: 121 | _list = self.FrozenList([1]) 122 | _list.freeze() 123 | with pytest.raises(RuntimeError): 124 | del _list[0] 125 | 126 | def test_frozen_insert(self) -> None: 127 | _list = self.FrozenList([1]) 128 | _list.freeze() 129 | with pytest.raises(RuntimeError): 130 | _list.insert(0, 2) 131 | 132 | def test_contains(self) -> None: 133 | _list = self.FrozenList([2]) 134 | assert 2 in _list 135 | 136 | def test_iadd(self) -> None: 137 | _list = self.FrozenList([1]) 138 | _list += [2] 139 | assert _list == [1, 2] 140 | 141 | def test_iadd_frozen(self) -> None: 142 | _list = self.FrozenList([1]) 143 | _list.freeze() 144 | with pytest.raises(RuntimeError): 145 | _list += [2] 146 | assert _list == [1] 147 | 148 | def test_index(self) -> None: 149 | _list = self.FrozenList([1]) 150 | assert _list.index(1) == 0 151 | 152 | def test_remove(self) -> None: 153 | _list = self.FrozenList([1]) 154 | _list.remove(1) 155 | assert len(_list) == 0 156 | 157 | def test_remove_frozen(self) -> None: 158 | _list = self.FrozenList([1]) 159 | _list.freeze() 160 | with pytest.raises(RuntimeError): 161 | _list.remove(1) 162 | assert _list == [1] 163 | 164 | def test_clear(self) -> None: 165 | _list = self.FrozenList([1]) 166 | _list.clear() 167 | assert len(_list) == 0 168 | 169 | def test_clear_frozen(self) -> None: 170 | _list = self.FrozenList([1]) 171 | _list.freeze() 172 | with pytest.raises(RuntimeError): 173 | _list.clear() 174 | assert _list == [1] 175 | 176 | def test_extend(self) -> None: 177 | _list = self.FrozenList([1]) 178 | _list.extend([2]) 179 | assert _list == [1, 2] 180 | 181 | def test_extend_frozen(self) -> None: 182 | _list = self.FrozenList([1]) 183 | _list.freeze() 184 | with pytest.raises(RuntimeError): 185 | _list.extend([2]) 186 | assert _list == [1] 187 | 188 | def test_reverse(self) -> None: 189 | _list = self.FrozenList([1, 2]) 190 | _list.reverse() 191 | assert _list == [2, 1] 192 | 193 | def test_reverse_frozen(self) -> None: 194 | _list = self.FrozenList([1, 2]) 195 | _list.freeze() 196 | with pytest.raises(RuntimeError): 197 | _list.reverse() 198 | assert _list == [1, 2] 199 | 200 | def test_pop(self) -> None: 201 | _list = self.FrozenList([1, 2]) 202 | assert _list.pop(0) == 1 203 | assert _list == [2] 204 | 205 | def test_pop_default(self) -> None: 206 | _list = self.FrozenList([1, 2]) 207 | assert _list.pop() == 2 208 | assert _list == [1] 209 | 210 | def test_pop_frozen(self) -> None: 211 | _list = self.FrozenList([1, 2]) 212 | _list.freeze() 213 | with pytest.raises(RuntimeError): 214 | _list.pop() 215 | assert _list == [1, 2] 216 | 217 | def test_append(self) -> None: 218 | _list = self.FrozenList([1, 2]) 219 | _list.append(3) 220 | assert _list == [1, 2, 3] 221 | 222 | def test_append_frozen(self) -> None: 223 | _list = self.FrozenList([1, 2]) 224 | _list.freeze() 225 | with pytest.raises(RuntimeError): 226 | _list.append(3) 227 | assert _list == [1, 2] 228 | 229 | def test_hash(self) -> None: 230 | _list = self.FrozenList([1, 2]) 231 | with pytest.raises(RuntimeError): 232 | hash(_list) 233 | 234 | def test_hash_frozen(self) -> None: 235 | _list = self.FrozenList([1, 2]) 236 | _list.freeze() 237 | h = hash(_list) 238 | assert h == hash((1, 2)) 239 | 240 | def test_dict_key(self) -> None: 241 | _list = self.FrozenList([1, 2]) 242 | with pytest.raises(RuntimeError): 243 | {_list: "hello"} 244 | _list.freeze() 245 | {_list: "hello"} 246 | 247 | def test_count(self) -> None: 248 | _list = self.FrozenList([1, 2]) 249 | assert _list.count(1) == 1 250 | 251 | 252 | class TestFrozenList(FrozenListMixin): 253 | FrozenList = FrozenList # type: ignore[assignment] # FIXME 254 | 255 | 256 | class TestFrozenListPy(FrozenListMixin): 257 | FrozenList = PyFrozenList # type: ignore[assignment] # FIXME 258 | -------------------------------------------------------------------------------- /tools/drop_merged_branches.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | git remote prune origin 4 | -------------------------------------------------------------------------------- /tools/run_docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | package_name="$1" 6 | if [ -z "$package_name" ] 7 | then 8 | >&2 echo "Please pass package name as a first argument of this script ($0)" 9 | exit 1 10 | fi 11 | 12 | manylinux1_image_prefix="quay.io/pypa/manylinux1_" 13 | dock_ext_args="" 14 | declare -A docker_pull_pids=() # This syntax requires at least bash v4 15 | 16 | for arch in x86_64 i686 17 | do 18 | docker pull "${manylinux1_image_prefix}${arch}" & 19 | docker_pull_pids[$arch]=$! 20 | done 21 | 22 | echo Creating dist folder with privileges of host-machine user 23 | mkdir -p dist # This is required to be created with host-machine user privileges 24 | 25 | for arch in x86_64 i686 26 | do 27 | echo 28 | echo 29 | arch_pull_pid=${docker_pull_pids[$arch]} 30 | echo Waiting for docker pull PID $arch_pull_pid to complete downloading container for $arch arch... 31 | wait $arch_pull_pid # await for docker image for current arch to be pulled from hub 32 | [ $arch == "i686" ] && dock_ext_args="linux32" 33 | 34 | echo Building wheel for $arch arch 35 | docker run --rm -v `pwd`:/io "${manylinux1_image_prefix}${arch}" $dock_ext_args /io/tools/build-wheels.sh "$package_name" 36 | 37 | dock_ext_args="" # Reset docker args, just in case 38 | done 39 | 40 | set +u 41 | -------------------------------------------------------------------------------- /towncrier.toml: -------------------------------------------------------------------------------- 1 | [tool.towncrier] 2 | package = "frozenlist" 3 | filename = "CHANGES.rst" 4 | directory = "CHANGES/" 5 | title_format = "v{version}" 6 | template = "CHANGES/.TEMPLATE.rst" 7 | issue_format = "{issue}" 8 | 9 | # NOTE: The types are declared because: 10 | # NOTE: - there is no mechanism to override just the value of 11 | # NOTE: `tool.towncrier.type.misc.showcontent`; 12 | # NOTE: - and, we want to declare extra non-default types for 13 | # NOTE: clarity and flexibility. 14 | 15 | [[tool.towncrier.section]] 16 | path = "" 17 | 18 | [[tool.towncrier.type]] 19 | # Something we deemed an improper undesired behavior that got corrected 20 | # in the release to match pre-agreed expectations. 21 | directory = "bugfix" 22 | name = "Bug fixes" 23 | showcontent = true 24 | 25 | [[tool.towncrier.type]] 26 | # New behaviors, public APIs. That sort of stuff. 27 | directory = "feature" 28 | name = "Features" 29 | showcontent = true 30 | 31 | [[tool.towncrier.type]] 32 | # Declarations of future API removals and breaking changes in behavior. 33 | directory = "deprecation" 34 | name = "Deprecations (removal in next major release)" 35 | showcontent = true 36 | 37 | [[tool.towncrier.type]] 38 | # When something public gets removed in a breaking way. Could be 39 | # deprecated in an earlier release. 40 | directory = "breaking" 41 | name = "Removals and backward incompatible breaking changes" 42 | showcontent = true 43 | 44 | [[tool.towncrier.type]] 45 | # Notable updates to the documentation structure or build process. 46 | directory = "doc" 47 | name = "Improved documentation" 48 | showcontent = true 49 | 50 | [[tool.towncrier.type]] 51 | # Notes for downstreams about unobvious side effects and tooling. Changes 52 | # in the test invocation considerations and runtime assumptions. 53 | directory = "packaging" 54 | name = "Packaging updates and notes for downstreams" 55 | showcontent = true 56 | 57 | [[tool.towncrier.type]] 58 | # Stuff that affects the contributor experience. e.g. Running tests, 59 | # building the docs, setting up the development environment. 60 | directory = "contrib" 61 | name = "Contributor-facing changes" 62 | showcontent = true 63 | 64 | [[tool.towncrier.type]] 65 | # Changes that are hard to assign to any of the above categories. 66 | directory = "misc" 67 | name = "Miscellaneous internal changes" 68 | showcontent = true 69 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | 3 | envlist = 4 | check, 5 | clean, 6 | py3{13,12,11,10,9}-{cython,pure}, 7 | pypy3{11,10,9}-pure, 8 | report, 9 | 10 | [testenv] 11 | 12 | usedevelop = True 13 | 14 | deps = 15 | pytest 16 | pytest-xdist 17 | pytest-cov 18 | cython: cython 19 | 20 | commands = 21 | pytest --cov-append {posargs} 22 | 23 | setenv = 24 | pure: FROZENLIST_NO_EXTENSIONS = 1 25 | 26 | [testenv:check] 27 | 28 | deps = 29 | wheel 30 | flake8 31 | docutils 32 | pygments 33 | 34 | commands = 35 | flake8 frozenlist tests 36 | python setup.py check -rms 37 | 38 | basepython: 39 | python3.13 40 | 41 | [testenv:clean] 42 | 43 | deps = coverage 44 | skip_install = true 45 | 46 | commands = 47 | coverage erase 48 | 49 | basepython: 50 | python3.13 51 | 52 | [testenv:report] 53 | 54 | deps = coverage 55 | skip_install = true 56 | 57 | commands = 58 | coverage report 59 | coverage html 60 | echo "open file://{toxinidir}/htmlcov/index.html" 61 | 62 | whitelist_externals = 63 | echo 64 | 65 | basepython: 66 | python3.13 67 | --------------------------------------------------------------------------------