pax_global_header00006660000000000000000000000064152047601500014512gustar00rootroot0000000000000052 comment=07e7a92f893b2d0d44788a42d79a0ffe6f91fc77 Bluetooth-Devices-ulid-transform-f51d4f1/000077500000000000000000000000001520476015000204175ustar00rootroot00000000000000Bluetooth-Devices-ulid-transform-f51d4f1/.all-contributorsrc000066400000000000000000000004551520476015000242540ustar00rootroot00000000000000{ "projectName": "ulid-transform", "projectOwner": "bluetooth-devices", "repoType": "github", "repoHost": "https://github.com", "files": ["README.md"], "imageSize": 80, "commit": true, "commitConvention": "angular", "contributors": [], "contributorsPerLine": 7, "skipCi": true } Bluetooth-Devices-ulid-transform-f51d4f1/.copier-answers.yml000066400000000000000000000010231520476015000241550ustar00rootroot00000000000000# Changes here will be overwritten by Copier _commit: 5559e3e _src_path: gh:browniebroke/pypackage-template add_me_as_contributor: false copyright_year: '2022' documentation: false email: nick@koston.org full_name: J. Nick Koston github_username: bluetooth-devices initial_commit: true open_source_license: MIT package_name: ulid_transform project_name: Fast ULID transformations project_short_description: Create and transform ULIDs project_slug: ulid-transform run_poetry_install: true setup_github: true setup_pre_commit: true Bluetooth-Devices-ulid-transform-f51d4f1/.editorconfig000066400000000000000000000004441520476015000230760ustar00rootroot00000000000000# http://editorconfig.org root = true [*] indent_style = space indent_size = 4 trim_trailing_whitespace = true insert_final_newline = true charset = utf-8 end_of_line = lf [*.bat] indent_style = tab end_of_line = crlf [LICENSE] insert_final_newline = false [Makefile] indent_style = tab Bluetooth-Devices-ulid-transform-f51d4f1/.git-blame-ignore-revs000066400000000000000000000000511520476015000245130ustar00rootroot00000000000000655f63973c5b3f4bcb1bdb42f7220cffc03a713c Bluetooth-Devices-ulid-transform-f51d4f1/.github/000077500000000000000000000000001520476015000217575ustar00rootroot00000000000000Bluetooth-Devices-ulid-transform-f51d4f1/.github/ISSUE_TEMPLATE/000077500000000000000000000000001520476015000241425ustar00rootroot00000000000000Bluetooth-Devices-ulid-transform-f51d4f1/.github/ISSUE_TEMPLATE/1-bug_report.md000066400000000000000000000004221520476015000267700ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve labels: bug --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: **Additional context** Add any other context about the problem here. Bluetooth-Devices-ulid-transform-f51d4f1/.github/ISSUE_TEMPLATE/2-feature-request.md000066400000000000000000000006721520476015000277510ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project labels: enhancement --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Additional context** Add any other context or screenshots about the feature request here. Bluetooth-Devices-ulid-transform-f51d4f1/.github/dependabot.yml000066400000000000000000000013441520476015000246110ustar00rootroot00000000000000# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "monthly" commit-message: prefix: "chore(ci): " groups: github-actions: patterns: - "*" - package-ecosystem: "pip" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" Bluetooth-Devices-ulid-transform-f51d4f1/.github/workflows/000077500000000000000000000000001520476015000240145ustar00rootroot00000000000000Bluetooth-Devices-ulid-transform-f51d4f1/.github/workflows/ci.yml000066400000000000000000000301721520476015000251350ustar00rootroot00000000000000name: CI on: push: branches: - main pull_request: concurrency: group: ${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: "3.11" - uses: pre-commit/action@v3.0.1 # Make sure commit messages follow the conventional commits convention: # https://www.conventionalcommits.org commitlint: name: Lint Commit Messages runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: fetch-depth: 0 - uses: wagoid/commitlint-github-action@v6.2.1 test: strategy: fail-fast: false matrix: python-version: - "3.11" - "3.12" - "3.13" - "3.14" - "3.14t" os: - ubuntu-latest - macOS-latest extension: - "skip_extension" - "use_extension" include: - python-version: "3.13" os: windows-latest extension: "skip_extension" - python-version: "3.13" os: windows-latest extension: "use_extension" runs-on: ${{ matrix.os }} env: POETRY_VIRTUALENVS_IN_PROJECT: "true" steps: - uses: actions/checkout@v6 - name: Set up uv id: setup-uv uses: astral-sh/setup-uv@v8.1.0 with: enable-cache: true python-version: ${{ matrix.python-version }} - name: Install poetry run: uv tool install poetry - name: Cache poetry virtualenv uses: actions/cache@v4 with: path: .venv key: poetry-${{ runner.os }}-${{ steps.setup-uv.outputs.python-version }}-${{ matrix.extension }}-${{ hashFiles('poetry.lock') }} - name: Install Dependencies # the separate `pip install` phase is required because Poetry # appears to hide the output of `pip install` commands (and possibly # doesn't pass all environment variables through to the build) run: | pip install --no-cache -v -e . poetry install --only=main,dev shell: bash env: REQUIRE_EXTENSION: ${{ matrix.extension == 'use_extension' }} SKIP_EXTENSION: ${{ matrix.extension == 'skip_extension' }} - name: Test with Pytest run: poetry run pytest -v -Wdefault --cov=ulid_transform --cov-report=term-missing:skip-covered --cov-report=xml tests shell: bash env: REQUIRE_EXTENSION: ${{ matrix.extension == 'use_extension' }} - name: Upload coverage to Codecov uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} test32bit: name: "Test on 32-bit Alpine Linux" runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Setup Alpine Linux v3.22 for x86 id: setup-alpine continue-on-error: true uses: jirutka/setup-alpine@v1 with: arch: x86 branch: v3.22 - name: Retry Setup Alpine Linux v3.22 for x86 if: steps.setup-alpine.outcome == 'failure' uses: jirutka/setup-alpine@v1 with: arch: x86 branch: v3.22 - name: Set up Python run: | apk add gcc g++ musl-dev python3-dev py3-pip poetry libffi-dev shell: alpine.sh --root {0} - name: Install Dependencies run: | REQUIRE_EXTENSION=1 poetry -vvv install --only=main,dev shell: alpine.sh --root {0} - name: Test with Pytest run: poetry run pytest -v -Wdefault --cov=ulid_transform --cov-report=term-missing:skip-covered --cov-report=xml tests shell: alpine.sh --root {0} sdist_safe_path: name: "sdist install under PYTHONSAFEPATH" runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v6 with: python-version: "3.13" - name: Build sdist run: | pip install poetry poetry build -f sdist shell: bash - name: Install sdist under PYTHONSAFEPATH (regression guard for #137) run: | PYTHONSAFEPATH=1 REQUIRE_EXTENSION=1 pip install -v dist/*.tar.gz shell: bash - name: Verify the C extension imports run: | PYTHONSAFEPATH=1 python -c "import ulid_transform._ulid_impl; from ulid_transform import ulid_now; print(ulid_now())" shell: bash benchmark: strategy: fail-fast: false matrix: python-version: - "3.13" os: - ubuntu-latest runs-on: ${{ matrix.os }} env: POETRY_VIRTUALENVS_IN_PROJECT: "true" steps: - uses: actions/checkout@v6 - name: Set up uv id: setup-uv uses: astral-sh/setup-uv@v8.1.0 with: enable-cache: true python-version: ${{ matrix.python-version }} - name: Install poetry run: uv tool install poetry - name: Cache poetry virtualenv uses: actions/cache@v4 with: path: .venv key: poetry-${{ runner.os }}-${{ steps.setup-uv.outputs.python-version }}-benchmark-${{ hashFiles('poetry.lock') }} - name: Install Dependencies run: poetry install --only=main,dev,benchmark env: REQUIRE_EXTENSION: 1 - name: Run benchmarks run: | poetry run pytest bench --benchmark-autosave echo '# Benchmark Results' >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY poetry run pytest-benchmark compare --columns=mean,ops >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY shell: bash codspeed_benchmark: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Set up uv uses: astral-sh/setup-uv@v8.1.0 with: enable-cache: true - name: Install poetry run: uv tool install poetry - name: Setup Python 3.13 uses: actions/setup-python@v6 with: python-version: 3.13 cache: "poetry" - name: Install Dependencies run: | REQUIRE_EXTENSION=1 poetry install --only=main,dev shell: bash - name: Run benchmarks uses: CodSpeedHQ/action@v4 with: token: ${{ secrets.CODSPEED_TOKEN }} run: poetry run pytest --no-cov -vvvvv --codspeed tests/benchmarks mode: instrumentation release: needs: - test - lint - test32bit - commitlint - sdist_safe_path runs-on: ubuntu-latest environment: release concurrency: group: release-${{ github.ref }} cancel-in-progress: false permissions: id-token: write contents: write outputs: released: ${{ steps.release.outputs.released }} newest_release_tag: ${{ steps.release.outputs.tag }} steps: - uses: actions/checkout@v6 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha || github.ref_name }} # PSR errors on detached HEAD; give it a named branch for PR dry runs - name: Create local branch for PSR dry run if: github.event_name == 'pull_request' run: git switch -c "pr-${{ github.event.pull_request.number }}" # Do a dry run of PSR - name: Test release uses: python-semantic-release/python-semantic-release@v10.5.3 if: github.ref_name != 'main' with: no_operation_mode: true # On main branch: actual PSR + upload to PyPI & GitHub - name: Release uses: python-semantic-release/python-semantic-release@v10.5.3 id: release if: github.ref_name == 'main' with: github_token: ${{ secrets.GITHUB_TOKEN }} - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 if: steps.release.outputs.released == 'true' - name: Publish package distributions to GitHub Releases uses: python-semantic-release/upload-to-gh-release@main if: steps.release.outputs.released == 'true' with: github_token: ${{ secrets.GITHUB_TOKEN }} build_wheels: needs: [release] if: needs.release.outputs.released == 'true' name: Wheels for ${{ matrix.os }} (${{ matrix.musl == 'musllinux' && 'musllinux' || 'manylinux' }}) ${{ matrix.qemu }} ${{ matrix.pyver }} runs-on: ${{ matrix.os }} strategy: matrix: os: [windows-latest, ubuntu-24.04-arm, ubuntu-latest, macos-latest] qemu: [""] musl: [""] pyver: [""] include: - os: ubuntu-latest musl: "musllinux" - os: ubuntu-24.04-arm musl: "musllinux" # qemu is slow, make a single # runner per Python version - os: ubuntu-24.04-arm qemu: armv7l musl: "musllinux" pyver: cp311 - os: ubuntu-24.04-arm qemu: armv7l musl: "musllinux" pyver: cp312 - os: ubuntu-24.04-arm qemu: armv7l musl: "musllinux" pyver: cp313 - os: ubuntu-24.04-arm qemu: armv7l musl: "musllinux" pyver: cp314 - os: ubuntu-24.04-arm qemu: armv7l musl: "musllinux" pyver: cp314t # qemu is slow, make a single # runner per Python version - os: ubuntu-24.04-arm qemu: armv7l musl: "" pyver: cp311 - os: ubuntu-24.04-arm qemu: armv7l musl: "" pyver: cp312 - os: ubuntu-24.04-arm qemu: armv7l musl: "" pyver: cp313 - os: ubuntu-24.04-arm qemu: armv7l musl: "" pyver: cp314 - os: ubuntu-24.04-arm qemu: armv7l musl: "" pyver: cp314t steps: - uses: actions/checkout@v6 with: ref: ${{ needs.release.outputs.newest_release_tag }} fetch-depth: 0 # Used to host cibuildwheel - name: Set up Python uses: actions/setup-python@v6 with: python-version: "3.12" - name: Set up QEMU if: ${{ matrix.qemu }} uses: docker/setup-qemu-action@v4 with: platforms: all # This should be temporary # xref https://github.com/docker/setup-qemu-action/issues/188 # xref https://github.com/tonistiigi/binfmt/issues/215 image: tonistiigi/binfmt:qemu-v8.1.5 id: qemu - name: Prepare emulation if: ${{ matrix.qemu }} run: | if [[ -n "${{ matrix.qemu }}" ]]; then # Build emulated architectures only if QEMU is set, # use default "auto" otherwise echo "CIBW_ARCHS_LINUX=${{ matrix.qemu }}" >> $GITHUB_ENV fi - name: Limit to a specific Python version on slow QEMU if: ${{ matrix.pyver }} run: | if [[ -n "${{ matrix.pyver }}" ]]; then echo "CIBW_BUILD=${{ matrix.pyver }}*" >> $GITHUB_ENV fi - name: Build wheels uses: pypa/cibuildwheel@v3.4.1 env: CIBW_SKIP: cp36-* cp37-* cp38-* cp39-* cp310-* pp* ${{ matrix.musl == 'musllinux' && '*manylinux*' || '*musllinux*' }} REQUIRE_EXTENSION: 1 - uses: actions/upload-artifact@v7 with: name: wheels-${{ matrix.os }}-${{ matrix.musl }}-${{ matrix.pyver }}-${{ matrix.qemu }} path: ./wheelhouse/*.whl upload_pypi: needs: [build_wheels] runs-on: ubuntu-latest environment: release permissions: id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: - uses: actions/download-artifact@v8 with: # unpacks default artifact into dist/ # if `name: artifact` is omitted, the action will create extra parent dir path: dist pattern: wheels-* merge-multiple: true - uses: pypa/gh-action-pypi-publish@v1.14.0 # To test: repository_url: https://test.pypi.org/legacy/ Bluetooth-Devices-ulid-transform-f51d4f1/.github/workflows/hacktoberfest.yml000066400000000000000000000005341520476015000273650ustar00rootroot00000000000000name: Hacktoberfest on: schedule: # Run every day in October - cron: "0 0 * 10 *" # Run on the 1st of November to revert - cron: "0 13 1 11 *" jobs: hacktoberfest: runs-on: ubuntu-latest steps: - uses: browniebroke/hacktoberfest-labeler-action@v2.6.0 with: github_token: ${{ secrets.GH_PAT }} Bluetooth-Devices-ulid-transform-f51d4f1/.github/workflows/issue-manager.yml000066400000000000000000000013401520476015000272750ustar00rootroot00000000000000name: Issue Manager on: schedule: - cron: "0 0 * * *" issue_comment: types: - created issues: types: - labeled pull_request_target: types: - labeled workflow_dispatch: jobs: issue-manager: runs-on: ubuntu-latest steps: - uses: tiangolo/issue-manager@0.6.0 with: token: ${{ secrets.GITHUB_TOKEN }} config: > { "answered": { "message": "Assuming the original issue was solved, it will be automatically closed now." }, "waiting": { "message": "Automatically closing. To re-open, please provide the additional information requested." } } Bluetooth-Devices-ulid-transform-f51d4f1/.gitignore000066400000000000000000000041511520476015000224100ustar00rootroot00000000000000# Created by .ignore support plugin (hsz.mobi) ### Python template # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ cover/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder {{package_name}} settings .spyderproject .spyproject # Rope {{package_name}} settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ # Cython debug symbols (legacy) cython_debug/ # PyCharm files .idea/ Bluetooth-Devices-ulid-transform-f51d4f1/.gitpod.yml000066400000000000000000000003061520476015000225050ustar00rootroot00000000000000tasks: - command: | pip install poetry PIP_USER=false poetry install - command: | pip install pre-commit pre-commit install PIP_USER=false pre-commit install-hooks Bluetooth-Devices-ulid-transform-f51d4f1/.pre-commit-config.yaml000066400000000000000000000035451520476015000247070ustar00rootroot00000000000000# See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks exclude: "CHANGELOG.md|.copier-answers.yml" default_stages: [pre-commit] ci: autofix_commit_msg: "chore(pre-commit.ci): auto fixes" autoupdate_commit_msg: "chore(pre-commit.ci): pre-commit autoupdate" repos: - repo: https://github.com/commitizen-tools/commitizen rev: v4.16.2 hooks: - id: commitizen stages: [commit-msg] - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - id: debug-statements - id: check-builtin-literals - id: check-case-conflict - id: check-docstring-first - id: check-json - id: check-toml - id: check-xml - id: check-yaml - id: detect-private-key - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/python-poetry/poetry rev: 2.4.1 hooks: - id: poetry-check - repo: https://github.com/pre-commit/mirrors-prettier rev: v4.0.0-alpha.8 hooks: - id: prettier args: ["--tab-width", "2"] additional_dependencies: - prettier@3.5.1 - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.15.13 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - id: ruff-format - repo: https://github.com/codespell-project/codespell rev: v2.4.2 hooks: - id: codespell args: - --ignore-words-list=ans - repo: https://github.com/pre-commit/mirrors-mypy rev: v2.1.0 hooks: - id: mypy additional_dependencies: [] exclude: "src/ulid_transform/__init__.pyi" - repo: https://github.com/pre-commit/mirrors-clang-format rev: v22.1.5 hooks: - id: clang-format types_or: - "c++" - "c" - "cuda" args: [-style=Webkit, -i] Bluetooth-Devices-ulid-transform-f51d4f1/CHANGELOG.md000066400000000000000000000663041520476015000222410ustar00rootroot00000000000000# CHANGELOG ## v1.4.0 (2025-03-07) ### Chores - Enable clang-format ([#91](https://github.com/Bluetooth-Devices/ulid-transform/pull/91), [`655f639`](https://github.com/Bluetooth-Devices/ulid-transform/commit/655f63973c5b3f4bcb1bdb42f7220cffc03a713c)) ### Features - Speed up random data and improve randomness distribution ([#92](https://github.com/Bluetooth-Devices/ulid-transform/pull/92), [`dfb3a3a`](https://github.com/Bluetooth-Devices/ulid-transform/commit/dfb3a3a39132c2395bf2a911304fa8555ebde9c3)) ## v1.3.0 (2025-03-05) ### Chores - Pin prettier to 3.5.1 to fix CI ([#83](https://github.com/Bluetooth-Devices/ulid-transform/pull/83), [`d9b4fc1`](https://github.com/Bluetooth-Devices/ulid-transform/commit/d9b4fc11a5f23ee00e01778726e54cedf368820c)) - Remove unused labels workflow ([#82](https://github.com/Bluetooth-Devices/ulid-transform/pull/82), [`98f2b08`](https://github.com/Bluetooth-Devices/ulid-transform/commit/98f2b08803abf08b69f1e5f5bc25cc8059721504)) - Update wheel build workflow to include armv7l ([#89](https://github.com/Bluetooth-Devices/ulid-transform/pull/89), [`3132a42`](https://github.com/Bluetooth-Devices/ulid-transform/commit/3132a4213e135f60b1552c90daf18be9fd2adc29)) - **ci**: Bump python-semantic-release/python-semantic-release from 9.20.0 to 9.21.0 in the github-actions group ([#85](https://github.com/Bluetooth-Devices/ulid-transform/pull/85), [`332ae0d`](https://github.com/Bluetooth-Devices/ulid-transform/commit/332ae0d5b9ed792816fc5d9ebeb8c9c9d8e08535)) - **ci**: Bump the github-actions group with 7 updates ([#81](https://github.com/Bluetooth-Devices/ulid-transform/pull/81), [`6130f2c`](https://github.com/Bluetooth-Devices/ulid-transform/commit/6130f2ca64dcabec68f5434f0605e47a60695538)) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston - **deps-dev**: Bump pytest from 8.3.4 to 8.3.5 ([#88](https://github.com/Bluetooth-Devices/ulid-transform/pull/88), [`9cef5f7`](https://github.com/Bluetooth-Devices/ulid-transform/commit/9cef5f7968ade50883e55a3ece159ec73e6c6de7)) Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.3.4 to 8.3.5. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.3.4...8.3.5) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps-dev**: Bump setuptools from 75.8.0 to 75.8.2 ([#87](https://github.com/Bluetooth-Devices/ulid-transform/pull/87), [`1d38ed2`](https://github.com/Bluetooth-Devices/ulid-transform/commit/1d38ed2817291362109c8d963088ecc4eb2b7228)) Bumps [setuptools](https://github.com/pypa/setuptools) from 75.8.0 to 75.8.2. - [Release notes](https://github.com/pypa/setuptools/releases) - [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst) - [Commits](https://github.com/pypa/setuptools/compare/v75.8.0...v75.8.2) --- updated-dependencies: - dependency-name: setuptools dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#84](https://github.com/Bluetooth-Devices/ulid-transform/pull/84), [`6f17818`](https://github.com/Bluetooth-Devices/ulid-transform/commit/6f1781866dcd51d01554f5c6028de5f31bef4386)) - **pre-commit.ci**: Pre-commit autoupdate ([#86](https://github.com/Bluetooth-Devices/ulid-transform/pull/86), [`24d0d69`](https://github.com/Bluetooth-Devices/ulid-transform/commit/24d0d69e2270639823ae67600ba9e88db40c0d4d)) updates: - [github.com/commitizen-tools/commitizen: v4.2.2 → v4.4.1](https://github.com/commitizen-tools/commitizen/compare/v4.2.2...v4.4.1) - [github.com/astral-sh/ruff-pre-commit: v0.9.7 → v0.9.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.7...v0.9.9) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ### Features - Reduce wheel sizes ([#90](https://github.com/Bluetooth-Devices/ulid-transform/pull/90), [`f2df716`](https://github.com/Bluetooth-Devices/ulid-transform/commit/f2df71640d8a56f9df96104ad78e8e6678821153)) Add -g0 to compile options to reduce wheel sizes ## v1.2.1 (2025-02-22) ### Bug Fixes - Update repo links to use bluetooth-devices ([#80](https://github.com/Bluetooth-Devices/ulid-transform/pull/80), [`3beca28`](https://github.com/Bluetooth-Devices/ulid-transform/commit/3beca289c6f0e728d01461a396ac93de15730d22)) ### Chores - Bump upload/download actions to v4 ([#73](https://github.com/Bluetooth-Devices/ulid-transform/pull/73), [`670b623`](https://github.com/Bluetooth-Devices/ulid-transform/commit/670b623d718d8b6622c5b0840a0dfa175ce7a0af)) - Create dependabot.yml ([`b7ad481`](https://github.com/Bluetooth-Devices/ulid-transform/commit/b7ad4816fe3df9e4681c63ce2825658af7a51251)) - Update dependabot.yml to include GHA ([`3f9f6d7`](https://github.com/Bluetooth-Devices/ulid-transform/commit/3f9f6d7294d600c50a4f8a003c6da794d1cdb294)) - **deps-dev**: Bump cython from 3.0.11 to 3.0.12 ([#79](https://github.com/Bluetooth-Devices/ulid-transform/pull/79), [`36c741a`](https://github.com/Bluetooth-Devices/ulid-transform/commit/36c741a6da458fd7ed92c613e3bdc4fdb7a1726a)) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps-dev**: Bump pytest from 7.4.4 to 8.3.4 ([#69](https://github.com/Bluetooth-Devices/ulid-transform/pull/69), [`3aad7db`](https://github.com/Bluetooth-Devices/ulid-transform/commit/3aad7db0fb0c75dc7edd8f698565c336f85ba43b)) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps-dev**: Bump pytest-benchmark from 4.0.0 to 5.1.0 ([#71](https://github.com/Bluetooth-Devices/ulid-transform/pull/71), [`b0fffc6`](https://github.com/Bluetooth-Devices/ulid-transform/commit/b0fffc6908b722349ccfcf147a16f328c7ae5d49)) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps-dev**: Bump pytest-codspeed from 3.1.2 to 3.2.0 ([#76](https://github.com/Bluetooth-Devices/ulid-transform/pull/76), [`0781d3c`](https://github.com/Bluetooth-Devices/ulid-transform/commit/0781d3c4aff06e5c51f740fd14eb3cc83f7ffbd2)) - **deps-dev**: Bump pytest-cov from 3.0.0 to 6.0.0 ([#67](https://github.com/Bluetooth-Devices/ulid-transform/pull/67), [`690cc84`](https://github.com/Bluetooth-Devices/ulid-transform/commit/690cc84106c2f489e4575a3094748923e9d23cfc)) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **deps-dev**: Bump setuptools from 65.7.0 to 75.8.0 ([#68](https://github.com/Bluetooth-Devices/ulid-transform/pull/68), [`1847efa`](https://github.com/Bluetooth-Devices/ulid-transform/commit/1847efaba902ad3d29a840e6c4db6db102189bdc)) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#72](https://github.com/Bluetooth-Devices/ulid-transform/pull/72), [`82c8ed5`](https://github.com/Bluetooth-Devices/ulid-transform/commit/82c8ed5a1504be470febf971a7c27b83d33d3422)) - **pre-commit.ci**: Pre-commit autoupdate ([#74](https://github.com/Bluetooth-Devices/ulid-transform/pull/74), [`ec6b661`](https://github.com/Bluetooth-Devices/ulid-transform/commit/ec6b661e6674a28c8d6bb41e888522340667c62f)) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#75](https://github.com/Bluetooth-Devices/ulid-transform/pull/75), [`1744f59`](https://github.com/Bluetooth-Devices/ulid-transform/commit/1744f59c264dad9b448a849f965b7bd238fca735)) - **pre-commit.ci**: Pre-commit autoupdate ([#77](https://github.com/Bluetooth-Devices/ulid-transform/pull/77), [`af69a21`](https://github.com/Bluetooth-Devices/ulid-transform/commit/af69a21b6e3b1b08bd07173f7d1bffb7c8f36bf1)) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#78](https://github.com/Bluetooth-Devices/ulid-transform/pull/78), [`76a8ad1`](https://github.com/Bluetooth-Devices/ulid-transform/commit/76a8ad18f3c5b0407ea6ab2824a43b23699b3101)) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ## v1.2.0 (2025-01-17) ### Features - Migrate benchmarks to Python 3.13 ([#66](https://github.com/Bluetooth-Devices/ulid-transform/pull/66), [`d160dd3`](https://github.com/Bluetooth-Devices/ulid-transform/commit/d160dd333020f22e902dab897b0c7839008fce91)) ## v1.1.0 (2025-01-17) ### Chores - Add codspeed benchmarks ([#64](https://github.com/Bluetooth-Devices/ulid-transform/pull/64), [`f7c563c`](https://github.com/Bluetooth-Devices/ulid-transform/commit/f7c563c579241adf69d7c676ca0111c35d11e43b)) - Wheel builds should only happen on release ([#59](https://github.com/Bluetooth-Devices/ulid-transform/pull/59), [`d4cbb09`](https://github.com/Bluetooth-Devices/ulid-transform/commit/d4cbb09b8c547ac9c0b8b9a41971bfadc61e4c7b)) - **pre-commit.ci**: Pre-commit autoupdate ([#54](https://github.com/Bluetooth-Devices/ulid-transform/pull/54), [`b81af0e`](https://github.com/Bluetooth-Devices/ulid-transform/commit/b81af0e631c949607a871adf2e1abd196237bf5c)) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#55](https://github.com/Bluetooth-Devices/ulid-transform/pull/55), [`6d5155c`](https://github.com/Bluetooth-Devices/ulid-transform/commit/6d5155c56674b245b3205d6e1491d204e2825a6e)) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#56](https://github.com/Bluetooth-Devices/ulid-transform/pull/56), [`c691406`](https://github.com/Bluetooth-Devices/ulid-transform/commit/c69140612d0840e8b40ed2b6ab55687f7746ffa6)) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#57](https://github.com/Bluetooth-Devices/ulid-transform/pull/57), [`c30be1a`](https://github.com/Bluetooth-Devices/ulid-transform/commit/c30be1a622544fb8c645b03e848911b09b3d35a3)) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#58](https://github.com/Bluetooth-Devices/ulid-transform/pull/58), [`2c2986f`](https://github.com/Bluetooth-Devices/ulid-transform/commit/2c2986f5db4aac03b1416f2cdac50ba0ffdcf418)) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#60](https://github.com/Bluetooth-Devices/ulid-transform/pull/60), [`56db2e2`](https://github.com/Bluetooth-Devices/ulid-transform/commit/56db2e2ddb59cd5037c434185ad76ced699690ca)) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#61](https://github.com/Bluetooth-Devices/ulid-transform/pull/61), [`e6ea31c`](https://github.com/Bluetooth-Devices/ulid-transform/commit/e6ea31c16334a1979919a751e4e50daab909d154)) - **pre-commit.ci**: Pre-commit autoupdate ([#62](https://github.com/Bluetooth-Devices/ulid-transform/pull/62), [`5fb4ab4`](https://github.com/Bluetooth-Devices/ulid-transform/commit/5fb4ab4d5a61fa2dcb2bdca0a967c47ebd4a4144)) - **pre-commit.ci**: Pre-commit autoupdate ([#63](https://github.com/Bluetooth-Devices/ulid-transform/pull/63), [`86ab538`](https://github.com/Bluetooth-Devices/ulid-transform/commit/86ab538b73bbc534a54c97b36ef31205d2820e5d)) ### Features - Add aarch64 wheels ([#65](https://github.com/Bluetooth-Devices/ulid-transform/pull/65), [`950376b`](https://github.com/Bluetooth-Devices/ulid-transform/commit/950376beb4e3d85a9f1e79a6e1408900910099d9)) ## v1.0.2 (2024-08-24) ### Bug Fixes - Switch to github trusted publishing to fix ci ([#53](https://github.com/Bluetooth-Devices/ulid-transform/pull/53), [`dfaf168`](https://github.com/Bluetooth-Devices/ulid-transform/commit/dfaf1687c32d07ea8e92358a9f1cb8275a6f6b54)) ## v1.0.1 (2024-08-24) ### Bug Fixes - Bump ci to run on python 3.11 ([#52](https://github.com/Bluetooth-Devices/ulid-transform/pull/52), [`857e9b8`](https://github.com/Bluetooth-Devices/ulid-transform/commit/857e9b80787e20565d41029dac5f655b1f152672)) ## v1.0.0 (2024-08-24) ### Features - Drop python 3.10 support ([#51](https://github.com/Bluetooth-Devices/ulid-transform/pull/51), [`b93b779`](https://github.com/Bluetooth-Devices/ulid-transform/commit/b93b77957945c38f334912d64e7c7aa4137863e3)) ## v0.14.0 (2024-08-24) ### Chores - **pre-commit.ci**: Pre-commit autoupdate ([#47](https://github.com/Bluetooth-Devices/ulid-transform/pull/47), [`3192c1c`](https://github.com/Bluetooth-Devices/ulid-transform/commit/3192c1cdf5a9c22a3d63193ee21ca133bc95006a)) updates: - [github.com/astral-sh/ruff-pre-commit: v0.5.5 → v0.5.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.5...v0.5.6) - [github.com/PyCQA/flake8: 7.1.0 → 7.1.1](https://github.com/PyCQA/flake8/compare/7.1.0...7.1.1) - [github.com/pre-commit/mirrors-mypy: v1.11.0 → v1.11.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.11.0...v1.11.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#48](https://github.com/Bluetooth-Devices/ulid-transform/pull/48), [`48fbe45`](https://github.com/Bluetooth-Devices/ulid-transform/commit/48fbe45e65c5a249a7c1eea3b8519b69f4f71020)) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#49](https://github.com/Bluetooth-Devices/ulid-transform/pull/49), [`3946e2f`](https://github.com/Bluetooth-Devices/ulid-transform/commit/3946e2ff626e3034685cf6b483aa6c204dc8bca6)) updates: - [github.com/astral-sh/ruff-pre-commit: v0.5.7 → v0.6.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.7...v0.6.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ### Features - Python 3.13 support ([#50](https://github.com/Bluetooth-Devices/ulid-transform/pull/50), [`c50e08a`](https://github.com/Bluetooth-Devices/ulid-transform/commit/c50e08ac1c019ee8a3313780a7fd180f60e4db3f)) ## v0.13.1 (2024-07-30) ### Bug Fixes - Add pyi stubs for cython implementation ([#46](https://github.com/Bluetooth-Devices/ulid-transform/pull/46), [`dedaa87`](https://github.com/Bluetooth-Devices/ulid-transform/commit/dedaa87d9e5df4ed037bb0a95c971a6e6e376439)) ### Chores - **pre-commit.ci**: Pre-commit autoupdate ([#45](https://github.com/Bluetooth-Devices/ulid-transform/pull/45), [`d4b3890`](https://github.com/Bluetooth-Devices/ulid-transform/commit/d4b3890a78a3145c84d0a0f72d80e6face310922)) updates: - [github.com/asottile/pyupgrade: v3.16.0 → v3.17.0](https://github.com/asottile/pyupgrade/compare/v3.16.0...v3.17.0) - [github.com/astral-sh/ruff-pre-commit: v0.5.4 → v0.5.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.4...v0.5.5) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ## v0.13.0 (2024-07-29) ### Chores - Ci fixes (version upgrades, repair extension build in some configs) ([#44](https://github.com/Bluetooth-Devices/ulid-transform/pull/44), [`de99c22`](https://github.com/Bluetooth-Devices/ulid-transform/commit/de99c22c1a0595fb0068d1abc3ffac157b11f3d4)) ### Features - Improve performance of C implementation ([#43](https://github.com/Bluetooth-Devices/ulid-transform/pull/43), [`f07d838`](https://github.com/Bluetooth-Devices/ulid-transform/commit/f07d838169743c4e68f344a04b6541a6a8fee239)) ## v0.12.0 (2024-07-26) ### Features - Improve testability, keep implementations in sync ([#40](https://github.com/Bluetooth-Devices/ulid-transform/pull/40), [`975079d`](https://github.com/Bluetooth-Devices/ulid-transform/commit/975079d682ffb6039454d3c041867e0d14a9f646)) * chore: ignore Cython annotation file * fix: synchronize c/py implementations Each implementation now exposes the same public methods, and their docstrings and signatures are equivalent. * chore: remove hand-decoding ULID timestamp from test code * feat: test C and Python implementations simultaneously * chore: use new-style annotations in Cython too They're exported as annotation strings anyway * chore: add tests to make sure Python and C impls are in sync ## v0.11.1 (2024-07-26) ### Bug Fixes - Cython build, add a test to verify the Cython module is available ([#41](https://github.com/Bluetooth-Devices/ulid-transform/pull/41), [`7187b35`](https://github.com/Bluetooth-Devices/ulid-transform/commit/7187b357b395b9c1716c8beaa5fc4db389fece18)) ### Chores - Add benchmarking tests ([#38](https://github.com/Bluetooth-Devices/ulid-transform/pull/38), [`456b967`](https://github.com/Bluetooth-Devices/ulid-transform/commit/456b967b457200f3001210fa527242f8a7d229b3)) - Remove IDEA configuration from repository ([#39](https://github.com/Bluetooth-Devices/ulid-transform/pull/39), [`1d95563`](https://github.com/Bluetooth-Devices/ulid-transform/commit/1d95563461026a7829b4e85a7f93efd71ed55080)) ## v0.11.0 (2024-07-25) ### Features - Add fast ULID bytes functions ([#37](https://github.com/Bluetooth-Devices/ulid-transform/pull/37), [`57b58ab`](https://github.com/Bluetooth-Devices/ulid-transform/commit/57b58ab269dcb97dbc905925861c3a25daf7e114)) ## v0.10.2 (2024-07-25) ### Bug Fixes - Fix error message in Cython `bytes_to_ulid` ([#36](https://github.com/Bluetooth-Devices/ulid-transform/pull/36), [`d8b5462`](https://github.com/Bluetooth-Devices/ulid-transform/commit/d8b54622d1445916f08b61ff3aad34e61bfbb986)) ### Chores - **pre-commit.ci**: Pre-commit autoupdate ([#33](https://github.com/Bluetooth-Devices/ulid-transform/pull/33), [`4d75f52`](https://github.com/Bluetooth-Devices/ulid-transform/commit/4d75f52e205ec7a4daf3cc1b23769867b9b65996)) updates: - [github.com/astral-sh/ruff-pre-commit: v0.5.0 → v0.5.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.0...v0.5.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#34](https://github.com/Bluetooth-Devices/ulid-transform/pull/34), [`a27b5b0`](https://github.com/Bluetooth-Devices/ulid-transform/commit/a27b5b0ae2b07f1d5990ea4c04788472e921d415)) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#35](https://github.com/Bluetooth-Devices/ulid-transform/pull/35), [`bdbb9c5`](https://github.com/Bluetooth-Devices/ulid-transform/commit/bdbb9c58f6d55209416eb7812e31669e14f39171)) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ## v0.10.1 (2024-07-05) ### Bug Fixes - Wheel builds ([#32](https://github.com/Bluetooth-Devices/ulid-transform/pull/32), [`87b511e`](https://github.com/Bluetooth-Devices/ulid-transform/commit/87b511e1b00b8fa07ef7a18a5b5012e8df943441)) ## v0.10.0 (2024-07-05) ### Chores - Add more cover and benchmarks ([#27](https://github.com/Bluetooth-Devices/ulid-transform/pull/27), [`822ea8c`](https://github.com/Bluetooth-Devices/ulid-transform/commit/822ea8c5e69b4a9652c19ae0172c16f8236634f5)) - Switch to ruff ([#31](https://github.com/Bluetooth-Devices/ulid-transform/pull/31), [`98679ea`](https://github.com/Bluetooth-Devices/ulid-transform/commit/98679ea082591ae710188798b594e57c4f05f444)) - Test on 32bit ([#26](https://github.com/Bluetooth-Devices/ulid-transform/pull/26), [`5a62d31`](https://github.com/Bluetooth-Devices/ulid-transform/commit/5a62d31fbb75fd20aa386d44a576e939f665312d)) - **pre-commit.ci**: Pre-commit autoupdate ([#28](https://github.com/Bluetooth-Devices/ulid-transform/pull/28), [`63cc7f9`](https://github.com/Bluetooth-Devices/ulid-transform/commit/63cc7f9f596df98c59a2564a8749f7b999f1407b)) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> - **pre-commit.ci**: Pre-commit autoupdate ([#29](https://github.com/Bluetooth-Devices/ulid-transform/pull/29), [`72c43be`](https://github.com/Bluetooth-Devices/ulid-transform/commit/72c43be55a4597066d33d44a92ecb33dd2a5c806)) updates: - [github.com/pre-commit/mirrors-mypy: v1.10.0 → v1.10.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.10.0...v1.10.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> ### Features - Add new fast or_none functions ([#30](https://github.com/Bluetooth-Devices/ulid-transform/pull/30), [`4d1d1ee`](https://github.com/Bluetooth-Devices/ulid-transform/commit/4d1d1ee398f791bd35a64941d3eb838cf1ef705e)) ## v0.9.0 (2023-10-18) ### Bug Fixes - Reduce size of wheels by excluding generated .cpp files ([#24](https://github.com/Bluetooth-Devices/ulid-transform/pull/24), [`29e0ff4`](https://github.com/Bluetooth-Devices/ulid-transform/commit/29e0ff474f729adea32c10a3d0adfc7801b5e892)) ### Features - Build wheel with latest cpython release ([#25](https://github.com/Bluetooth-Devices/ulid-transform/pull/25), [`8bc67fe`](https://github.com/Bluetooth-Devices/ulid-transform/commit/8bc67fe8f2c56a9aacdede63331afe6c54a1528d)) ## v0.8.1 (2023-08-27) ### Bug Fixes - Rebuild wheels with cython 3.0.2 ([#22](https://github.com/Bluetooth-Devices/ulid-transform/pull/22), [`8d78432`](https://github.com/Bluetooth-Devices/ulid-transform/commit/8d78432cf81b1a2e5230dd434b50c21365686548)) ### Chores - Bump py3.12 to rc1 ([#23](https://github.com/Bluetooth-Devices/ulid-transform/pull/23), [`e21accd`](https://github.com/Bluetooth-Devices/ulid-transform/commit/e21accd67406f20aca65e754643e282da39ada26)) ## v0.8.0 (2023-07-24) ### Features - Initial cpython 3.12 support ([#21](https://github.com/Bluetooth-Devices/ulid-transform/pull/21), [`7f7e8b9`](https://github.com/Bluetooth-Devices/ulid-transform/commit/7f7e8b90d3a7a529a58d00e66be8494a18476842)) ## v0.7.2 (2023-05-01) ### Bug Fixes - Ensure windows wheel work with older versions ([#20](https://github.com/Bluetooth-Devices/ulid-transform/pull/20), [`8a440e3`](https://github.com/Bluetooth-Devices/ulid-transform/commit/8a440e3f818b3af8a694544755d55f1c221ada3a)) ## v0.7.1 (2023-05-01) ### Bug Fixes - Missing decode for _bytes_to_ulid ([#19](https://github.com/Bluetooth-Devices/ulid-transform/pull/19), [`ddf6433`](https://github.com/Bluetooth-Devices/ulid-transform/commit/ddf6433554ca3d4fef6500b84e43adf475a794bb)) ## v0.7.0 (2023-04-23) ### Features - Add a bytes_to_ulid cpp version ([#18](https://github.com/Bluetooth-Devices/ulid-transform/pull/18), [`fa1c62c`](https://github.com/Bluetooth-Devices/ulid-transform/commit/fa1c62c97be608390a5e42de5712382ee8ec86e9)) ## v0.6.3 (2023-04-10) ### Bug Fixes - Additional 32bit time fixes ([#17](https://github.com/Bluetooth-Devices/ulid-transform/pull/17), [`2f5f1be`](https://github.com/Bluetooth-Devices/ulid-transform/commit/2f5f1be3fe63bb63b57e158c32ab5e9d7ab16b9c)) ## v0.6.2 (2023-04-09) ### Bug Fixes - Apply 32bit cast for for random data ([#16](https://github.com/Bluetooth-Devices/ulid-transform/pull/16), [`1e1d62a`](https://github.com/Bluetooth-Devices/ulid-transform/commit/1e1d62aa961178d6416e4ac82cc0634b06260ad4)) ## v0.6.1 (2023-04-09) ### Bug Fixes - Apply 32bit time fix ([#15](https://github.com/Bluetooth-Devices/ulid-transform/pull/15), [`26347f0`](https://github.com/Bluetooth-Devices/ulid-transform/commit/26347f0067be94ba36607ea9b875b5d1354e6002)) ### Chores - Add test_non_uppercase_b32_data ([#14](https://github.com/Bluetooth-Devices/ulid-transform/pull/14), [`06819d0`](https://github.com/Bluetooth-Devices/ulid-transform/commit/06819d058f2a754a39221027cafb98d0568c5b18)) ## v0.6.0 (2023-04-06) ### Features - Reflect the invalid value when raising ValueError ([#13](https://github.com/Bluetooth-Devices/ulid-transform/pull/13), [`6a022fb`](https://github.com/Bluetooth-Devices/ulid-transform/commit/6a022fb4084e3a007c469e6e2993ccb3621c4271)) ## v0.5.1 (2023-03-22) ### Bug Fixes - Specify python version to build wheels ([#12](https://github.com/Bluetooth-Devices/ulid-transform/pull/12), [`9263de0`](https://github.com/Bluetooth-Devices/ulid-transform/commit/9263de0f70a4198cfcb2ba4a8f44440a7841e407)) ## v0.5.0 (2023-03-22) ### Features - Wheels for macOS and Windows ([#11](https://github.com/Bluetooth-Devices/ulid-transform/pull/11), [`086852e`](https://github.com/Bluetooth-Devices/ulid-transform/commit/086852e57250994d0f3c9faedabf2df6aeb9e789)) Build wheels for macOS and Windows too ## v0.4.2 (2023-03-13) ### Bug Fixes - Ulid_now on 32bit ([#10](https://github.com/Bluetooth-Devices/ulid-transform/pull/10), [`c8f7dd7`](https://github.com/Bluetooth-Devices/ulid-transform/commit/c8f7dd790f987ca4310dd155a78fff263adf0cfc)) ## v0.4.1 (2023-03-13) ### Bug Fixes - Random as 0 on 32 bit arch ([#9](https://github.com/Bluetooth-Devices/ulid-transform/pull/9), [`c9fa4f3`](https://github.com/Bluetooth-Devices/ulid-transform/commit/c9fa4f32c12eabea67da14d0c32d9a5ac65f2842)) ## v0.4.0 (2023-03-01) ### Features - Add bytes_to_ulid ([#8](https://github.com/Bluetooth-Devices/ulid-transform/pull/8), [`c9a57ef`](https://github.com/Bluetooth-Devices/ulid-transform/commit/c9a57ef2d68886d37e03dd9d884a51c35a5c1e12)) ## v0.3.1 (2023-03-01) ### Bug Fixes - Encode timestamps correctly in cpp implementation ([#7](https://github.com/Bluetooth-Devices/ulid-transform/pull/7), [`c7fedc3`](https://github.com/Bluetooth-Devices/ulid-transform/commit/c7fedc350bf9848dc38af362e33819fac4036a9e)) ### Chores - Add timestamp test ([#6](https://github.com/Bluetooth-Devices/ulid-transform/pull/6), [`404487d`](https://github.com/Bluetooth-Devices/ulid-transform/commit/404487dd5879f83c6df6ad230ca6670edaacaa40)) ## v0.3.0 (2023-02-28) ### Features - Add examples ([#5](https://github.com/Bluetooth-Devices/ulid-transform/pull/5), [`be30a13`](https://github.com/Bluetooth-Devices/ulid-transform/commit/be30a133b3a03b1e6704954cf0c59f4fb64d4b7c)) ## v0.2.1 (2023-02-28) ### Bug Fixes - Drop fast-ulid for cython ([#4](https://github.com/Bluetooth-Devices/ulid-transform/pull/4), [`8d72d6b`](https://github.com/Bluetooth-Devices/ulid-transform/commit/8d72d6b58d306d722f096a5b3697d4365eae397d)) ## v0.2.0 (2023-02-28) ### Features - Enable cython builds ([#3](https://github.com/Bluetooth-Devices/ulid-transform/pull/3), [`154bf0c`](https://github.com/Bluetooth-Devices/ulid-transform/commit/154bf0c8d02591508b87c2c154ba877da2aa8f97)) ## v0.1.0 (2023-02-28) ### Bug Fixes - Remove html file ([#2](https://github.com/Bluetooth-Devices/ulid-transform/pull/2), [`c1a5cf4`](https://github.com/Bluetooth-Devices/ulid-transform/commit/c1a5cf4a4a8c5a2c8ba1663b9132d612fe47570d)) ### Chores - Initial commit ([`61fc260`](https://github.com/Bluetooth-Devices/ulid-transform/commit/61fc2606750f879ba7fb69ba3bdd371c5bcac2e8)) ### Features - Init repo ([#1](https://github.com/Bluetooth-Devices/ulid-transform/pull/1), [`0ef9511`](https://github.com/Bluetooth-Devices/ulid-transform/commit/0ef95113cd638617de16be44909b228d0df5f092)) Bluetooth-Devices-ulid-transform-f51d4f1/CONTRIBUTING.md000066400000000000000000000074741520476015000226640ustar00rootroot00000000000000# Contributing Contributions are welcome, and they are greatly appreciated! Every little helps, and credit will always be given. You can contribute in many ways: ## Types of Contributions ### Report Bugs Report bugs to [our issue page][gh-issues]. If you are reporting a bug, please include: - Your operating system name and version. - Any details about your local setup that might be helpful in troubleshooting. - Detailed steps to reproduce the bug. ### Fix Bugs Look through the GitHub issues for bugs. Anything tagged with "bug" and "help wanted" is open to whoever wants to implement it. ### Implement Features Look through the GitHub issues for features. Anything tagged with "enhancement" and "help wanted" is open to whoever wants to implement it. ### Write Documentation Fast ULID transformations could always use more documentation, whether as part of the official Fast ULID transformations docs, in docstrings, or even on the web in blog posts, articles, and such. ### Submit Feedback The best way to send feedback [our issue page][gh-issues] on GitHub. If you are proposing a feature: - Explain in detail how it would work. - Keep the scope as narrow as possible, to make it easier to implement. - Remember that this is a volunteer-driven project, and that contributions are welcome 😊 ## Get Started! Ready to contribute? Here's how to set yourself up for local development. 1. Fork the repo on GitHub. 2. Clone your fork locally: ```shell $ git clone git@github.com:your_name_here/ulid-transform.git ``` 3. Install the project dependencies with [Poetry](https://python-poetry.org): ```shell $ poetry install ``` 4. Create a branch for local development: ```shell $ git checkout -b name-of-your-bugfix-or-feature ``` Now you can make your changes locally. 5. When you're done making changes, check that your changes pass our tests: ```shell $ poetry run pytest ``` 6. Linting is done through [pre-commit](https://pre-commit.com). Provided you have the tool installed globally, you can run them all as one-off: ```shell $ pre-commit run -a ``` Or better, install the hooks once and have them run automatically each time you commit: ```shell $ pre-commit install ``` 7. Commit your changes and push your branch to GitHub: ```shell $ git add . $ git commit -m "feat(something): your detailed description of your changes" $ git push origin name-of-your-bugfix-or-feature ``` Note: the commit message should follow [the conventional commits](https://www.conventionalcommits.org). We run [`commitlint` on CI](https://github.com/marketplace/actions/commit-linter) to validate it, and if you've installed pre-commit hooks at the previous step, the message will be checked at commit time. 8. Submit a pull request through the GitHub website or using the GitHub CLI (if you have it installed): ```shell $ gh pr create --fill ``` ## Pull Request Guidelines We like to have the pull request open as soon as possible, that's a great place to discuss any piece of work, even unfinished. You can use draft pull request if it's still a work in progress. Here are a few guidelines to follow: 1. Include tests for feature or bug fixes. 2. Update the documentation for significant features. 3. Ensure tests are passing on CI. ## Tips To run a subset of tests: ```shell $ pytest tests ``` ## Making a new release The deployment should be automated and can be triggered from the Semantic Release workflow in GitHub. The next version will be based on [the commit logs](https://python-semantic-release.readthedocs.io/en/latest/commit-log-parsing.html#commit-log-parsing). This is done by [python-semantic-release](https://python-semantic-release.readthedocs.io/en/latest/index.html) via a GitHub action. [gh-issues]: https://github.com/bluetooth-devices/ulid-transform/issues Bluetooth-Devices-ulid-transform-f51d4f1/LICENSE000066400000000000000000000020601520476015000214220ustar00rootroot00000000000000 MIT License Copyright (c) 2022 J. Nick Koston Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Bluetooth-Devices-ulid-transform-f51d4f1/README.md000066400000000000000000000116471520476015000217070ustar00rootroot00000000000000# Fast ULID transformations

CI Status Test coverage percentage

Poetry black pre-commit CodSpeed Badge

PyPI Version Supported Python versions License

Create and transform ULIDs This library will use the C++ implementation from https://github.com/suyash/ulid when the C extension is available, and will fallback to pure python if it is not. ## Example ```python >>> import ulid_transform >>> ulid_transform.ulid_hex() '01869a2ea5fb0b43aa056293e47c0a35' >>> ulid_transform.ulid_now() '0001HZX0NW00GW0X476W5TVBFE' >>> ulid_transform.ulid_now_bytes() b'\x01\x9eS\x9d\x1bhl~\x1e\xf7\x959\xe1\xf2\xbe\xea' >>> ulid_transform.ulid_at_time(1234) '000000016JC62D620DGYNG2R8H' >>> ulid_transform.ulid_at_time_bytes(1234) b'\x00\x00\x00\x12\xd4Pq+\x1eG?\x91\xe9+|\xbd' >>> ulid_transform.ulid_to_bytes('0001HZX0NW00GW0X476W5TVBFE') b'\x00\x00c\xfe\x82\xbc\x00!\xc0t\x877\x0b\xad\xad\xee' >>> ulid_transform.bytes_to_ulid(b"\x01\x86\x99?\xe8\xf3\x11\xbc\xed\xef\x86U.9\x03z") '01GTCKZT7K26YEVVW6AMQ3J0VT' >>> ulid_transform.ulid_to_timestamp('0001HZX0NW00GW0X476W5TVBFE') # milliseconds since the epoch 1677623996 >>> ulid_transform.ulid_to_timestamp(b'\x00\x00c\xfe\x82\xbc\x00!\xc0t\x877\x0b\xad\xad\xee') # also accepts bytes 1677623996 >>> ulid_transform.ulid_to_bytes_or_none('0001HZX0NW00GW0X476W5TVBFE') b'\x00\x00c\xfe\x82\xbc\x00!\xc0t\x877\x0b\xad\xad\xee' >>> ulid_transform.ulid_to_bytes_or_none(None) >>> ulid_transform.bytes_to_ulid_or_none(b'\x00\x00c\xfe\x82\xbc\x00!\xc0t\x877\x0b\xad\xad\xee') '0001HZX0NW00GW0X476W5TVBFE' >>> ulid_transform.bytes_to_ulid_or_none(None) ``` ## Installation Install this via pip (or your favourite package manager): `pip install ulid-transform` ## Contributors ✨ Thanks to https://github.com/suyash/ulid which provides the C++ implementation guts. Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! ## Credits This package was created with [Copier](https://copier.readthedocs.io/) and the [browniebroke/pypackage-template](https://github.com/browniebroke/pypackage-template) project template. Bluetooth-Devices-ulid-transform-f51d4f1/bench/000077500000000000000000000000001520476015000214765ustar00rootroot00000000000000Bluetooth-Devices-ulid-transform-f51d4f1/bench/__init__.py000066400000000000000000000000001520476015000235750ustar00rootroot00000000000000Bluetooth-Devices-ulid-transform-f51d4f1/bench/conftest.py000066400000000000000000000000611520476015000236720ustar00rootroot00000000000000from tests.utils import impl __all__ = ["impl"] Bluetooth-Devices-ulid-transform-f51d4f1/bench/test_timestamp.py000066400000000000000000000014111520476015000251070ustar00rootroot00000000000000import pytest import tests.conftest # noqa: F401 example = "01J3YPYJJW00GW0X476W5TVBFE" example_timestamp = 1722238847580 @pytest.mark.benchmark(group="timestamp") def test_ut_timestamp(benchmark, impl): assert benchmark(lambda: impl.ulid_to_timestamp(example)) == example_timestamp @pytest.mark.benchmark(group="timestamp") def test_ulid2_timestamp(benchmark): ulid2 = pytest.importorskip("ulid2") assert ( benchmark(lambda: ulid2.get_ulid_timestamp(example)) == example_timestamp / 1000.0 ) @pytest.mark.benchmark(group="timestamp") def test_ulidpy_uuid(benchmark): ulid = pytest.importorskip("ulid") assert ( benchmark(lambda: ulid.from_str(example).timestamp().timestamp) == example_timestamp / 1000.0 ) Bluetooth-Devices-ulid-transform-f51d4f1/bench/test_ulid_as_uuid.py000066400000000000000000000014421520476015000255560ustar00rootroot00000000000000from uuid import UUID import pytest import tests.conftest # noqa: F401 @pytest.mark.benchmark(group="ulid_as_uuid") def test_ut_ulid_now_bytes(benchmark, impl): assert isinstance( benchmark(lambda: UUID(bytes=impl.ulid_now_bytes())), UUID, ) @pytest.mark.benchmark(group="ulid_as_uuid") def test_ut_ulid_hex(benchmark, impl): assert isinstance(benchmark(lambda: UUID(hex=impl.ulid_hex())), UUID) @pytest.mark.benchmark(group="ulid_as_uuid") def test_ulid2_uuid(benchmark): ulid2 = pytest.importorskip("ulid2") assert isinstance(benchmark(ulid2.generate_ulid_as_uuid), UUID) @pytest.mark.benchmark(group="ulid_as_uuid") def test_ulidpy_uuid(benchmark): ulid = pytest.importorskip("ulid") assert isinstance(benchmark(lambda: ulid.new().uuid), UUID) Bluetooth-Devices-ulid-transform-f51d4f1/bench/test_ulid_parsing.py000066400000000000000000000012201520476015000255620ustar00rootroot00000000000000import pytest import tests.conftest # noqa: F401 example = "01J3MS6XG9XC7X9FR15J9A82ZP" @pytest.mark.benchmark(group="ulid_parsing") def test_ut_decode(benchmark, impl): assert isinstance(benchmark(lambda: impl.ulid_to_bytes(example)), bytes) @pytest.mark.benchmark(group="ulid_parsing") def test_ulid2_decode(benchmark): ulid2 = pytest.importorskip("ulid2") assert isinstance(benchmark(lambda: ulid2.decode_ulid_base32(example)), bytes) @pytest.mark.benchmark(group="ulid_parsing") def test_ulidpy_decode(benchmark): ulid = pytest.importorskip("ulid") assert isinstance(benchmark(lambda: ulid.from_str(example).bytes), bytes) Bluetooth-Devices-ulid-transform-f51d4f1/bench/test_ulid_strings.py000066400000000000000000000022211520476015000256120ustar00rootroot00000000000000import pytest import tests.conftest # noqa: F401 @pytest.mark.benchmark(group="ulid_strings") def test_ut_ulid_now(benchmark, impl): assert isinstance(benchmark(impl.ulid_now), str) @pytest.mark.benchmark(group="ulid_strings") def test_ut_ulid_at_time(benchmark, impl): assert isinstance(benchmark(lambda: impl.ulid_at_time(1)), str) @pytest.mark.benchmark(group="ulid_strings") def test_ulid2_ulid_now(benchmark): ulid2 = pytest.importorskip("ulid2") assert isinstance(benchmark(ulid2.generate_ulid_as_base32), str) @pytest.mark.benchmark(group="ulid_strings") def test_ulid2_ulid_at_time(benchmark): ulid2 = pytest.importorskip("ulid2") assert isinstance( benchmark(lambda: ulid2.generate_ulid_as_base32(timestamp=1)), str ) @pytest.mark.benchmark(group="ulid_strings") def test_ulidpy_uuid_now(benchmark): ulid = pytest.importorskip("ulid") assert isinstance(benchmark(lambda: ulid.new().str), str) @pytest.mark.benchmark(group="ulid_strings") def test_ulidpy_uuid_at_time(benchmark): ulid = pytest.importorskip("ulid") assert isinstance(benchmark(lambda: ulid.from_timestamp(timestamp=1).str), str) Bluetooth-Devices-ulid-transform-f51d4f1/build_ext.py000066400000000000000000000071141520476015000227530ustar00rootroot00000000000000"""Build optional C extension modules.""" from distutils.command.build_ext import build_ext import logging import os from pathlib import Path import sys from typing import Any try: from setuptools import Extension, setup except ImportError: from distutils.core import Extension, setup _LOGGER = logging.getLogger(__name__) def getenv_bool(key: str, default: bool = False) -> bool: value = os.environ.get(key, str(default)).lower() if value in ("1", "true", "yes"): return True if value in ("0", "false", "no"): return False msg = f"Invalid value for boolean envvar {key}: {value}" raise ValueError(msg) ulid_module = Extension( "ulid_transform._ulid_impl", [ str(Path("src") / "ulid_transform" / "_ulid_impl.cpp"), ], language="c++", extra_compile_args=["-std=c++11", "-O3", "-g0"], extra_link_args=["-std=c++11"], ) class BuildExt(build_ext): def build_extensions(self) -> None: if self.parallel is None: # type: ignore[has-type, unused-ignore] self.parallel = os.cpu_count() or 1 try: super().build_extensions() except Exception: # nosec _LOGGER.exception("Failed to build extensions") if getenv_bool("REQUIRE_CYTHON") or getenv_bool("REQUIRE_EXTENSION"): raise def build(setup_kwargs: Any) -> None: if getenv_bool("SKIP_CYTHON") or getenv_bool("SKIP_EXTENSION"): return try: setup_kwargs.update( { "ext_modules": [ulid_module], "cmdclass": {"build_ext": BuildExt}, } ) except Exception: _LOGGER.exception("Failed to configure C extension") if getenv_bool("REQUIRE_CYTHON") or getenv_bool("REQUIRE_EXTENSION"): raise def _clean_stale_artifacts() -> None: """Remove previously-built extension artifacts before packaging. The wheel ``include`` globs in ``pyproject.toml`` are unconditional, so a stale ``.so``/``.pyd`` left in ``src/ulid_transform/`` by an earlier build would otherwise be packaged even when the extension is skipped (``SKIP_EXTENSION``) or its build failed and was swallowed, yielding an incompatible binary wheel instead of the requested pure-Python fallback. Clearing them first means the include globs only ever match a freshly built artifact from this run. """ pkg_dir = Path("src") / "ulid_transform" for pattern in ("*.so", "*.pyd"): for artifact in pkg_dir.glob(pattern): artifact.unlink() def _run_main() -> None: """Build the C extension when invoked directly. poetry-core calls ``python build_ext.py`` (no args) during the wheel build when ``generate-setup-file = false`` is set in ``pyproject.toml``. We synthesise a ``setuptools.setup()`` call with an in-place ``build_ext`` so the compiled extension lands next to its sources in ``src/ulid_transform/`` where poetry-core's ``find_files_to_add`` picks it up. ``generate-setup-file = false`` is set so the sdist does not ship a generated ``setup.py`` that does ``from build_ext import *``, which fails under ``PYTHONSAFEPATH=1`` (see issue #137). """ _clean_stale_artifacts() setup_kwargs: dict[str, Any] = { "name": "ulid-transform", "packages": ["ulid_transform"], "package_dir": {"": "src"}, } build(setup_kwargs) if "ext_modules" not in setup_kwargs: return if len(sys.argv) == 1: sys.argv.extend(["build_ext", "--inplace"]) setup(**setup_kwargs) if __name__ == "__main__": _run_main() Bluetooth-Devices-ulid-transform-f51d4f1/commitlint.config.mjs000066400000000000000000000003621520476015000245560ustar00rootroot00000000000000export default { extends: ["@commitlint/config-conventional"], rules: { "header-max-length": [0, "always", Infinity], "body-max-line-length": [0, "always", Infinity], "footer-max-line-length": [0, "always", Infinity], }, }; Bluetooth-Devices-ulid-transform-f51d4f1/poetry.lock000066400000000000000000000722541520476015000226250ustar00rootroot00000000000000# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["benchmark", "dev"] markers = "sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] [[package]] name = "coverage" version = "7.10.6" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "coverage-7.10.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:70e7bfbd57126b5554aa482691145f798d7df77489a177a6bef80de78860a356"}, {file = "coverage-7.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e41be6f0f19da64af13403e52f2dec38bbc2937af54df8ecef10850ff8d35301"}, {file = "coverage-7.10.6-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c61fc91ab80b23f5fddbee342d19662f3d3328173229caded831aa0bd7595460"}, {file = "coverage-7.10.6-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10356fdd33a7cc06e8051413140bbdc6f972137508a3572e3f59f805cd2832fd"}, {file = "coverage-7.10.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80b1695cf7c5ebe7b44bf2521221b9bb8cdf69b1f24231149a7e3eb1ae5fa2fb"}, {file = "coverage-7.10.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2e4c33e6378b9d52d3454bd08847a8651f4ed23ddbb4a0520227bd346382bbc6"}, {file = "coverage-7.10.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c8a3ec16e34ef980a46f60dc6ad86ec60f763c3f2fa0db6d261e6e754f72e945"}, {file = "coverage-7.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7d79dabc0a56f5af990cc6da9ad1e40766e82773c075f09cc571e2076fef882e"}, {file = "coverage-7.10.6-cp310-cp310-win32.whl", hash = "sha256:86b9b59f2b16e981906e9d6383eb6446d5b46c278460ae2c36487667717eccf1"}, {file = "coverage-7.10.6-cp310-cp310-win_amd64.whl", hash = "sha256:e132b9152749bd33534e5bd8565c7576f135f157b4029b975e15ee184325f528"}, {file = "coverage-7.10.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c706db3cabb7ceef779de68270150665e710b46d56372455cd741184f3868d8f"}, {file = "coverage-7.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e0c38dc289e0508ef68ec95834cb5d2e96fdbe792eaccaa1bccac3966bbadcc"}, {file = "coverage-7.10.6-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:752a3005a1ded28f2f3a6e8787e24f28d6abe176ca64677bcd8d53d6fe2ec08a"}, {file = "coverage-7.10.6-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:689920ecfd60f992cafca4f5477d55720466ad2c7fa29bb56ac8d44a1ac2b47a"}, {file = "coverage-7.10.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec98435796d2624d6905820a42f82149ee9fc4f2d45c2c5bc5a44481cc50db62"}, {file = "coverage-7.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b37201ce4a458c7a758ecc4efa92fa8ed783c66e0fa3c42ae19fc454a0792153"}, {file = "coverage-7.10.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2904271c80898663c810a6b067920a61dd8d38341244a3605bd31ab55250dad5"}, {file = "coverage-7.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5aea98383463d6e1fa4e95416d8de66f2d0cb588774ee20ae1b28df826bcb619"}, {file = "coverage-7.10.6-cp311-cp311-win32.whl", hash = "sha256:e3fb1fa01d3598002777dd259c0c2e6d9d5e10e7222976fc8e03992f972a2cba"}, {file = "coverage-7.10.6-cp311-cp311-win_amd64.whl", hash = "sha256:f35ed9d945bece26553d5b4c8630453169672bea0050a564456eb88bdffd927e"}, {file = "coverage-7.10.6-cp311-cp311-win_arm64.whl", hash = "sha256:99e1a305c7765631d74b98bf7dbf54eeea931f975e80f115437d23848ee8c27c"}, {file = "coverage-7.10.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b2dd6059938063a2c9fee1af729d4f2af28fd1a545e9b7652861f0d752ebcea"}, {file = "coverage-7.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:388d80e56191bf846c485c14ae2bc8898aa3124d9d35903fef7d907780477634"}, {file = "coverage-7.10.6-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:90cb5b1a4670662719591aa92d0095bb41714970c0b065b02a2610172dbf0af6"}, {file = "coverage-7.10.6-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:961834e2f2b863a0e14260a9a273aff07ff7818ab6e66d2addf5628590c628f9"}, {file = "coverage-7.10.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf9a19f5012dab774628491659646335b1928cfc931bf8d97b0d5918dd58033c"}, {file = "coverage-7.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99c4283e2a0e147b9c9cc6bc9c96124de9419d6044837e9799763a0e29a7321a"}, {file = "coverage-7.10.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:282b1b20f45df57cc508c1e033403f02283adfb67d4c9c35a90281d81e5c52c5"}, {file = "coverage-7.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cdbe264f11afd69841bd8c0d83ca10b5b32853263ee62e6ac6a0ab63895f972"}, {file = "coverage-7.10.6-cp312-cp312-win32.whl", hash = "sha256:a517feaf3a0a3eca1ee985d8373135cfdedfbba3882a5eab4362bda7c7cf518d"}, {file = "coverage-7.10.6-cp312-cp312-win_amd64.whl", hash = "sha256:856986eadf41f52b214176d894a7de05331117f6035a28ac0016c0f63d887629"}, {file = "coverage-7.10.6-cp312-cp312-win_arm64.whl", hash = "sha256:acf36b8268785aad739443fa2780c16260ee3fa09d12b3a70f772ef100939d80"}, {file = "coverage-7.10.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ffea0575345e9ee0144dfe5701aa17f3ba546f8c3bb48db62ae101afb740e7d6"}, {file = "coverage-7.10.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:95d91d7317cde40a1c249d6b7382750b7e6d86fad9d8eaf4fa3f8f44cf171e80"}, {file = "coverage-7.10.6-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e23dd5408fe71a356b41baa82892772a4cefcf758f2ca3383d2aa39e1b7a003"}, {file = "coverage-7.10.6-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0f3f56e4cb573755e96a16501a98bf211f100463d70275759e73f3cbc00d4f27"}, {file = "coverage-7.10.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db4a1d897bbbe7339946ffa2fe60c10cc81c43fab8b062d3fcb84188688174a4"}, {file = "coverage-7.10.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8fd7879082953c156d5b13c74aa6cca37f6a6f4747b39538504c3f9c63d043d"}, {file = "coverage-7.10.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:28395ca3f71cd103b8c116333fa9db867f3a3e1ad6a084aa3725ae002b6583bc"}, {file = "coverage-7.10.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:61c950fc33d29c91b9e18540e1aed7d9f6787cc870a3e4032493bbbe641d12fc"}, {file = "coverage-7.10.6-cp313-cp313-win32.whl", hash = "sha256:160c00a5e6b6bdf4e5984b0ef21fc860bc94416c41b7df4d63f536d17c38902e"}, {file = "coverage-7.10.6-cp313-cp313-win_amd64.whl", hash = "sha256:628055297f3e2aa181464c3808402887643405573eb3d9de060d81531fa79d32"}, {file = "coverage-7.10.6-cp313-cp313-win_arm64.whl", hash = "sha256:df4ec1f8540b0bcbe26ca7dd0f541847cc8a108b35596f9f91f59f0c060bfdd2"}, {file = "coverage-7.10.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c9a8b7a34a4de3ed987f636f71881cd3b8339f61118b1aa311fbda12741bff0b"}, {file = "coverage-7.10.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dd5af36092430c2b075cee966719898f2ae87b636cefb85a653f1d0ba5d5393"}, {file = "coverage-7.10.6-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0353b0f0850d49ada66fdd7d0c7cdb0f86b900bb9e367024fd14a60cecc1e27"}, {file = "coverage-7.10.6-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d6b9ae13d5d3e8aeca9ca94198aa7b3ebbc5acfada557d724f2a1f03d2c0b0df"}, {file = "coverage-7.10.6-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:675824a363cc05781b1527b39dc2587b8984965834a748177ee3c37b64ffeafb"}, {file = "coverage-7.10.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:692d70ea725f471a547c305f0d0fc6a73480c62fb0da726370c088ab21aed282"}, {file = "coverage-7.10.6-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:851430a9a361c7a8484a36126d1d0ff8d529d97385eacc8dfdc9bfc8c2d2cbe4"}, {file = "coverage-7.10.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d9369a23186d189b2fc95cc08b8160ba242057e887d766864f7adf3c46b2df21"}, {file = "coverage-7.10.6-cp313-cp313t-win32.whl", hash = "sha256:92be86fcb125e9bda0da7806afd29a3fd33fdf58fba5d60318399adf40bf37d0"}, {file = "coverage-7.10.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6b3039e2ca459a70c79523d39347d83b73f2f06af5624905eba7ec34d64d80b5"}, {file = "coverage-7.10.6-cp313-cp313t-win_arm64.whl", hash = "sha256:3fb99d0786fe17b228eab663d16bee2288e8724d26a199c29325aac4b0319b9b"}, {file = "coverage-7.10.6-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6008a021907be8c4c02f37cdc3ffb258493bdebfeaf9a839f9e71dfdc47b018e"}, {file = "coverage-7.10.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5e75e37f23eb144e78940b40395b42f2321951206a4f50e23cfd6e8a198d3ceb"}, {file = "coverage-7.10.6-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0f7cb359a448e043c576f0da00aa8bfd796a01b06aa610ca453d4dde09cc1034"}, {file = "coverage-7.10.6-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c68018e4fc4e14b5668f1353b41ccf4bc83ba355f0e1b3836861c6f042d89ac1"}, {file = "coverage-7.10.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cd4b2b0707fc55afa160cd5fc33b27ccbf75ca11d81f4ec9863d5793fc6df56a"}, {file = "coverage-7.10.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cec13817a651f8804a86e4f79d815b3b28472c910e099e4d5a0e8a3b6a1d4cb"}, {file = "coverage-7.10.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f2a6a8e06bbda06f78739f40bfb56c45d14eb8249d0f0ea6d4b3d48e1f7c695d"}, {file = "coverage-7.10.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:081b98395ced0d9bcf60ada7661a0b75f36b78b9d7e39ea0790bb4ed8da14747"}, {file = "coverage-7.10.6-cp314-cp314-win32.whl", hash = "sha256:6937347c5d7d069ee776b2bf4e1212f912a9f1f141a429c475e6089462fcecc5"}, {file = "coverage-7.10.6-cp314-cp314-win_amd64.whl", hash = "sha256:adec1d980fa07e60b6ef865f9e5410ba760e4e1d26f60f7e5772c73b9a5b0713"}, {file = "coverage-7.10.6-cp314-cp314-win_arm64.whl", hash = "sha256:a80f7aef9535442bdcf562e5a0d5a5538ce8abe6bb209cfbf170c462ac2c2a32"}, {file = "coverage-7.10.6-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0de434f4fbbe5af4fa7989521c655c8c779afb61c53ab561b64dcee6149e4c65"}, {file = "coverage-7.10.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6e31b8155150c57e5ac43ccd289d079eb3f825187d7c66e755a055d2c85794c6"}, {file = "coverage-7.10.6-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:98cede73eb83c31e2118ae8d379c12e3e42736903a8afcca92a7218e1f2903b0"}, {file = "coverage-7.10.6-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f863c08f4ff6b64fa8045b1e3da480f5374779ef187f07b82e0538c68cb4ff8e"}, {file = "coverage-7.10.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b38261034fda87be356f2c3f42221fdb4171c3ce7658066ae449241485390d5"}, {file = "coverage-7.10.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e93b1476b79eae849dc3872faeb0bf7948fd9ea34869590bc16a2a00b9c82a7"}, {file = "coverage-7.10.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ff8a991f70f4c0cf53088abf1e3886edcc87d53004c7bb94e78650b4d3dac3b5"}, {file = "coverage-7.10.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ac765b026c9f33044419cbba1da913cfb82cca1b60598ac1c7a5ed6aac4621a0"}, {file = "coverage-7.10.6-cp314-cp314t-win32.whl", hash = "sha256:441c357d55f4936875636ef2cfb3bee36e466dcf50df9afbd398ce79dba1ebb7"}, {file = "coverage-7.10.6-cp314-cp314t-win_amd64.whl", hash = "sha256:073711de3181b2e204e4870ac83a7c4853115b42e9cd4d145f2231e12d670930"}, {file = "coverage-7.10.6-cp314-cp314t-win_arm64.whl", hash = "sha256:137921f2bac5559334ba66122b753db6dc5d1cf01eb7b64eb412bb0d064ef35b"}, {file = "coverage-7.10.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90558c35af64971d65fbd935c32010f9a2f52776103a259f1dee865fe8259352"}, {file = "coverage-7.10.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8953746d371e5695405806c46d705a3cd170b9cc2b9f93953ad838f6c1e58612"}, {file = "coverage-7.10.6-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c83f6afb480eae0313114297d29d7c295670a41c11b274e6bca0c64540c1ce7b"}, {file = "coverage-7.10.6-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7eb68d356ba0cc158ca535ce1381dbf2037fa8cb5b1ae5ddfc302e7317d04144"}, {file = "coverage-7.10.6-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5b15a87265e96307482746d86995f4bff282f14b027db75469c446da6127433b"}, {file = "coverage-7.10.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fc53ba868875bfbb66ee447d64d6413c2db91fddcfca57025a0e7ab5b07d5862"}, {file = "coverage-7.10.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:efeda443000aa23f276f4df973cb82beca682fd800bb119d19e80504ffe53ec2"}, {file = "coverage-7.10.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9702b59d582ff1e184945d8b501ffdd08d2cee38d93a2206aa5f1365ce0b8d78"}, {file = "coverage-7.10.6-cp39-cp39-win32.whl", hash = "sha256:2195f8e16ba1a44651ca684db2ea2b2d4b5345da12f07d9c22a395202a05b23c"}, {file = "coverage-7.10.6-cp39-cp39-win_amd64.whl", hash = "sha256:f32ff80e7ef6a5b5b606ea69a36e97b219cd9dc799bcf2963018a4d8f788cfbf"}, {file = "coverage-7.10.6-py3-none-any.whl", hash = "sha256:92c4ecf6bf11b2e85fd4d8204814dc26e6a19f0c9d938c207c5cb0eadfcabbe3"}, {file = "coverage-7.10.6.tar.gz", hash = "sha256:f644a3ae5933a552a29dbb9aa2f90c677a875f80ebea028e5a52a4f429044b90"}, ] [package.extras] toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" groups = ["benchmark", "dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] [[package]] name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, ] [package.dependencies] mdurl = ">=0.1,<1.0" [package.extras] benchmarking = ["psutil", "pytest", "pytest-benchmark"] code-style = ["pre-commit (>=3.0,<4.0)"] compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] linkify = ["linkify-it-py (>=1,<3)"] plugins = ["mdit-py-plugins"] profiling = ["gprof2dot"] rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" groups = ["dev"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] [[package]] name = "packaging" version = "24.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["benchmark", "dev"] files = [ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] name = "pluggy" version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" groups = ["benchmark", "dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] [[package]] name = "py-cpuinfo" version = "9.0.0" description = "Get CPU info with pure Python" optional = false python-versions = "*" groups = ["benchmark"] files = [ {file = "py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690"}, {file = "py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5"}, ] [[package]] name = "pygments" version = "2.20.0" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.9" groups = ["benchmark", "dev"] files = [ {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, ] [package.extras] windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pytest" version = "9.0.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.10" groups = ["benchmark", "dev"] files = [ {file = "pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9"}, {file = "pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c"}, ] [package.dependencies] colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} iniconfig = ">=1.0.1" packaging = ">=22" pluggy = ">=1.5,<2" pygments = ">=2.7.2" [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-benchmark" version = "5.2.3" description = "A ``pytest`` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer." optional = false python-versions = ">=3.9" groups = ["benchmark"] files = [ {file = "pytest_benchmark-5.2.3-py3-none-any.whl", hash = "sha256:bc839726ad20e99aaa0d11a127445457b4219bdb9e80a1afc4b51da7f96b0803"}, {file = "pytest_benchmark-5.2.3.tar.gz", hash = "sha256:deb7317998a23c650fd4ff76e1230066a76cb45dcece0aca5607143c619e7779"}, ] [package.dependencies] py-cpuinfo = "*" pytest = ">=8.1" [package.extras] aspect = ["aspectlib"] elasticsearch = ["elasticsearch"] histogram = ["pygal", "pygaljs", "setuptools"] [[package]] name = "pytest-codspeed" version = "5.0.2" description = "Pytest plugin to create CodSpeed benchmarks" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "pytest_codspeed-5.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fbd1e86900e7ebbbf3cdf5a48124412d2b75283ab1378994ac27ba3308e262fc"}, {file = "pytest_codspeed-5.0.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d394d0d27ead72d0b00906e3832f4dcb9aadb81887a4f379c534c32c0ab965b7"}, {file = "pytest_codspeed-5.0.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ee33ac4c3bd7317b6956c0b6cb250f759e02072bb14fd0324de0df71d5d488f"}, {file = "pytest_codspeed-5.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799ca9e54d6958d1b388371d00f928fcc4e1e68427d312348dd413a1bba5e0b"}, {file = "pytest_codspeed-5.0.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b42d2aae3ac94192b8843fa7578eae584223bcb6334c50ca9f0e9ebafd40053b"}, {file = "pytest_codspeed-5.0.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:793d423dc76fd52b67495318681be18c541a7cfe30432ab2f272cd393422c56b"}, {file = "pytest_codspeed-5.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c20756925af58ad9d5b584d66a9b8dc709f9b243e6d8fd377e2a1b5a99bf9229"}, {file = "pytest_codspeed-5.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6d24532a8fee7018b9a33df51e1a14e27ae6b2b0772e6ad477ce5c561ab06a5"}, {file = "pytest_codspeed-5.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2c09ec82a2def144816c6ffb311252c6ff0624189b3b5e674d889920b6d926c"}, {file = "pytest_codspeed-5.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d223b0fe74625e633c86934a1da3ed1607f694fb3981a598bcfc02811e54808e"}, {file = "pytest_codspeed-5.0.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82d3c9db57ccaef5177e1096b4dbbf8f3fde8d25c568e38d31a259474c94e5b4"}, {file = "pytest_codspeed-5.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0621a458c52e77aa113c8d6e14037b90ce3cb5a8dd10a7656b71641999baef8c"}, {file = "pytest_codspeed-5.0.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1b87b6a5e3c0e05ea043790aae08791dd6b3e7f487b18ec1bce145a60c78a130"}, {file = "pytest_codspeed-5.0.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22332fefae895fc80a36ac8a6d5b314663efcad9e833aed8452388441b95c50f"}, {file = "pytest_codspeed-5.0.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dedc9e4542832a3487aedf0b448217f34fdc794676b9e0daeaf408a343322c2b"}, {file = "pytest_codspeed-5.0.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0fd7db3e6fb6bd28abbf0059dd54ee6233f5faf5c08597b1e9624821417e8d99"}, {file = "pytest_codspeed-5.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a5ce30d2bcfbeb329b61f3435369720ed122caa1dd898464acbcd7edc63cf04"}, {file = "pytest_codspeed-5.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:687e5aa0fd101adbfe98f36dc253cd4e3b77d90ad96260e6e7e78bde4319c357"}, {file = "pytest_codspeed-5.0.2-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:a14a6515cd315745b4b5b4739a72b287782c00a35f2927e55c310499b79d6bc2"}, {file = "pytest_codspeed-5.0.2-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3658d3b42a15c6f40fa385629a8a8655dbedadd5d7bb5a01bc342b47f73da252"}, {file = "pytest_codspeed-5.0.2-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3d15eaa6ca380d0d7cb5b7b8692f362a8aac3832dff6867a0c7068fb8c7a4ef1"}, {file = "pytest_codspeed-5.0.2-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:53473907ee2a7569b5ce6ffbfd2ba1793d284a37ff5c8670ed3149133c3ed37b"}, {file = "pytest_codspeed-5.0.2-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b033d25f40c47733234f29c10629f14d004540c743a5c30718e2aa768d7cbb3"}, {file = "pytest_codspeed-5.0.2-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:33245c1fd96b1a4299604f6791e7fded376605c140ad778db7032dcd46a74d1c"}, {file = "pytest_codspeed-5.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:40b12cbf88eb69583d7063a4f5c986a7eed14f750a49764ef39a565ffa33d540"}, {file = "pytest_codspeed-5.0.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:439cd9d87ad449b7db327724b8fdc4a1ae79090b166b77c4e5e15102a371f6c7"}, {file = "pytest_codspeed-5.0.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd07e12c38f6974c969e76d070aba448c92fad66601cda4fd289afa52c81ef13"}, {file = "pytest_codspeed-5.0.2-py3-none-any.whl", hash = "sha256:a88fcddd08bdb1afe043ac4f992e032baee92c88990a611111e0c00d77927cfe"}, {file = "pytest_codspeed-5.0.2.tar.gz", hash = "sha256:93fea30b2d7266343dd505a182bdf1eb47f96f5fa2929f1d9aff01d3b60e1589"}, ] [package.dependencies] pytest = ">=3.8" rich = ">=13.8.1" [package.extras] compat = ["pytest-benchmark (>=5.0.0,<5.1.0)", "pytest-xdist (>=3.6.1,<3.7.0)"] [[package]] name = "pytest-cov" version = "7.1.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678"}, {file = "pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2"}, ] [package.dependencies] coverage = {version = ">=7.10.6", extras = ["toml"]} pluggy = ">=1.2" pytest = ">=7" [package.extras] testing = ["process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "rich" version = "13.9.4" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" groups = ["dev"] files = [ {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, ] [package.dependencies] markdown-it-py = ">=2.2.0" pygments = ">=2.13.0,<3.0.0" [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "setuptools" version = "82.0.1" description = "Most extensible Python build backend with support for C/C++ extension modules" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb"}, {file = "setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9"}, ] [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.13.0) ; sys_platform != \"cygwin\""] core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.18.*)", "pytest-mypy"] [[package]] name = "ulid-py" version = "1.1.0" description = "Universally Unique Lexicographically Sortable Identifier" optional = false python-versions = "*" groups = ["benchmark"] files = [ {file = "ulid-py-1.1.0.tar.gz", hash = "sha256:dc6884be91558df077c3011b9fb0c87d1097cb8fc6534b11f310161afd5738f0"}, {file = "ulid_py-1.1.0-py2.py3-none-any.whl", hash = "sha256:b56a0f809ef90d6020b21b89a87a48edc7c03aea80e5ed5174172e82d76e3987"}, ] [[package]] name = "ulid2" version = "0.3.0" description = "ULID encoding/decoding for Python" optional = false python-versions = "*" groups = ["benchmark"] files = [ {file = "ulid2-0.3.0-py2.py3-none-any.whl", hash = "sha256:eaf0d39ad3cedb644247b69161cc87e32009aebb1420eccfd037b6a55a20329c"}, {file = "ulid2-0.3.0.tar.gz", hash = "sha256:915b18c75b735537d7d39cb1ba8fa8c05073ddef3ed2fe7d2e35426a63c0cf70"}, ] [metadata] lock-version = "2.1" python-versions = "^3.11" content-hash = "6c7869042f71575daf835a3079065d434762df3ca59e17202abcf80ba1f47892" Bluetooth-Devices-ulid-transform-f51d4f1/pyproject.toml000066400000000000000000000112651520476015000233400ustar00rootroot00000000000000[project] name = "ulid-transform" version = "2.2.9" license = "MIT" description = "Create and transform ULIDs" readme = "README.md" authors = [{ name = "J. Nick Koston", email = "nick@koston.org" }] requires-python = ">=3.11" [project.urls] "Repository" = "https://github.com/bluetooth-devices/ulid-transform" "Bug Tracker" = "https://github.com/bluetooth-devices/ulid-transform/issues" "Changelog" = "https://github.com/bluetooth-devices/ulid-transform/blob/main/CHANGELOG.md" [tool.poetry] classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Natural Language :: English", "Operating System :: OS Independent", "Topic :: Software Development :: Libraries", ] packages = [ { include = "ulid_transform", from = "src" }, ] include = [ { path = "src/ulid_transform/*.so", format = "wheel" }, { path = "src/ulid_transform/*.pyd", format = "wheel" }, ] [tool.poetry.build] generate-setup-file = false script = "build_ext.py" [tool.poetry.dependencies] python = "^3.11" [tool.poetry.group.dev.dependencies] pytest = ">=9.0.3,<10" pytest-cov = ">=3,<8" setuptools = ">=65.4.1,<83.0.0" pytest-codspeed = ">=5.0.2,<6.0.0" [tool.poetry.group.benchmark.dependencies] ulid-py = "^1.1.0" ulid2 = "^0.3.0" pytest-benchmark = ">=4,<6" [tool.semantic_release] version_toml = ["pyproject.toml:project.version"] version_variables = [ "src/ulid_transform/__init__.py:__version__", ] build_command = "pip install poetry && poetry build" [tool.pytest.ini_options] pythonpath = ["src"] [tool.coverage.run] branch = true [tool.coverage.report] exclude_lines = [ "pragma: no cover", "@overload", "if TYPE_CHECKING", "raise NotImplementedError", 'if __name__ == "__main__":', ] [tool.mypy] check_untyped_defs = true disallow_any_generics = true disallow_incomplete_defs = true disallow_untyped_defs = true mypy_path = "src/" no_implicit_optional = true show_error_codes = true warn_unreachable = true warn_unused_ignores = true exclude = [ 'setup.py', ] [[tool.mypy.overrides]] module = "tests.*" allow_untyped_defs = true warn_unused_ignores = false [[tool.mypy.overrides]] module = "bench.*" allow_untyped_defs = true [tool.ruff] required-version = ">=0.5.0" target-version = "py311" [tool.ruff.lint] select = [ "A", # flake8-builtins "ARG", # flake8-unused-arguments "ASYNC", # async rules "B", # flake8-bugbear "BLE", # flake8-blind-except "C4", # flake8-comprehensions "C90", # mccabe complexity "DTZ", # flake8-datetimez "E", # pycodestyle "EM", # flake8-errmsg "ERA", # eradicate (commented-out code) "EXE", # flake8-executable "F", # pyflakes/autoflake "FA", # flake8-future-annotations "FIX", # flake8-fixme "FLY", # flynt "FURB", # refurb "G", # flake8-logging-format "I", # isort "ICN", # flake8-import-conventions "INP", # flake8-no-pep420 (implicit namespace packages) "ISC", # flake8-implicit-str-concat "LOG", # flake8-logging "N", # pep8-naming "NPY", # numpy-specific rules "PERF", # Perflint "PGH", # pygrep-hooks "PIE", # flake8-pie "PL", # pylint "PT", # flake8-pytest-style "PTH", # flake8-use-pathlib "PYI", # flake8-pyi "Q", # flake8-quotes "UP", # pyupgrade "RET", # flake8-return "RSE", # flake8-raise "RUF", # ruff "S", # flake8-bandit "SIM", # flake8-SIM "SLF", # flake8-self "SLOT", # flake8-slots "T10", # flake8-debugger "T20", # flake8-print "TC", # flake8-type-checking "TD", # flake8-todos "TID", # Tidy imports "TRY", # try rules "W", # pycodestyle warnings "YTT", # flake8-2020 ] ignore = [ "ASYNC109", # `timeout` parameters are part of the public async API "E501", # line too long "PLR0911", # Too many return statements "PLR0912", # Too many branches "PLR0913", # Too many arguments to function call "PLR0915", # Too many statements "PLR2004", # Magic value used in comparison "PLW2901", # Outer variable overwritten by inner target "TRY003", # Avoid specifying long messages outside the exception class "TID252", # Prefer absolute imports over relative imports from parent modules ] [tool.ruff.lint.isort] force-sort-within-sections = true known-first-party = ["ulid_transform", "tests"] combine-as-imports = true split-on-trailing-comma = false [tool.ruff.lint.per-file-ignores] "bench/*" = ["S101", "SLF", "T20"] "tests/*" = ["ARG", "S101", "S106", "S311", "SLF"] [build-system] requires = ['setuptools>=77.0', "poetry-core>=2.1.0"] build-backend = "poetry.core.masonry.api" Bluetooth-Devices-ulid-transform-f51d4f1/renovate.json000066400000000000000000000001011520476015000231250ustar00rootroot00000000000000{ "extends": ["github>browniebroke/renovate-configs:python"] } Bluetooth-Devices-ulid-transform-f51d4f1/src/000077500000000000000000000000001520476015000212065ustar00rootroot00000000000000Bluetooth-Devices-ulid-transform-f51d4f1/src/ulid_transform/000077500000000000000000000000001520476015000242365ustar00rootroot00000000000000Bluetooth-Devices-ulid-transform-f51d4f1/src/ulid_transform/__init__.py000066400000000000000000000015251520476015000263520ustar00rootroot00000000000000__version__ = "2.2.9" try: from ._ulid_impl import ( bytes_to_ulid, bytes_to_ulid_or_none, ulid_at_time, ulid_at_time_bytes, ulid_hex, ulid_now, ulid_now_bytes, ulid_to_bytes, ulid_to_bytes_or_none, ulid_to_timestamp, ) except ImportError: from ._py_ulid_impl import ( bytes_to_ulid, bytes_to_ulid_or_none, ulid_at_time, ulid_at_time_bytes, ulid_hex, ulid_now, ulid_now_bytes, ulid_to_bytes, ulid_to_bytes_or_none, ulid_to_timestamp, ) __all__ = [ "bytes_to_ulid", "bytes_to_ulid_or_none", "ulid_at_time", "ulid_at_time_bytes", "ulid_hex", "ulid_now", "ulid_now_bytes", "ulid_to_bytes", "ulid_to_bytes_or_none", "ulid_to_timestamp", ] Bluetooth-Devices-ulid-transform-f51d4f1/src/ulid_transform/__init__.pyi000066400000000000000000000007241520476015000265230ustar00rootroot00000000000000def ulid_hex() -> str: ... def ulid_at_time_bytes(timestamp: float) -> bytes: ... def ulid_now_bytes() -> bytes: ... def ulid_now() -> str: ... def ulid_at_time(timestamp: float) -> str: ... def ulid_to_bytes(value: str) -> bytes: ... def bytes_to_ulid(value: bytes) -> str: ... def ulid_to_bytes_or_none(ulid: str | None) -> bytes | None: ... def bytes_to_ulid_or_none(ulid_bytes: bytes | None) -> str | None: ... def ulid_to_timestamp(ulid: str | bytes) -> int: ... Bluetooth-Devices-ulid-transform-f51d4f1/src/ulid_transform/_py_ulid_impl.py000066400000000000000000000300551520476015000274400ustar00rootroot00000000000000import array from random import getrandbits from time import time # From https://github.com/ahawker/ulid/blob/06289583e9de4286b4d80b4ad000d137816502ca/ulid/base32.py#L102 #: Array that maps encoded string char byte values to enable O(1) lookups. _DECODE = array.array( "B", ( 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x01, 0x12, 0x13, 0x01, 0x14, 0x15, 0x00, 0x16, 0x17, 0x18, 0x19, 0x1A, 0xFF, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x01, 0x12, 0x13, 0x01, 0x14, 0x15, 0x00, 0x16, 0x17, 0x18, 0x19, 0x1A, 0xFF, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, ), ) def ulid_hex() -> str: """ Generate a ULID in lowercase hex that will work for a UUID. This ulid should not be used for cryptographically secure operations. This string can be converted with https://github.com/ahawker/ulid ulid.from_uuid(uuid.UUID(ulid_hex)) """ return f"{int(time() * 1000):012x}{getrandbits(80):020x}" def ulid_at_time_bytes(timestamp: float) -> bytes: """ Generate an ULID as 16 bytes that will work for a UUID. uuid.UUID(bytes=ulid_bytes) """ if not isinstance(timestamp, (int, float)): msg = f"must be real number, not {type(timestamp).__name__}" # type: ignore[unreachable] raise TypeError(msg) ms = int(timestamp * 1000) if ms == 0 and timestamp < 0: # The C extension checks the sign of the millisecond value before the # int cast, so it rejects any negative timestamp. Python's int() # truncates toward zero, so timestamps in (-0.001, 0) seconds silently # produced a ts=0 ULID instead of raising. Match the C extension's # OverflowError. (NaN/inf/true-negative cases already raise from the # int() or to_bytes() calls; -0.0 is not < 0 and stays valid.) msg = "can't convert negative int to unsigned" raise OverflowError(msg) return ms.to_bytes(6, byteorder="big") + int(getrandbits(80)).to_bytes( 10, byteorder="big" ) def ulid_now_bytes() -> bytes: """Generate an ULID as 16 bytes that will work for a UUID.""" return ulid_at_time_bytes(time()) def ulid_now() -> str: """Generate a ULID.""" return ulid_at_time(time()) def ulid_at_time(timestamp: float) -> str: """ Generate a ULID. This ulid should not be used for cryptographically secure operations. 01AN4Z07BY 79KA1307SR9X4MV3 |----------| |----------------| Timestamp Randomness 48bits 80bits This string can be loaded directly with https://github.com/ahawker/ulid import ulid_transform as ulid_util import ulid ulid.parse(ulid_util.ulid()) """ return _encode(ulid_at_time_bytes(timestamp)) def _encode(ulid_bytes: bytes) -> str: # This is base32 crockford encoding with the loop unrolled for performance # # This code is adapted from: # https://github.com/ahawker/ulid/blob/06289583e9de4286b4d80b4ad000d137816502ca/ulid/base32.py#L102 # enc = "0123456789ABCDEFGHJKMNPQRSTVWXYZ" return ( enc[(ulid_bytes[0] & 224) >> 5] + enc[ulid_bytes[0] & 31] + enc[(ulid_bytes[1] & 248) >> 3] + enc[((ulid_bytes[1] & 7) << 2) | ((ulid_bytes[2] & 192) >> 6)] + enc[((ulid_bytes[2] & 62) >> 1)] + enc[((ulid_bytes[2] & 1) << 4) | ((ulid_bytes[3] & 240) >> 4)] + enc[((ulid_bytes[3] & 15) << 1) | ((ulid_bytes[4] & 128) >> 7)] + enc[(ulid_bytes[4] & 124) >> 2] + enc[((ulid_bytes[4] & 3) << 3) | ((ulid_bytes[5] & 224) >> 5)] + enc[ulid_bytes[5] & 31] + enc[(ulid_bytes[6] & 248) >> 3] + enc[((ulid_bytes[6] & 7) << 2) | ((ulid_bytes[7] & 192) >> 6)] + enc[(ulid_bytes[7] & 62) >> 1] + enc[((ulid_bytes[7] & 1) << 4) | ((ulid_bytes[8] & 240) >> 4)] + enc[((ulid_bytes[8] & 15) << 1) | ((ulid_bytes[9] & 128) >> 7)] + enc[(ulid_bytes[9] & 124) >> 2] + enc[((ulid_bytes[9] & 3) << 3) | ((ulid_bytes[10] & 224) >> 5)] + enc[ulid_bytes[10] & 31] + enc[(ulid_bytes[11] & 248) >> 3] + enc[((ulid_bytes[11] & 7) << 2) | ((ulid_bytes[12] & 192) >> 6)] + enc[(ulid_bytes[12] & 62) >> 1] + enc[((ulid_bytes[12] & 1) << 4) | ((ulid_bytes[13] & 240) >> 4)] + enc[((ulid_bytes[13] & 15) << 1) | ((ulid_bytes[14] & 128) >> 7)] + enc[(ulid_bytes[14] & 124) >> 2] + enc[((ulid_bytes[14] & 3) << 3) | ((ulid_bytes[15] & 224) >> 5)] + enc[ulid_bytes[15] & 31] ) def ulid_to_bytes(value: str) -> bytes: """Decode a ulid to bytes.""" if not isinstance(value, str): msg = f"ULID must be a string, not {type(value).__name__}" # type: ignore[unreachable] raise TypeError(msg) if len(value) != 26 or not value.isascii(): # The C extension measures length in UTF-8 bytes via # PyUnicode_AsUTF8AndSize, so any non-ASCII codepoint pushes the byte # length past 26 and is rejected with ValueError there. Match that: a # non-ASCII string is never a valid 26-character ULID, and folding the # check here keeps the exception type aligned (C raises ValueError; the # bare value.encode("ascii") below would otherwise raise # UnicodeEncodeError). # Use repr (!r) to match the C extension's PyErr_Format("%R", arg) and # the bytes-decode messages, which already repr-quote the value. msg = f"ULID must be a 26 character string: {value!r}" raise ValueError(msg) encoded = value.encode("ascii") decoding = _DECODE return bytes( ( ((decoding[encoded[0]] << 5) | decoding[encoded[1]]) & 0xFF, ((decoding[encoded[2]] << 3) | (decoding[encoded[3]] >> 2)) & 0xFF, ( (decoding[encoded[3]] << 6) | (decoding[encoded[4]] << 1) | (decoding[encoded[5]] >> 4) ) & 0xFF, ((decoding[encoded[5]] << 4) | (decoding[encoded[6]] >> 1)) & 0xFF, ( (decoding[encoded[6]] << 7) | (decoding[encoded[7]] << 2) | (decoding[encoded[8]] >> 3) ) & 0xFF, ((decoding[encoded[8]] << 5) | (decoding[encoded[9]])) & 0xFF, ((decoding[encoded[10]] << 3) | (decoding[encoded[11]] >> 2)) & 0xFF, ( (decoding[encoded[11]] << 6) | (decoding[encoded[12]] << 1) | (decoding[encoded[13]] >> 4) ) & 0xFF, ((decoding[encoded[13]] << 4) | (decoding[encoded[14]] >> 1)) & 0xFF, ( (decoding[encoded[14]] << 7) | (decoding[encoded[15]] << 2) | (decoding[encoded[16]] >> 3) ) & 0xFF, ((decoding[encoded[16]] << 5) | (decoding[encoded[17]])) & 0xFF, ((decoding[encoded[18]] << 3) | (decoding[encoded[19]] >> 2)) & 0xFF, ( (decoding[encoded[19]] << 6) | (decoding[encoded[20]] << 1) | (decoding[encoded[21]] >> 4) ) & 0xFF, ((decoding[encoded[21]] << 4) | (decoding[encoded[22]] >> 1)) & 0xFF, ( (decoding[encoded[22]] << 7) | (decoding[encoded[23]] << 2) | (decoding[encoded[24]] >> 3) ) & 0xFF, ((decoding[encoded[24]] << 5) | (decoding[encoded[25]])) & 0xFF, ) ) def bytes_to_ulid(value: bytes) -> str: """Encode bytes to a ulid.""" if not isinstance(value, bytes): msg = f"ULID bytes must be bytes, not {type(value).__name__}" # type: ignore[unreachable] raise TypeError(msg) if len(value) != 16: msg = f"ULID bytes must be 16 bytes: {value!r}" raise ValueError(msg) return _encode(value) def ulid_to_bytes_or_none(ulid: str | None) -> bytes | None: """Convert an ulid to bytes.""" if not isinstance(ulid, str) or len(ulid) != 26: return None try: return ulid_to_bytes(ulid) except (ValueError, UnicodeEncodeError): return None def bytes_to_ulid_or_none(ulid_bytes: bytes | None) -> str | None: """Convert bytes to a ulid.""" if not isinstance(ulid_bytes, bytes) or len(ulid_bytes) != 16: return None return bytes_to_ulid(ulid_bytes) def ulid_to_timestamp(ulid: str | bytes) -> int: """ Get the timestamp from a ULID. The returned value is in milliseconds since the UNIX epoch. """ if isinstance(ulid, bytes): if len(ulid) != 16: msg = f"ULID bytes must be 16 bytes: {ulid!r}" raise ValueError(msg) ulid_bytes = ulid elif isinstance(ulid, str): ulid_bytes = ulid_to_bytes(ulid) else: msg = f"ULID must be a string or bytes, not {type(ulid).__name__}" # type: ignore[unreachable] raise TypeError(msg) return int.from_bytes(b"\x00\x00" + ulid_bytes[:6], "big") Bluetooth-Devices-ulid-transform-f51d4f1/src/ulid_transform/_ulid_impl.cpp000066400000000000000000000273751520476015000270750ustar00rootroot00000000000000#include "Python.h" #include #include #ifdef __SIZEOF_INT128__ #include "ulid_uint128.hh" #else #include "ulid_struct.hh" #endif #if defined(__has_builtin) #if __has_builtin(__builtin_expect) #define ULID_LIKELY(x) __builtin_expect(!!(x), 1) #else #define ULID_LIKELY(x) (x) #endif #elif defined(__GNUC__) #define ULID_LIKELY(x) __builtin_expect(!!(x), 1) #else #define ULID_LIKELY(x) (x) #endif constexpr Py_ssize_t ULID_BYTES_LEN = 16; constexpr Py_ssize_t ULID_TEXT_LEN = 26; constexpr Py_ssize_t ULID_HEX_LEN = 32; /** * Create a PyUnicode object from a known-ASCII buffer without validation. * All callers MUST GUARANTEE the buffer contains only ASCII characters. */ static inline PyObject* unicode_from_ascii_buf(const char* buf, Py_ssize_t len) { PyObject* str = PyUnicode_New(len, 127); if (!str) { return NULL; } // This is likely on any and all current versions of CPython. if (ULID_LIKELY(PyUnicode_IS_COMPACT_ASCII(str))) { memcpy(PyUnicode_DATA(str), buf, len); } else { // Unexpected layout; copy character-by-character via public API. for (Py_ssize_t i = 0; i < len; i++) PyUnicode_WriteChar(str, i, (unsigned char)buf[i]); } return str; } static inline void hexlify_16(const uint8_t b[ULID_BYTES_LEN], char dst[ULID_HEX_LEN]) { static const char hexdigits[17] = "0123456789abcdef"; for (int i = 0, j = 0; i < ULID_BYTES_LEN; i++) { dst[j++] = hexdigits[b[i] >> 4]; dst[j++] = hexdigits[b[i] & 0x0f]; } } static inline uint64_t bytes_to_timestamp(const uint8_t b[ULID_BYTES_LEN]) { return (static_cast(b[0]) << 40) | (static_cast(b[1]) << 32) | (static_cast(b[2]) << 24) | (static_cast(b[3]) << 16) | (static_cast(b[4]) << 8) | static_cast(b[5]); } /* ulid_hex() -> str */ static PyObject* py_ulid_hex(PyObject* module, PyObject* Py_UNUSED(ignored)) { ulid::ULID ulid; ulid::EncodeTimeSystemClockNow(ulid); ulid::EncodeEntropyFast(ulid); uint8_t buf[ULID_BYTES_LEN]; ulid::MarshalBinaryTo(ulid, buf); char hex[ULID_HEX_LEN]; hexlify_16(buf, hex); return unicode_from_ascii_buf(hex, ULID_HEX_LEN); // SAFETY: Hexadecimal digits are ASCII characters } /* ulid_now_bytes() -> bytes */ static PyObject* py_ulid_now_bytes(PyObject* module, PyObject* Py_UNUSED(ignored)) { ulid::ULID ulid; ulid::EncodeTimeSystemClockNow(ulid); ulid::EncodeEntropyFast(ulid); uint8_t buf[ULID_BYTES_LEN]; ulid::MarshalBinaryTo(ulid, buf); return PyBytes_FromStringAndSize((const char*)buf, ULID_BYTES_LEN); } /* Validate a timestamp (in seconds) and convert to milliseconds. * Returns 0 on success, -1 on error (with a Python exception set). * Error types/messages mirror what int(ts * 1000).to_bytes(6, 'big') raises * in the Python implementation, so the two impls have exception-type parity. */ static inline int validate_timestamp_ms(double ts, int64_t* out_ms) { if (std::isnan(ts)) { PyErr_SetString(PyExc_ValueError, "cannot convert float NaN to integer"); return -1; } if (std::isinf(ts)) { PyErr_SetString(PyExc_OverflowError, "cannot convert float infinity to integer"); return -1; } double ts_ms = ts * 1000.0; if (ts_ms < 0.0) { PyErr_SetString(PyExc_OverflowError, "can't convert negative int to unsigned"); return -1; } // ULID timestamps are 48-bit unsigned milliseconds. // 2^48 (281474976710656) is the exclusive upper bound and is exactly // representable as a double, so the comparison is precise here. if (ts_ms >= 281474976710656.0) { PyErr_SetString(PyExc_OverflowError, "int too big to convert"); return -1; } *out_ms = static_cast(ts_ms); return 0; } /* ulid_at_time_bytes(timestamp) -> bytes */ static PyObject* py_ulid_at_time_bytes(PyObject* module, PyObject* arg) { double ts = PyFloat_AsDouble(arg); if (ts == -1.0 && PyErr_Occurred()) return NULL; int64_t ts_ms; if (validate_timestamp_ms(ts, &ts_ms) < 0) return NULL; ulid::ULID ulid; ulid::EncodeTimestamp(ts_ms, ulid); ulid::EncodeEntropyFast(ulid); uint8_t buf[ULID_BYTES_LEN]; ulid::MarshalBinaryTo(ulid, buf); return PyBytes_FromStringAndSize((const char*)buf, ULID_BYTES_LEN); } /* ulid_now() -> str */ static PyObject* py_ulid_now(PyObject* module, PyObject* Py_UNUSED(ignored)) { ulid::ULID ulid; ulid::EncodeTimeSystemClockNow(ulid); ulid::EncodeEntropyFast(ulid); char buf[ULID_TEXT_LEN]; ulid::MarshalTo(ulid, buf); return unicode_from_ascii_buf(buf, ULID_TEXT_LEN); // SAFETY: text ULIDs are ASCII } /* ulid_at_time(timestamp) -> str */ static PyObject* py_ulid_at_time(PyObject* module, PyObject* arg) { double ts = PyFloat_AsDouble(arg); if (ts == -1.0 && PyErr_Occurred()) return NULL; int64_t ts_ms; if (validate_timestamp_ms(ts, &ts_ms) < 0) return NULL; ulid::ULID ulid; ulid::EncodeTimestamp(ts_ms, ulid); ulid::EncodeEntropyFast(ulid); char buf[ULID_TEXT_LEN]; ulid::MarshalTo(ulid, buf); return unicode_from_ascii_buf(buf, ULID_TEXT_LEN); // SAFETY: text ULIDs are ASCII } /* ulid_to_bytes(value) -> bytes */ static PyObject* py_ulid_to_bytes(PyObject* module, PyObject* arg) { if (!PyUnicode_Check(arg)) { PyErr_Format(PyExc_TypeError, "ULID must be a string, not %.200s", Py_TYPE(arg)->tp_name); return NULL; } Py_ssize_t len; const char* str = PyUnicode_AsUTF8AndSize(arg, &len); if (!str) return NULL; // len is the UTF-8 byte length. A non-ASCII string can still be exactly // 26 bytes (e.g. 13 two-byte codepoints), so an ASCII check is required to // reject it — otherwise DecodeBase32To reads garbage. Mirrors the Python // impl's `len(value) != 26 or not value.isascii()`. if (len != ULID_TEXT_LEN || !PyUnicode_IS_ASCII(arg)) { PyErr_Format(PyExc_ValueError, "ULID must be a 26 character string: %R", arg); return NULL; } uint8_t buf[ULID_BYTES_LEN]; ulid::DecodeBase32To(str, buf); return PyBytes_FromStringAndSize((const char*)buf, ULID_BYTES_LEN); } /* bytes_to_ulid(value) -> str */ static PyObject* py_bytes_to_ulid(PyObject* module, PyObject* arg) { if (!PyBytes_Check(arg)) { PyErr_Format(PyExc_TypeError, "ULID bytes must be bytes, not %.200s", Py_TYPE(arg)->tp_name); return NULL; } if (PyBytes_GET_SIZE(arg) != ULID_BYTES_LEN) { PyErr_Format(PyExc_ValueError, "ULID bytes must be 16 bytes: %R", arg); return NULL; } char buf[ULID_TEXT_LEN]; ulid::EncodeBase32From((const uint8_t*)PyBytes_AS_STRING(arg), buf); return unicode_from_ascii_buf(buf, ULID_TEXT_LEN); // SAFETY: base32 is ASCII } /* ulid_to_bytes_or_none(ulid) -> bytes | None */ static PyObject* py_ulid_to_bytes_or_none(PyObject* module, PyObject* arg) { if (arg == Py_None || !PyUnicode_Check(arg)) Py_RETURN_NONE; Py_ssize_t len; const char* str = PyUnicode_AsUTF8AndSize(arg, &len); if (!str) return NULL; // See py_ulid_to_bytes: a non-ASCII string can be exactly 26 UTF-8 bytes, // so reject anything non-ASCII to match the Python impl returning None. if (len != ULID_TEXT_LEN || !PyUnicode_IS_ASCII(arg)) Py_RETURN_NONE; uint8_t buf[ULID_BYTES_LEN]; ulid::DecodeBase32To(str, buf); return PyBytes_FromStringAndSize((const char*)buf, ULID_BYTES_LEN); } /* bytes_to_ulid_or_none(ulid_bytes) -> str | None */ static PyObject* py_bytes_to_ulid_or_none(PyObject* module, PyObject* arg) { if (arg == Py_None || !PyBytes_Check(arg) || PyBytes_GET_SIZE(arg) != ULID_BYTES_LEN) Py_RETURN_NONE; char buf[ULID_TEXT_LEN]; ulid::EncodeBase32From((const uint8_t*)PyBytes_AS_STRING(arg), buf); return unicode_from_ascii_buf(buf, ULID_TEXT_LEN); // SAFETY: base32 is ASCII } /* ulid_to_timestamp(ulid) -> int */ static PyObject* py_ulid_to_timestamp(PyObject* module, PyObject* arg) { if (PyBytes_Check(arg)) { if (PyBytes_GET_SIZE(arg) != ULID_BYTES_LEN) { PyErr_Format(PyExc_ValueError, "ULID bytes must be 16 bytes: %R", arg); return NULL; } uint64_t ts = bytes_to_timestamp( (const uint8_t*)PyBytes_AS_STRING(arg)); return PyLong_FromUnsignedLongLong(ts); } if (!PyUnicode_Check(arg)) { PyErr_Format(PyExc_TypeError, "ULID must be a string or bytes, not %.200s", Py_TYPE(arg)->tp_name); return NULL; } Py_ssize_t len; const char* str = PyUnicode_AsUTF8AndSize(arg, &len); if (!str) return NULL; // See py_ulid_to_bytes: a non-ASCII string can be exactly 26 UTF-8 bytes, // so reject anything non-ASCII to match the Python impl raising ValueError. if (len != ULID_TEXT_LEN || !PyUnicode_IS_ASCII(arg)) { PyErr_Format(PyExc_ValueError, "ULID must be a 26 character string: %R", arg); return NULL; } uint8_t buf[ULID_BYTES_LEN]; ulid::DecodeBase32To(str, buf); uint64_t ts = bytes_to_timestamp(buf); return PyLong_FromUnsignedLongLong(ts); } static PyMethodDef module_methods[] = { { "ulid_hex", py_ulid_hex, METH_NOARGS, "ulid_hex()\n--\n\n" "Generate a ULID in lowercase hex that will work for a UUID.\n\n" "This ulid should not be used for cryptographically secure\n" "operations.\n\n" "This string can be converted with https://github.com/ahawker/ulid\n\n" "ulid.from_uuid(uuid.UUID(ulid_hex))" }, { "ulid_now_bytes", py_ulid_now_bytes, METH_NOARGS, "ulid_now_bytes()\n--\n\n" "Generate an ULID as 16 bytes that will work for a UUID." }, { "ulid_at_time_bytes", py_ulid_at_time_bytes, METH_O, "ulid_at_time_bytes($self, timestamp, /)\n--\n\n" "Generate an ULID as 16 bytes that will work for a UUID.\n\n" "uuid.UUID(bytes=ulid_bytes)" }, { "ulid_now", py_ulid_now, METH_NOARGS, "ulid_now()\n--\n\n" "Generate a ULID." }, { "ulid_at_time", py_ulid_at_time, METH_O, "ulid_at_time($self, timestamp, /)\n--\n\n" "Generate a ULID.\n\n" "This ulid should not be used for cryptographically secure\n" "operations.\n\n" " 01AN4Z07BY 79KA1307SR9X4MV3\n" "|----------| |----------------|\n" " Timestamp Randomness\n" " 48bits 80bits\n\n" "This string can be loaded directly with https://github.com/ahawker/ulid\n\n" "import ulid_transform as ulid_util\n" "import ulid\n" "ulid.parse(ulid_util.ulid())" }, { "ulid_to_bytes", py_ulid_to_bytes, METH_O, "ulid_to_bytes($self, value, /)\n--\n\n" "Decode a ulid to bytes." }, { "bytes_to_ulid", py_bytes_to_ulid, METH_O, "bytes_to_ulid($self, value, /)\n--\n\n" "Encode bytes to a ulid." }, { "ulid_to_bytes_or_none", py_ulid_to_bytes_or_none, METH_O, "ulid_to_bytes_or_none($self, ulid, /)\n--\n\n" "Convert an ulid to bytes." }, { "bytes_to_ulid_or_none", py_bytes_to_ulid_or_none, METH_O, "bytes_to_ulid_or_none($self, ulid_bytes, /)\n--\n\n" "Convert bytes to a ulid." }, { "ulid_to_timestamp", py_ulid_to_timestamp, METH_O, "ulid_to_timestamp($self, ulid, /)\n--\n\n" "Get the timestamp from a ULID.\n" "The returned value is in milliseconds since the UNIX epoch." }, { NULL, NULL, 0, NULL } }; static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "ulid_transform._ulid_impl", NULL, -1, module_methods, }; PyMODINIT_FUNC PyInit__ulid_impl(void) { return PyModule_Create(&moduledef); } Bluetooth-Devices-ulid-transform-f51d4f1/src/ulid_transform/py.typed000066400000000000000000000000001520476015000257230ustar00rootroot00000000000000Bluetooth-Devices-ulid-transform-f51d4f1/src/ulid_transform/splitmix64.hh000066400000000000000000000010571520476015000266050ustar00rootroot00000000000000#ifndef ULID_SPLITMIX64_HH #define ULID_SPLITMIX64_HH #include namespace ulid { /** * SplitMix64 is a fast, small-state PRNG suitable for non-cryptographic use. */ struct SplitMix64 { uint64_t state; explicit SplitMix64(uint64_t seed) : state(seed) { } uint64_t operator()() { uint64_t z = (state += 0x9e3779b97f4a7c15ULL); z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ULL; z = (z ^ (z >> 27)) * 0x94d049bb133111ebULL; return z ^ (z >> 31); } }; } #endif // ULID_SPLITMIX64_HH Bluetooth-Devices-ulid-transform-f51d4f1/src/ulid_transform/ulid_base32.hh000066400000000000000000000137011520476015000266550ustar00rootroot00000000000000#ifndef ULID_BASE32_HH #define ULID_BASE32_HH #include namespace ulid { /** * Crockford's Base32 * */ static const char Encoding[33] = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; /** * dec stores decimal encodings for characters. * 0xFF indicates invalid character. * 48-57 are digits. * 65-90 are capital alphabets, 97-122 are lowercase. * Per Crockford's base32 spec, decoding is case-insensitive and accepts * the aliases I/i/L/l -> 1, O/o -> 0. U/u are excluded. * */ static const uint8_t dec[256] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0 1 2 3 4 5 6 7 */ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, /* 8 9 */ 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 10(A) 11(B) 12(C) 13(D) 14(E) 15(F) 16(G) */ 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, /*17(H) (I) 18(J) 19(K) (L) 20(M) 21(N) (O) */ 0x11, 0x01, 0x12, 0x13, 0x01, 0x14, 0x15, 0x00, /*22(P)23(Q)24(R) 25(S) 26(T) (U) 27(V) 28(W) */ 0x16, 0x17, 0x18, 0x19, 0x1A, 0xFF, 0x1B, 0x1C, /*29(X)30(Y)31(Z) */ 0x1D, 0x1E, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 10(a) 11(b) 12(c) 13(d) 14(e) 15(f) 16(g) */ 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, /*17(h) (i) 18(j) 19(k) (l) 20(m) 21(n) (o) */ 0x11, 0x01, 0x12, 0x13, 0x01, 0x14, 0x15, 0x00, /*22(p)23(q)24(r) 25(s) 26(t) (u) 27(v) 28(w) */ 0x16, 0x17, 0x18, 0x19, 0x1A, 0xFF, 0x1B, 0x1C, /*29(x)30(y)31(z) */ 0x1D, 0x1E, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; /** * Decode a base32 ULID string directly to 16 bytes, no intermediate ULID. * */ inline void DecodeBase32To(const char str[26], uint8_t dst[16]) { // timestamp dst[0] = (dec[(unsigned char)str[0]] << 5) | dec[(unsigned char)str[1]]; dst[1] = (dec[(unsigned char)str[2]] << 3) | (dec[(unsigned char)str[3]] >> 2); dst[2] = (dec[(unsigned char)str[3]] << 6) | (dec[(unsigned char)str[4]] << 1) | (dec[(unsigned char)str[5]] >> 4); dst[3] = (dec[(unsigned char)str[5]] << 4) | (dec[(unsigned char)str[6]] >> 1); dst[4] = (dec[(unsigned char)str[6]] << 7) | (dec[(unsigned char)str[7]] << 2) | (dec[(unsigned char)str[8]] >> 3); dst[5] = (dec[(unsigned char)str[8]] << 5) | dec[(unsigned char)str[9]]; // entropy dst[6] = (dec[(unsigned char)str[10]] << 3) | (dec[(unsigned char)str[11]] >> 2); dst[7] = (dec[(unsigned char)str[11]] << 6) | (dec[(unsigned char)str[12]] << 1) | (dec[(unsigned char)str[13]] >> 4); dst[8] = (dec[(unsigned char)str[13]] << 4) | (dec[(unsigned char)str[14]] >> 1); dst[9] = (dec[(unsigned char)str[14]] << 7) | (dec[(unsigned char)str[15]] << 2) | (dec[(unsigned char)str[16]] >> 3); dst[10] = (dec[(unsigned char)str[16]] << 5) | dec[(unsigned char)str[17]]; dst[11] = (dec[(unsigned char)str[18]] << 3) | (dec[(unsigned char)str[19]] >> 2); dst[12] = (dec[(unsigned char)str[19]] << 6) | (dec[(unsigned char)str[20]] << 1) | (dec[(unsigned char)str[21]] >> 4); dst[13] = (dec[(unsigned char)str[21]] << 4) | (dec[(unsigned char)str[22]] >> 1); dst[14] = (dec[(unsigned char)str[22]] << 7) | (dec[(unsigned char)str[23]] << 2) | (dec[(unsigned char)str[24]] >> 3); dst[15] = (dec[(unsigned char)str[24]] << 5) | dec[(unsigned char)str[25]]; } /** * Encode 16 bytes directly to a base32 ULID string, no intermediate ULID. * */ inline void EncodeBase32From(const uint8_t b[16], char dst[26]) { // timestamp dst[0] = Encoding[(b[0] & 224) >> 5]; dst[1] = Encoding[b[0] & 31]; dst[2] = Encoding[(b[1] & 248) >> 3]; dst[3] = Encoding[((b[1] & 7) << 2) | ((b[2] & 192) >> 6)]; dst[4] = Encoding[(b[2] & 62) >> 1]; dst[5] = Encoding[((b[2] & 1) << 4) | ((b[3] & 240) >> 4)]; dst[6] = Encoding[((b[3] & 15) << 1) | ((b[4] & 128) >> 7)]; dst[7] = Encoding[(b[4] & 124) >> 2]; dst[8] = Encoding[((b[4] & 3) << 3) | ((b[5] & 224) >> 5)]; dst[9] = Encoding[b[5] & 31]; // entropy dst[10] = Encoding[(b[6] & 248) >> 3]; dst[11] = Encoding[((b[6] & 7) << 2) | ((b[7] & 192) >> 6)]; dst[12] = Encoding[(b[7] & 62) >> 1]; dst[13] = Encoding[((b[7] & 1) << 4) | ((b[8] & 240) >> 4)]; dst[14] = Encoding[((b[8] & 15) << 1) | ((b[9] & 128) >> 7)]; dst[15] = Encoding[(b[9] & 124) >> 2]; dst[16] = Encoding[((b[9] & 3) << 3) | ((b[10] & 224) >> 5)]; dst[17] = Encoding[b[10] & 31]; dst[18] = Encoding[(b[11] & 248) >> 3]; dst[19] = Encoding[((b[11] & 7) << 2) | ((b[12] & 192) >> 6)]; dst[20] = Encoding[(b[12] & 62) >> 1]; dst[21] = Encoding[((b[12] & 1) << 4) | ((b[13] & 240) >> 4)]; dst[22] = Encoding[((b[13] & 15) << 1) | ((b[14] & 128) >> 7)]; dst[23] = Encoding[(b[14] & 124) >> 2]; dst[24] = Encoding[((b[14] & 3) << 3) | ((b[15] & 224) >> 5)]; dst[25] = Encoding[b[15] & 31]; } }; // namespace ulid #endif // ULID_BASE32_HH Bluetooth-Devices-ulid-transform-f51d4f1/src/ulid_transform/ulid_struct.hh000066400000000000000000000113431520476015000271220ustar00rootroot00000000000000#ifndef ULID_STRUCT_HH #define ULID_STRUCT_HH #include #include #include #include #include #include #include #include #include "splitmix64.hh" #include "ulid_base32.hh" namespace ulid { /** * ULID is a 16 byte Universally Unique Lexicographically Sortable Identifier * */ struct ULID { uint8_t data[16]; }; /** * EncodeTimestamp will encode the int64_t timestamp to the passed ulid * */ inline void EncodeTimestamp(int64_t timestamp, ULID& ulid) { ulid.data[0] = static_cast(timestamp >> 40); ulid.data[1] = static_cast(timestamp >> 32); ulid.data[2] = static_cast(timestamp >> 24); ulid.data[3] = static_cast(timestamp >> 16); ulid.data[4] = static_cast(timestamp >> 8); ulid.data[5] = static_cast(timestamp); } /** * EncodeTime will encode the time point to the passed ulid * */ inline void EncodeTime(std::chrono::time_point time_point, ULID& ulid) { auto time_ms = std::chrono::time_point_cast(time_point); int64_t timestamp = time_ms.time_since_epoch().count(); EncodeTimestamp(timestamp, ulid); } /** * EncodeTimeSystemClockNow will encode a ULID using the time obtained using * std::chrono::system_clock::now() by taking the timestamp in milliseconds. * */ inline void EncodeTimeSystemClockNow(ULID& ulid) { EncodeTime(std::chrono::system_clock::now(), ulid); } /** * EncodeEntropyFast will encode using SplitMix64 * with only 2 generated values. * */ inline void EncodeEntropyFast(ULID& ulid) { static thread_local SplitMix64 gen([]() { // Use multiple entropy sources for seeding uint64_t seed = static_cast(std::chrono::high_resolution_clock::now().time_since_epoch().count()); seed ^= static_cast(std::random_device { }()) << 32; seed ^= static_cast(std::random_device { }()); return seed; }()); uint64_t high = gen(); uint64_t low = gen(); ulid.data[6] = (high >> 56) & 0xFF; ulid.data[7] = (high >> 48) & 0xFF; ulid.data[8] = (high >> 40) & 0xFF; ulid.data[9] = (high >> 32) & 0xFF; ulid.data[10] = (high >> 24) & 0xFF; ulid.data[11] = (high >> 16) & 0xFF; ulid.data[12] = (high >> 8) & 0xFF; ulid.data[13] = high & 0xFF; ulid.data[14] = (low >> 8) & 0xFF; ulid.data[15] = low & 0xFF; } /** * MarshalTo will marshal a ULID to the passed character array. * * Implementation taken directly from oklog/ulid * (https://sourcegraph.com/github.com/oklog/ulid@0774f81f6e44af5ce5e91c8d7d76cf710e889ebb/-/blob/ulid.go#L162-190) * * timestamp:
* dst[0]: first 3 bits of data[0]
* dst[1]: last 5 bits of data[0]
* dst[2]: first 5 bits of data[1]
* dst[3]: last 3 bits of data[1] + first 2 bits of data[2]
* dst[4]: bits 3-7 of data[2]
* dst[5]: last bit of data[2] + first 4 bits of data[3]
* dst[6]: last 4 bits of data[3] + first bit of data[4]
* dst[7]: bits 2-6 of data[4]
* dst[8]: last 2 bits of data[4] + first 3 bits of data[5]
* dst[9]: last 5 bits of data[5]
* * entropy: * follows similarly, except now all components are set to 5 bits. * */ inline void MarshalTo(const ULID& ulid, char dst[26]) { EncodeBase32From(ulid.data, dst); } /** * MarshalBinaryTo will Marshal a ULID to the passed byte array * */ inline void MarshalBinaryTo(const ULID& ulid, uint8_t dst[16]) { // timestamp dst[0] = ulid.data[0]; dst[1] = ulid.data[1]; dst[2] = ulid.data[2]; dst[3] = ulid.data[3]; dst[4] = ulid.data[4]; dst[5] = ulid.data[5]; // entropy dst[6] = ulid.data[6]; dst[7] = ulid.data[7]; dst[8] = ulid.data[8]; dst[9] = ulid.data[9]; dst[10] = ulid.data[10]; dst[11] = ulid.data[11]; dst[12] = ulid.data[12]; dst[13] = ulid.data[13]; dst[14] = ulid.data[14]; dst[15] = ulid.data[15]; } /** * UnmarshalFrom will unmarshal a ULID from the passed character array. * */ inline void UnmarshalFrom(const char str[26], ULID& ulid) { DecodeBase32To(str, ulid.data); } /** * UnmarshalBinaryFrom will unmarshal a ULID from the passed byte array. * */ inline void UnmarshalBinaryFrom(const uint8_t b[16], ULID& ulid) { // timestamp ulid.data[0] = b[0]; ulid.data[1] = b[1]; ulid.data[2] = b[2]; ulid.data[3] = b[3]; ulid.data[4] = b[4]; ulid.data[5] = b[5]; // entropy ulid.data[6] = b[6]; ulid.data[7] = b[7]; ulid.data[8] = b[8]; ulid.data[9] = b[9]; ulid.data[10] = b[10]; ulid.data[11] = b[11]; ulid.data[12] = b[12]; ulid.data[13] = b[13]; ulid.data[14] = b[14]; ulid.data[15] = b[15]; } }; // namespace ulid #endif // ULID_STRUCT_HH Bluetooth-Devices-ulid-transform-f51d4f1/src/ulid_transform/ulid_uint128.hh000066400000000000000000000166131520476015000270150ustar00rootroot00000000000000#ifndef ULID_UINT128_HH #define ULID_UINT128_HH #include #include #include #include #include #include #include #include "splitmix64.hh" #include "ulid_base32.hh" namespace ulid { /** * ULID is a 16 byte Universally Unique Lexicographically Sortable Identifier * */ typedef __uint128_t ULID; /** * EncodeTimestamp will encode the int64_t timestamp to the passed ulid * */ inline void EncodeTimestamp(int64_t timestamp, ULID& ulid) { ULID t = static_cast(timestamp >> 40); t <<= 8; t |= static_cast(timestamp >> 32); t <<= 8; t |= static_cast(timestamp >> 24); t <<= 8; t |= static_cast(timestamp >> 16); t <<= 8; t |= static_cast(timestamp >> 8); t <<= 8; t |= static_cast(timestamp); t <<= 80; ULID mask = 1; mask <<= 80; mask--; ulid = t | (ulid & mask); } /** * EncodeTime will encode the time point to the passed ulid * */ inline void EncodeTime(std::chrono::time_point time_point, ULID& ulid) { auto time_ms = std::chrono::time_point_cast(time_point); int64_t timestamp = time_ms.time_since_epoch().count(); EncodeTimestamp(timestamp, ulid); } /** * EncodeTimeSystemClockNow will encode a ULID using the time obtained using * std::chrono::system_clock::now() by taking the timestamp in milliseconds. * */ inline void EncodeTimeSystemClockNow(ULID& ulid) { EncodeTime(std::chrono::system_clock::now(), ulid); } /** * EncodeEntropyFast will encode using SplitMix64 * with only 2 generated values (providing 128 bits, of which 80 are used). * */ inline void EncodeEntropyFast(ULID& ulid) { static thread_local SplitMix64 gen([]() { // Use multiple entropy sources for seeding uint64_t seed = static_cast(std::chrono::high_resolution_clock::now().time_since_epoch().count()); seed ^= static_cast(std::random_device { }()) << 32; seed ^= static_cast(std::random_device { }()); return seed; }()); constexpr ULID lower80 = (static_cast(1) << 80) - 1; ulid = (ulid >> 80) << 80; // Clear lower 80 bits uint64_t first_draw = gen(); uint64_t second_draw = gen(); ulid |= ((static_cast(first_draw) << 16) | (second_draw & 0xFFFF)) & lower80; } /** * MarshalTo will marshal a ULID to the passed character array. * * Implementation taken directly from oklog/ulid * (https://sourcegraph.com/github.com/oklog/ulid@0774f81f6e44af5ce5e91c8d7d76cf710e889ebb/-/blob/ulid.go#L162-190) * * timestamp: * dst[0]: first 3 bits of data[0] * dst[1]: last 5 bits of data[0] * dst[2]: first 5 bits of data[1] * dst[3]: last 3 bits of data[1] + first 2 bits of data[2] * dst[4]: bits 3-7 of data[2] * dst[5]: last bit of data[2] + first 4 bits of data[3] * dst[6]: last 4 bits of data[3] + first bit of data[4] * dst[7]: bits 2-6 of data[4] * dst[8]: last 2 bits of data[4] + first 3 bits of data[5] * dst[9]: last 5 bits of data[5] * * entropy: * follows similarly, except now all components are set to 5 bits. * */ inline void MarshalTo(const ULID& ulid, char dst[26]) { // Decompose into two 64-bit halves kept in registers to avoid // repeated memory loads the compiler generates with __uint128_t. const uint64_t hi = static_cast(ulid >> 64); const uint64_t lo = static_cast(ulid); // 10 char timestamp (3 + 9*5 = 48 bits) dst[0] = Encoding[(hi >> 61) & 0x07]; dst[1] = Encoding[(hi >> 56) & 0x1F]; dst[2] = Encoding[(hi >> 51) & 0x1F]; dst[3] = Encoding[(hi >> 46) & 0x1F]; dst[4] = Encoding[(hi >> 41) & 0x1F]; dst[5] = Encoding[(hi >> 36) & 0x1F]; dst[6] = Encoding[(hi >> 31) & 0x1F]; dst[7] = Encoding[(hi >> 26) & 0x1F]; dst[8] = Encoding[(hi >> 21) & 0x1F]; dst[9] = Encoding[(hi >> 16) & 0x1F]; // 16 char entropy (80 bits) dst[10] = Encoding[(hi >> 11) & 0x1F]; dst[11] = Encoding[(hi >> 6) & 0x1F]; dst[12] = Encoding[(hi >> 1) & 0x1F]; dst[13] = Encoding[((hi & 1) << 4) | ((lo >> 60) & 0x0F)]; dst[14] = Encoding[(lo >> 55) & 0x1F]; dst[15] = Encoding[(lo >> 50) & 0x1F]; dst[16] = Encoding[(lo >> 45) & 0x1F]; dst[17] = Encoding[(lo >> 40) & 0x1F]; dst[18] = Encoding[(lo >> 35) & 0x1F]; dst[19] = Encoding[(lo >> 30) & 0x1F]; dst[20] = Encoding[(lo >> 25) & 0x1F]; dst[21] = Encoding[(lo >> 20) & 0x1F]; dst[22] = Encoding[(lo >> 15) & 0x1F]; dst[23] = Encoding[(lo >> 10) & 0x1F]; dst[24] = Encoding[(lo >> 5) & 0x1F]; dst[25] = Encoding[lo & 0x1F]; } /** * MarshalBinaryTo will Marshal a ULID to the passed byte array * */ inline void MarshalBinaryTo(const ULID& ulid, uint8_t dst[16]) { // Use bswap to convert each 64-bit half from host order to big-endian, // instead of 16 individual byte shifts. uint64_t high = __builtin_bswap64(static_cast(ulid >> 64)); uint64_t low = __builtin_bswap64(static_cast(ulid)); __builtin_memcpy(dst, &high, 8); __builtin_memcpy(dst + 8, &low, 8); } /** * UnmarshalFrom will unmarshal a ULID from the passed character array. * */ inline void UnmarshalFrom(const char str[26], ULID& ulid) { // timestamp ulid = (dec[int(str[0])] << 5) | dec[int(str[1])]; ulid <<= 8; ulid |= (dec[int(str[2])] << 3) | (dec[int(str[3])] >> 2); ulid <<= 8; ulid |= (dec[int(str[3])] << 6) | (dec[int(str[4])] << 1) | (dec[int(str[5])] >> 4); ulid <<= 8; ulid |= (dec[int(str[5])] << 4) | (dec[int(str[6])] >> 1); ulid <<= 8; ulid |= (dec[int(str[6])] << 7) | (dec[int(str[7])] << 2) | (dec[int(str[8])] >> 3); ulid <<= 8; ulid |= (dec[int(str[8])] << 5) | dec[int(str[9])]; // entropy ulid <<= 8; ulid |= (dec[int(str[10])] << 3) | (dec[int(str[11])] >> 2); ulid <<= 8; ulid |= (dec[int(str[11])] << 6) | (dec[int(str[12])] << 1) | (dec[int(str[13])] >> 4); ulid <<= 8; ulid |= (dec[int(str[13])] << 4) | (dec[int(str[14])] >> 1); ulid <<= 8; ulid |= (dec[int(str[14])] << 7) | (dec[int(str[15])] << 2) | (dec[int(str[16])] >> 3); ulid <<= 8; ulid |= (dec[int(str[16])] << 5) | dec[int(str[17])]; ulid <<= 8; ulid |= (dec[int(str[18])] << 3) | (dec[int(str[19])] >> 2); ulid <<= 8; ulid |= (dec[int(str[19])] << 6) | (dec[int(str[20])] << 1) | (dec[int(str[21])] >> 4); ulid <<= 8; ulid |= (dec[int(str[21])] << 4) | (dec[int(str[22])] >> 1); ulid <<= 8; ulid |= (dec[int(str[22])] << 7) | (dec[int(str[23])] << 2) | (dec[int(str[24])] >> 3); ulid <<= 8; ulid |= (dec[int(str[24])] << 5) | dec[int(str[25])]; } /** * UnmarshalBinaryFrom will unmarshal a ULID from the passed byte array. * */ inline void UnmarshalBinaryFrom(const uint8_t b[16], ULID& ulid) { // timestamp ulid = b[0]; ulid <<= 8; ulid |= b[1]; ulid <<= 8; ulid |= b[2]; ulid <<= 8; ulid |= b[3]; ulid <<= 8; ulid |= b[4]; ulid <<= 8; ulid |= b[5]; // entropy ulid <<= 8; ulid |= b[6]; ulid <<= 8; ulid |= b[7]; ulid <<= 8; ulid |= b[8]; ulid <<= 8; ulid |= b[9]; ulid <<= 8; ulid |= b[10]; ulid <<= 8; ulid |= b[11]; ulid <<= 8; ulid |= b[12]; ulid <<= 8; ulid |= b[13]; ulid <<= 8; ulid |= b[14]; ulid <<= 8; ulid |= b[15]; } }; // namespace ulid #endif // ULID_UINT128_HH Bluetooth-Devices-ulid-transform-f51d4f1/tests/000077500000000000000000000000001520476015000215615ustar00rootroot00000000000000Bluetooth-Devices-ulid-transform-f51d4f1/tests/__init__.py000066400000000000000000000000001520476015000236600ustar00rootroot00000000000000Bluetooth-Devices-ulid-transform-f51d4f1/tests/benchmarks/000077500000000000000000000000001520476015000236765ustar00rootroot00000000000000Bluetooth-Devices-ulid-transform-f51d4f1/tests/benchmarks/__init__.py000066400000000000000000000000001520476015000257750ustar00rootroot00000000000000Bluetooth-Devices-ulid-transform-f51d4f1/tests/benchmarks/test_ulid.py000066400000000000000000000065011520476015000262460ustar00rootroot00000000000000from pytest_codspeed import BenchmarkFixture from ulid_transform import ( bytes_to_ulid, bytes_to_ulid_or_none, ulid_at_time, ulid_at_time_bytes, ulid_hex, ulid_now, ulid_now_bytes, ulid_to_bytes, ulid_to_bytes_or_none, ulid_to_timestamp, ) ITERATIONS = 10000 _SAMPLE_ULID_STR = "01GTCKZT7K26YEVVW6AMQ3J0VT" _SAMPLE_ULID_BYTES = b"\x01\x86\x99?\xe8\xf3\x11\xbc\xed\xef\x86U.9\x03z" def test_ulid_now(benchmark: BenchmarkFixture) -> None: _ulid_now = ulid_now @benchmark def _(): for _ in range(ITERATIONS): _ulid_now() def test_ulid_now_bytes(benchmark: BenchmarkFixture) -> None: _ulid_now_bytes = ulid_now_bytes @benchmark def _(): for _ in range(ITERATIONS): _ulid_now_bytes() def test_ulid_hex(benchmark: BenchmarkFixture) -> None: _ulid_hex = ulid_hex @benchmark def _(): for _ in range(ITERATIONS): _ulid_hex() def test_ulid_at_time(benchmark: BenchmarkFixture) -> None: _ulid_at_time = ulid_at_time @benchmark def _(): for _ in range(ITERATIONS): _ulid_at_time(1) def test_ulid_at_time_bytes(benchmark: BenchmarkFixture) -> None: _ulid_at_time_bytes = ulid_at_time_bytes @benchmark def _(): for _ in range(ITERATIONS): _ulid_at_time_bytes(1) def test_ulid_to_bytes(benchmark: BenchmarkFixture) -> None: _ulid_to_bytes = ulid_to_bytes sample = _SAMPLE_ULID_STR @benchmark def _(): for _ in range(ITERATIONS): _ulid_to_bytes(sample) def test_ulid_to_bytes_or_none_valid(benchmark: BenchmarkFixture) -> None: _ulid_to_bytes_or_none = ulid_to_bytes_or_none sample = _SAMPLE_ULID_STR @benchmark def _(): for _ in range(ITERATIONS): _ulid_to_bytes_or_none(sample) def test_ulid_to_bytes_or_none_invalid(benchmark: BenchmarkFixture) -> None: _ulid_to_bytes_or_none = ulid_to_bytes_or_none sample = "not a valid ulid" @benchmark def _(): for _ in range(ITERATIONS): _ulid_to_bytes_or_none(sample) def test_bytes_to_ulid(benchmark: BenchmarkFixture) -> None: _bytes_to_ulid = bytes_to_ulid sample = _SAMPLE_ULID_BYTES @benchmark def _(): for _ in range(ITERATIONS): _bytes_to_ulid(sample) def test_bytes_to_ulid_or_none_valid(benchmark: BenchmarkFixture) -> None: _bytes_to_ulid_or_none = bytes_to_ulid_or_none sample = _SAMPLE_ULID_BYTES @benchmark def _(): for _ in range(ITERATIONS): _bytes_to_ulid_or_none(sample) def test_bytes_to_ulid_or_none_invalid(benchmark: BenchmarkFixture) -> None: _bytes_to_ulid_or_none = bytes_to_ulid_or_none sample = b"too short" @benchmark def _(): for _ in range(ITERATIONS): _bytes_to_ulid_or_none(sample) def test_ulid_to_timestamp_str(benchmark: BenchmarkFixture) -> None: _ulid_to_timestamp = ulid_to_timestamp sample = _SAMPLE_ULID_STR @benchmark def _(): for _ in range(ITERATIONS): _ulid_to_timestamp(sample) def test_ulid_to_timestamp_bytes(benchmark: BenchmarkFixture) -> None: _ulid_to_timestamp = ulid_to_timestamp sample = _SAMPLE_ULID_BYTES @benchmark def _(): for _ in range(ITERATIONS): _ulid_to_timestamp(sample) Bluetooth-Devices-ulid-transform-f51d4f1/tests/conftest.py000066400000000000000000000000611520476015000237550ustar00rootroot00000000000000from tests.utils import impl __all__ = ["impl"] Bluetooth-Devices-ulid-transform-f51d4f1/tests/test_base32_decode.py000066400000000000000000000026551520476015000255640ustar00rootroot00000000000000"""Tests for Crockford base32 decoding edge cases (case + aliases).""" import pytest _REF_ULID = "01GTCKZT7K26YEVVW6AMQ3J0VT" _REF_BYTES = b"\x01\x86\x99?\xe8\xf3\x11\xbc\xed\xef\x86U.9\x03z" def test_lowercase_decodes_same_as_uppercase(impl): assert impl.ulid_to_bytes(_REF_ULID.lower()) == _REF_BYTES @pytest.mark.parametrize( "ulid", [ "01gtCKZT7K26YEVVW6AMQ3J0VT", "01GtCkZt7k26yEvVw6amq3j0vt", "01GTCKZT7K26YEVVW6AMQ3J0vt", ], ) def test_mixed_case_decodes(impl, ulid): assert impl.ulid_to_bytes(ulid) == _REF_BYTES @pytest.mark.parametrize( ("alias", "canon"), [("I", "1"), ("i", "1"), ("L", "1"), ("l", "1"), ("O", "0"), ("o", "0")], ) def test_crockford_aliases(impl, alias, canon): """I/L (any case) decode as 1, O (any case) decodes as 0.""" aliased = alias + _REF_ULID[1:] canonical = canon + _REF_ULID[1:] assert impl.ulid_to_bytes(aliased) == impl.ulid_to_bytes(canonical) @pytest.mark.parametrize("char", ["U", "u"]) def test_u_remains_excluded(impl, char): """Crockford excludes U from the alphabet entirely. Both implementations treat U the same (invalid -> 0xFF lookup), so decoding does not raise but the result is implementation-defined. The contract is that C and Python produce identical output. """ candidate = char + _REF_ULID[1:] # No exception, and length is preserved assert len(impl.ulid_to_bytes(candidate)) == 16 Bluetooth-Devices-ulid-transform-f51d4f1/tests/test_cython.py000066400000000000000000000011711520476015000244760ustar00rootroot00000000000000import os import pytest def _require_extension() -> bool: """Check if REQUIRE_EXTENSION or REQUIRE_CYTHON is truthy.""" for key in ("REQUIRE_EXTENSION", "REQUIRE_CYTHON"): if os.environ.get(key, "").lower() in ("1", "true", "yes"): return True return False def test_require_c_extension(): """Fail if REQUIRE_EXTENSION is set and the C extension module is not available.""" if not _require_extension(): pytest.skip("REQUIRE_EXTENSION is not truthy") import ulid_transform._ulid_impl as c_impl # noqa: PLC0415 assert repr(c_impl.ulid_now).startswith(" 1, O (any case) -> 0; both impls must agree.""" base = "01GTCKZT7K26YEVVW6AMQ3J0VT" aliased = alias_char + base[1:] assert _c_impl.ulid_to_bytes(aliased) == _py_impl.ulid_to_bytes(aliased) @pytestmark_parity def test_parity_ulid_at_time_timestamp_portion(): """First 10 chars (timestamp) of ulid_at_time must match across impls.""" for ts in (0.0, 1.0, 1677627631.2127638, 9999999999.999): assert _c_impl.ulid_at_time(ts)[:10] == _py_impl.ulid_at_time(ts)[:10] assert _c_impl.ulid_at_time_bytes(ts)[:6] == _py_impl.ulid_at_time_bytes(ts)[:6] @pytestmark_parity def test_parity_ulid_hex_format(): """ulid_hex output format (length, hex chars) matches across impls.""" for _ in range(10): for h in (_c_impl.ulid_hex(), _py_impl.ulid_hex()): assert len(h) == 32 assert all(ch in "0123456789abcdef" for ch in h) @pytestmark_parity def test_parity_ulid_to_timestamp_valid_string(): for s in ("01GTCKZT7K26YEVVW6AMQ3J0VT", "00000000000000000000000000"): assert _c_impl.ulid_to_timestamp(s) == _py_impl.ulid_to_timestamp(s) @pytestmark_parity def test_parity_or_none_valid_inputs(): s = "01GTCKZT7K26YEVVW6AMQ3J0VT" b = bytes(range(16)) assert _c_impl.ulid_to_bytes_or_none(s) == _py_impl.ulid_to_bytes_or_none(s) assert _c_impl.ulid_to_bytes_or_none(None) == _py_impl.ulid_to_bytes_or_none(None) assert _c_impl.bytes_to_ulid_or_none(b) == _py_impl.bytes_to_ulid_or_none(b) assert _c_impl.bytes_to_ulid_or_none(None) == _py_impl.bytes_to_ulid_or_none(None) @pytestmark_parity def test_parity_or_none_invalid_lengths(): """_or_none variants return None for length-invalid (but type-correct) input.""" assert _c_impl.ulid_to_bytes_or_none("short") == _py_impl.ulid_to_bytes_or_none( "short" ) assert _c_impl.bytes_to_ulid_or_none(b"short") == _py_impl.bytes_to_ulid_or_none( b"short" ) Bluetooth-Devices-ulid-transform-f51d4f1/tests/test_init.py000066400000000000000000000242341520476015000241420ustar00rootroot00000000000000import time from types import ModuleType import pytest def test_ulid_now(impl): ulid_str = impl.ulid_now() assert len(ulid_str) == 26 timestamp = impl.ulid_to_timestamp(ulid_str) assert timestamp == pytest.approx(int(time.time() * 1000), 1) def test_ulid_now_bytes(impl): ulid_bytes = impl.ulid_now_bytes() assert len(ulid_bytes) == 16 timestamp = impl.ulid_to_timestamp(ulid_bytes) assert timestamp == pytest.approx(int(time.time() * 1000), 1) def test_ulid_hex(impl): ulid_str = impl.ulid_hex() assert len(ulid_str) == 32 def test_ulid_to_bytes(impl): assert ( impl.ulid_to_bytes("01GTCKZT7K26YEVVW6AMQ3J0VT") == b"\x01\x86\x99?\xe8\xf3\x11\xbc\xed\xef\x86U.9\x03z" ) def test_ulid_to_bytes_overflow(impl): with pytest.raises(ValueError, match="26 character"): impl.ulid_to_bytes("01GTCKZT7K26YEVVW6AMQ3J0VT0000") def test_ulid_to_bytes_under_low(impl): with pytest.raises(ValueError, match="26 character"): impl.ulid_to_bytes("01") def test_bytes_to_ulid(impl): assert ( impl.bytes_to_ulid(b"\x01\x86\x99?\xe8\xf3\x11\xbc\xed\xef\x86U.9\x03z") == "01GTCKZT7K26YEVVW6AMQ3J0VT" ) def test_ulid_to_bytes_invalid_length(impl): with pytest.raises(ValueError, match="26 character"): assert impl.ulid_to_bytes("aa") def test_bytes_to_ulid_invalid_length(impl): with pytest.raises(ValueError, match="aa"): assert impl.bytes_to_ulid(b"aa") def test_ulid_to_bytes_2(impl): assert ( impl.ulid_to_bytes("00000000AC00GW0X476W5TVBFE") == b"\x00\x00\x00\x00\x01L\x00!\xc0t\x877\x0b\xad\xad\xee" ) def test_timestamp_string(impl): ulid = impl.ulid_at_time(1677627631.2127638) assert ulid[:10] == "01GTD6C9KC" def test_timestamp_bytes(impl): ulid = impl.ulid_at_time_bytes(1677627631.2127638) # prefix verified with another ulid implementation (valohai/ulid2) assert ulid[:6] == b"\x01\x86\x9af&l" @pytest.mark.parametrize("gen", ["ulid_at_time", "ulid_at_time_bytes"]) def test_timestamp(impl, gen): gen = getattr(impl, gen) now = time.time() ulid = gen(now) # ULIDs store time to 3 decimal places compared to python timestamps assert impl.ulid_to_timestamp(ulid) == int(now * 1000) @pytest.mark.parametrize("gen", ["ulid_at_time", "ulid_at_time_bytes"]) def test_timestamp_fixed(impl, gen): gen = getattr(impl, gen) now = 1677627631.2127638 ulid = gen(now) # ULIDs store time to 3 decimal places compared to python timestamps assert impl.ulid_to_timestamp(ulid) == int(now * 1000) @pytest.mark.parametrize("gen", ["ulid_at_time", "ulid_at_time_bytes"]) def test_ulid_at_time_nan_raises(impl, gen): with pytest.raises(ValueError, match="NaN"): getattr(impl, gen)(float("nan")) @pytest.mark.parametrize("gen", ["ulid_at_time", "ulid_at_time_bytes"]) @pytest.mark.parametrize("value", [float("inf"), float("-inf")]) def test_ulid_at_time_infinity_raises(impl, gen, value): with pytest.raises(OverflowError, match="infinity"): getattr(impl, gen)(value) @pytest.mark.parametrize("gen", ["ulid_at_time", "ulid_at_time_bytes"]) @pytest.mark.parametrize( "value", [ -1.0, # Timestamps in (-0.001, 0) seconds: ts * 1000 lands in (-1, 0), which # int() truncates toward zero. The C extension checks the sign of the # millisecond value *before* the cast, so it rejects these as negative; # the Python impl must do the same instead of silently producing ts=0. -0.0009, -0.0005, -0.0001, -1e-9, ], ) def test_ulid_at_time_negative_raises(impl, gen, value): with pytest.raises(OverflowError, match="negative"): getattr(impl, gen)(value) @pytest.mark.parametrize("gen", ["ulid_at_time", "ulid_at_time_bytes"]) def test_ulid_at_time_too_large_raises(impl, gen): # 2^48 ms / 1000 = 281474976710.656 seconds → ts >= that overflows. with pytest.raises(OverflowError, match="int too big"): getattr(impl, gen)(1e18) @pytest.mark.parametrize("gen", ["ulid_at_time", "ulid_at_time_bytes"]) def test_ulid_at_time_max_valid(impl, gen): # Exclusive upper bound is 2^48 ms; the largest valid second-timestamp # is just under (2^48 - 1) / 1000. Stay safely below to avoid float # rounding edge cases. ts = 281474976710.000 ulid = getattr(impl, gen)(ts) assert ulid is not None @pytest.mark.parametrize("gen", ["ulid_at_time", "ulid_at_time_bytes"]) def test_ulid_at_time_wrong_type_raises(impl, gen): with pytest.raises(TypeError): getattr(impl, gen)("not-a-float") def test_non_uppercase_b32_data(impl): assert len(impl.ulid_to_bytes("not_uppercase_b32_data_:::")) == 16 def test_ulid_to_bytes_or_none(impl): """Test ulid_to_bytes_or_none.""" assert ( impl.ulid_to_bytes_or_none("01EYQZJXZ5Z1Z1Z1Z1Z1Z1Z1Z1") == b"\x01w\xaf\xf9w\xe5\xf8~\x1f\x87\xe1\xf8~\x1f\x87\xe1" ) assert impl.ulid_to_bytes_or_none("invalid") is None assert impl.ulid_to_bytes_or_none(None) is None @pytest.mark.parametrize( "value", [ 123, 3.14, [], (), {}, b"\x01w\xaf\xf9w\xe5\xf8~\x1f\x87\xe1\xf8~\x1f\x87\xe1", # bytes, not str object(), ], ) def test_ulid_to_bytes_or_none_invalid_type(impl, value): """Non-str inputs return None instead of raising (parity with C impl).""" assert impl.ulid_to_bytes_or_none(value) is None def test_ulid_to_bytes_or_none_non_ascii(impl): """Non-ASCII 26-char strings return None instead of raising.""" assert impl.ulid_to_bytes_or_none("é" * 26) is None def test_entropy(impl): """Verify generated ULIDs have non-trivial entropy in the random part.""" samples = [impl.ulid_at_time_bytes(1677627631.0)[6:] for _ in range(200)] assert len(set(samples)) == len(samples), ( "Generated ULIDs should have unique entropy" ) for pos in range(10): distinct_bytes = {s[pos] for s in samples} assert len(distinct_bytes) > 1, f"Byte position {pos} has no variety" for pos in range(20): byte_idx, shift = divmod(pos, 2) distinct_nibbles = {(s[byte_idx] >> (4 * shift)) & 0xF for s in samples} assert len(distinct_nibbles) > 1, f"Nibble position {pos} has no variety" def test_ulid_to_timestamp_wrong_length_bytes(impl: ModuleType) -> None: """Bytes input with the wrong length must raise ValueError, not silently truncate.""" with pytest.raises(ValueError, match="16 bytes"): impl.ulid_to_timestamp(b"short") with pytest.raises(ValueError, match="16 bytes"): impl.ulid_to_timestamp(b"") with pytest.raises(ValueError, match="16 bytes"): impl.ulid_to_timestamp(b"a" * 50) def test_ulid_to_timestamp_wrong_length_string(impl: ModuleType) -> None: """String input with the wrong length must raise ValueError.""" with pytest.raises(ValueError, match="26 character"): impl.ulid_to_timestamp("too short") with pytest.raises(ValueError, match="26 character"): impl.ulid_to_timestamp("01GTCKZT7K26YEVVW6AMQ3J0VT0000") def test_ulid_to_timestamp_wrong_type(impl: ModuleType) -> None: """Non-str / non-bytes input must raise TypeError.""" with pytest.raises(TypeError): impl.ulid_to_timestamp(123) with pytest.raises(TypeError): impl.ulid_to_timestamp(None) with pytest.raises(TypeError): impl.ulid_to_timestamp( bytearray(b"\x01\x86\x99?\xe8\xf3\x11\xbc\xed\xef\x86U.9\x03z") ) @pytest.mark.parametrize( "value", [ b"01GTCKZT7K26YEVVW6AMQ3J0VT", bytearray(b"01GTCKZT7K26YEVVW6AMQ3J0VT"), memoryview(b"01GTCKZT7K26YEVVW6AMQ3J0VT"), 123, None, 3.14, ], ) def test_ulid_to_bytes_wrong_type(impl, value): """Non-str input must raise TypeError (parity with C impl).""" with pytest.raises(TypeError): impl.ulid_to_bytes(value) @pytest.mark.parametrize( "value", [ bytearray(16), memoryview(b"x" * 16), "01GTCKZT7K26YEVVW6AMQ3J0VT", 123, None, ], ) def test_bytes_to_ulid_wrong_type(impl, value): """Non-bytes input must raise TypeError (parity with C impl).""" with pytest.raises(TypeError): impl.bytes_to_ulid(value) def test_bytes_to_ulid_or_none(impl): """Test bytes_to_ulid_or_none.""" assert ( impl.bytes_to_ulid_or_none( b"\x01w\xaf\xf9w\xe5\xf8~\x1f\x87\xe1\xf8~\x1f\x87\xe1" ) == "01EYQZJXZ5Z1Z1Z1Z1Z1Z1Z1Z1" ) assert impl.bytes_to_ulid_or_none(b"invalid") is None assert impl.bytes_to_ulid_or_none(None) is None @pytest.mark.parametrize( "value", [ 123, 3.14, [], (), {}, "01EYQZJXZ5Z1Z1Z1Z1Z1Z1Z1Z1", # str, not bytes object(), ], ) def test_bytes_to_ulid_or_none_invalid_type(impl, value): """Non-bytes inputs return None instead of raising (parity with C impl).""" assert impl.bytes_to_ulid_or_none(value) is None @pytest.mark.parametrize( "value", ["", "short", "01GTCKZT7K26YEVVW6AMQ3J0VT ", "01GTCKZT7K26YEVVW6AMQ3J0VT0000"], ) def test_ulid_to_bytes_error_message_reprs_value(impl: ModuleType, value: str) -> None: """The wrong-length ValueError message repr-quotes the offending value. Both impls must embed repr(value) (the C extension uses PyErr_Format's %R), so a trailing space or empty string is unambiguous in the message and the two implementations stay byte-for-byte aligned. """ with pytest.raises(ValueError, match="26 character") as exc_info: impl.ulid_to_bytes(value) assert repr(value) in str(exc_info.value) @pytest.mark.parametrize( "value", ["", "short", "01GTCKZT7K26YEVVW6AMQ3J0VT ", "01GTCKZT7K26YEVVW6AMQ3J0VT0000"], ) def test_ulid_to_timestamp_error_message_reprs_value( impl: ModuleType, value: str ) -> None: """ulid_to_timestamp's string path inherits the repr-quoted ValueError. It delegates to ulid_to_bytes, so the wrong-length message must embed repr(value) on both impls just like the ulid_to_bytes path. """ with pytest.raises(ValueError, match="26 character") as exc_info: impl.ulid_to_timestamp(value) assert repr(value) in str(exc_info.value) Bluetooth-Devices-ulid-transform-f51d4f1/tests/test_parity_fuzz.py000066400000000000000000000225211520476015000255620ustar00rootroot00000000000000"""Generative C-vs-Python parity fuzzing. Complements the enumerated grid in ``test_parity_matrix.py``: instead of a fixed table of hand-picked cases, this seeds a PRNG and feeds thousands of random valid and malformed inputs to both implementations, asserting identical observable behavior — same return value, or same exception type. It locks in the parity achieved across the #206-#212 / #219 fixes and guards against future regressions in the base32 codec or the type-checking front doors. Scope is deliberately the *deterministic* surface: ``ulid_to_bytes``, ``bytes_to_ulid``, ``ulid_to_timestamp``, and their ``*_or_none`` counterparts. The ``ulid_at_time*`` family stays out of scope: its random 80-bit tail makes full-output comparison non-deterministic, and its numeric-domain corners (NaN/inf/overflow/str-reject — #218/#219) are pinned in the matrix instead. Buffer-protocol inputs (``bytearray``/``memoryview``/``list``) to the encode/decode front doors were once excluded under #210, when Python's buffer tolerance diverged from the C extension's strict type check. That divergence is resolved (#210 closed) — both impls now reject them identically at the strict ``str``/``bytes`` gate — so they are fuzzed here in ``test_wrong_type_parity``. Seeds are fixed so CI is fully reproducible: a failure always replays from the ``seed`` printed in the assertion message. """ from __future__ import annotations import random from typing import TYPE_CHECKING import pytest import ulid_transform._py_ulid_impl as py_impl if TYPE_CHECKING: from collections.abc import Callable try: import ulid_transform._ulid_impl as c_impl except ImportError: c_impl = None pytestmark = pytest.mark.skipif( c_impl is None, reason="C extension unavailable; parity fuzz is C-vs-Py only" ) # Crockford base32 alphabet (canonical, uppercase) used to encode ULIDs. _ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ" # Decode-time aliases both impls accept: I/i/L/l -> 1, O/o -> 0. _DECODE_ALIASES = "ILOilo" # Arbitrary printable ASCII, including chars that are *invalid* base32 (U, !, ...). _PRINTABLE = ( "0123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "!@#$%^&*()_+-=[]{}|;:,.<>?/~` " ) # Multiple fixed seeds widen coverage while keeping every run reproducible. _SEEDS = [1, 7, 42, 1234, 98765] _ITERATIONS = 4000 def _run(fn: Callable[..., object], *args: object) -> tuple[str, object]: """Reduce a call to a comparable ``(kind, payload)`` pair. ``("ok", value)`` on success, ``("exc", ExceptionTypeName)`` on failure. Exception *messages* are intentionally ignored — only the type matters for parity here (the matrix already pins message text where it counts). """ try: return ("ok", fn(*args)) except Exception as exc: # noqa: BLE001 - any divergence in raised type is a finding return ("exc", type(exc).__name__) def _assert_parity(fn_name: str, value: object, seed: int) -> None: py_outcome = _run(getattr(py_impl, fn_name), value) c_outcome = _run(getattr(c_impl, fn_name), value) assert py_outcome == c_outcome, ( f"parity mismatch (seed={seed}) for {fn_name}({value!r}): " f"py={py_outcome!r} c={c_outcome!r}" ) def _random_valid_ulid_str(rng: random.Random) -> str: """A 26-char string over the canonical alphabet, optionally case-folded.""" s = "".join(rng.choice(_ALPHABET) for _ in range(26)) roll = rng.random() if roll < 0.3: return s.lower() if roll < 0.4: # Mixed case + decode aliases that both impls fold to 0/1. chars = list(s) for _ in range(rng.randint(1, 4)): chars[rng.randint(0, 25)] = rng.choice(_DECODE_ALIASES) return "".join(chars) return s # --------------------------------------------------------------------------- # # Decode path: valid alphabet -> identical bytes / timestamp. # --------------------------------------------------------------------------- # @pytest.mark.parametrize("seed", _SEEDS) def test_decode_valid_alphabet_parity(seed): """Random alphabet ULIDs decode to byte-identical output in both impls.""" rng = random.Random(seed) for _ in range(_ITERATIONS): s = _random_valid_ulid_str(rng) _assert_parity("ulid_to_bytes", s, seed) _assert_parity("ulid_to_timestamp", s, seed) _assert_parity("ulid_to_bytes_or_none", s, seed) # --------------------------------------------------------------------------- # # Decode path: arbitrary ASCII (mostly invalid base32) -> same outcome. # --------------------------------------------------------------------------- # @pytest.mark.parametrize("seed", _SEEDS) def test_decode_arbitrary_ascii_parity(seed): """26-char strings of arbitrary ASCII: same value, or same exception type.""" rng = random.Random(seed + 1) for _ in range(_ITERATIONS): s = "".join(rng.choice(_PRINTABLE) for _ in range(26)) _assert_parity("ulid_to_bytes", s, seed) _assert_parity("ulid_to_bytes_or_none", s, seed) _assert_parity("ulid_to_timestamp", s, seed) @pytest.mark.parametrize("seed", _SEEDS) def test_decode_arbitrary_length_parity(seed): """Wrong-length strings (incl. empty and >26) must diverge identically.""" rng = random.Random(seed + 2) for _ in range(_ITERATIONS): length = rng.choice([0, 1, 13, 25, 26, 27, 32]) s = "".join(rng.choice(_PRINTABLE) for _ in range(length)) _assert_parity("ulid_to_bytes", s, seed) _assert_parity("ulid_to_bytes_or_none", s, seed) _assert_parity("ulid_to_timestamp", s, seed) @pytest.mark.parametrize("seed", _SEEDS) def test_decode_non_ascii_parity(seed): """26-char strings with non-ASCII codepoints: same outcome in both impls.""" rng = random.Random(seed + 3) pool = "éàÿĀ中µ\U0001f600" for _ in range(_ITERATIONS): s = "".join(rng.choice(_ALPHABET + pool) for _ in range(26)) _assert_parity("ulid_to_bytes", s, seed) _assert_parity("ulid_to_bytes_or_none", s, seed) # --------------------------------------------------------------------------- # # Encode path: random bytes -> same string / timestamp / outcome. # --------------------------------------------------------------------------- # @pytest.mark.parametrize("seed", _SEEDS) def test_encode_random_bytes_parity(seed): """Random byte strings of assorted lengths: same value or same exception.""" rng = random.Random(seed + 4) for _ in range(_ITERATIONS): length = rng.choice([0, 1, 8, 15, 16, 16, 16, 17, 32]) b = bytes(rng.getrandbits(8) for _ in range(length)) _assert_parity("bytes_to_ulid", b, seed) _assert_parity("bytes_to_ulid_or_none", b, seed) _assert_parity("ulid_to_timestamp", b, seed) # --------------------------------------------------------------------------- # # Round trip: random 16 bytes -> ulid -> bytes, identical across impls. # --------------------------------------------------------------------------- # @pytest.mark.parametrize("seed", _SEEDS) def test_round_trip_random_bytes(seed): """16 random bytes survive encode->decode, identically in C and Python.""" rng = random.Random(seed + 5) for _ in range(_ITERATIONS): raw = bytes(rng.getrandbits(8) for _ in range(16)) c_str = c_impl.bytes_to_ulid(raw) py_str = py_impl.bytes_to_ulid(raw) assert c_str == py_str, ( f"encode mismatch (seed={seed}) for {raw!r}: c={c_str!r} py={py_str!r}" ) # Round-trips back to the original bytes in both impls. assert c_impl.ulid_to_bytes(c_str) == raw assert py_impl.ulid_to_bytes(py_str) == raw # --------------------------------------------------------------------------- # # Type front door: random non-str/bytes inputs -> same exception type / None. # Includes buffer-protocol types (bytearray/memoryview/list): #210 is closed and # both impls now reject them identically at the strict str/bytes type gate. # --------------------------------------------------------------------------- # def _random_buffer_value(rng: random.Random) -> object: """A buffer-protocol value of assorted length and random content. ``bytearray``/``memoryview``/``list`` all expose a buffer but are neither ``str`` nor ``bytes``; both impls must reject them at the type gate regardless of length or content. """ length = rng.choice([0, 1, 15, 16, 17, 26, 32]) raw = bytes(rng.getrandbits(8) for _ in range(length)) kind = rng.choice(("bytearray", "memoryview", "list")) if kind == "bytearray": return bytearray(raw) if kind == "memoryview": return memoryview(raw) return list(raw) @pytest.mark.parametrize("seed", _SEEDS) def test_wrong_type_parity(seed): """Non-str/bytes inputs must hit the same type-check verdict in both impls. Covers plain scalars and buffer-protocol types (``bytearray``/ ``memoryview``/``list``) of assorted lengths and contents — see #210. """ rng = random.Random(seed + 6) scalars = [None, 123, 12.5, {"x": 1}, object(), True, frozenset()] for _ in range(_ITERATIONS): value = _random_buffer_value(rng) if rng.random() < 0.5 else rng.choice(scalars) for fn in ( "ulid_to_bytes", "bytes_to_ulid", "ulid_to_timestamp", "ulid_to_bytes_or_none", "bytes_to_ulid_or_none", ): _assert_parity(fn, value, seed) Bluetooth-Devices-ulid-transform-f51d4f1/tests/test_parity_matrix.py000066400000000000000000000277061520476015000261020ustar00rootroot00000000000000"""Table-driven C-vs-Python behavioral parity matrix. Diff-feeds a fixed grid of valid and invalid inputs to both implementations and asserts identical observable behavior — same return value, or same exception type. Known divergences are marked ``xfail(strict=False)`` with the issue or PR that tracks them; when those land, the corresponding case flips XPASS and the marker can be removed. Companion to ``test_impl_sync.py`` (signature/docstring parity). Together they form the cross-impl contract net referenced in issue #210. """ from __future__ import annotations from typing import TYPE_CHECKING import pytest import ulid_transform._py_ulid_impl as py_impl if TYPE_CHECKING: from collections.abc import Callable try: import ulid_transform._ulid_impl as c_impl except ImportError: c_impl = None pytestmark = pytest.mark.skipif( c_impl is None, reason="C extension unavailable; parity matrix is C-vs-Py only" ) _VALID_ULID_STR = "01GTCKZT7K26YEVVW6AMQ3J0VT" _VALID_ULID_BYTES = b"\x01\x86\x99\x3f\xe8\xf3\x11\xbd\xfb\xd6\x70\x55\x6c\x18\xc0\x6b" _VALID_ULID_STR_LOWER = "01gtckzt7k26yevvw6amq3j0vt" def _run(fn: Callable[..., object], *args: object) -> tuple[str, object]: try: return ("ok", fn(*args)) except Exception as exc: # noqa: BLE001 return ("exc", type(exc).__name__) def _assert_parity(fn_name: str, *args: object) -> None: py_fn = getattr(py_impl, fn_name) c_fn = getattr(c_impl, fn_name) py_outcome = _run(py_fn, *args) c_outcome = _run(c_fn, *args) assert py_outcome == c_outcome, ( f"parity mismatch for {fn_name}({', '.join(repr(a) for a in args)}): " f"py={py_outcome!r} c={c_outcome!r}" ) # --------------------------------------------------------------------------- # # bytes_to_ulid # --------------------------------------------------------------------------- # _BYTES_TO_ULID_CASES = [ pytest.param(b"\x00" * 16, id="zero-16-bytes"), pytest.param(_VALID_ULID_BYTES, id="valid-bytes"), pytest.param(b"", id="empty-bytes"), pytest.param(b"short", id="short-bytes"), pytest.param(b"\x00" * 17, id="too-long-bytes"), pytest.param(123, id="int"), pytest.param(None, id="none"), pytest.param("a" * 16, id="str-16"), pytest.param([0] * 16, id="list"), pytest.param(bytearray(16), id="bytearray-16"), pytest.param(memoryview(b"\x00" * 16), id="memoryview-16"), ] @pytest.mark.parametrize("value", _BYTES_TO_ULID_CASES) def test_bytes_to_ulid_parity(value): _assert_parity("bytes_to_ulid", value) # --------------------------------------------------------------------------- # # ulid_to_bytes # --------------------------------------------------------------------------- # _ULID_TO_BYTES_CASES = [ pytest.param(_VALID_ULID_STR, id="valid-upper"), pytest.param(_VALID_ULID_STR_LOWER, id="valid-lower"), pytest.param("", id="empty-str"), pytest.param("short", id="short-str"), pytest.param("X" * 27, id="too-long-str"), # 26 codepoints but non-ASCII: C measures UTF-8 byte length (> 26) and # raises ValueError; Python must too (not UnicodeEncodeError from .encode). pytest.param("é" * 26, id="non-ascii-26"), # 13 two-byte codepoints = exactly 26 UTF-8 bytes: the byte-length check # alone passes, so C must also verify ASCII or it decodes garbage instead # of raising ValueError like Python. pytest.param("é" * 13, id="non-ascii-13cp-26bytes"), pytest.param(123, id="int"), pytest.param(None, id="none"), pytest.param([0] * 26, id="list"), pytest.param(_VALID_ULID_BYTES, id="ulid-as-bytes"), pytest.param(bytearray(_VALID_ULID_STR.encode()), id="ulid-as-bytearray"), ] @pytest.mark.parametrize("value", _ULID_TO_BYTES_CASES) def test_ulid_to_bytes_parity(value): _assert_parity("ulid_to_bytes", value) # --------------------------------------------------------------------------- # # ulid_to_timestamp # --------------------------------------------------------------------------- # _ULID_TO_TIMESTAMP_CASES = [ pytest.param(_VALID_ULID_STR, id="valid-str"), pytest.param(_VALID_ULID_BYTES, id="valid-bytes"), pytest.param(b"\x00" * 16, id="zero-bytes"), pytest.param("short", id="short-str"), # 13 two-byte codepoints = 26 UTF-8 bytes: the C string path decodes inline, # so it must reject non-ASCII or return a garbage timestamp instead of the # ValueError Python raises. pytest.param("é" * 13, id="non-ascii-13cp-26bytes"), pytest.param(123, id="int"), pytest.param(None, id="none"), pytest.param(b"short", id="short-bytes"), pytest.param(b"\x00" * 17, id="too-long-bytes"), pytest.param(bytearray(16), id="bytearray-16"), pytest.param(memoryview(b"\x00" * 16), id="memoryview-16"), ] @pytest.mark.parametrize("value", _ULID_TO_TIMESTAMP_CASES) def test_ulid_to_timestamp_parity(value): _assert_parity("ulid_to_timestamp", value) # --------------------------------------------------------------------------- # # ulid_to_bytes_or_none # --------------------------------------------------------------------------- # _ULID_TO_BYTES_OR_NONE_CASES = [ pytest.param(None, id="none"), pytest.param(_VALID_ULID_STR, id="valid-str"), pytest.param(_VALID_ULID_STR_LOWER, id="valid-lower"), pytest.param("", id="empty"), pytest.param("short", id="short"), pytest.param("é" * 26, id="non-ascii-26"), # 13 two-byte codepoints = 26 UTF-8 bytes: C must reject non-ASCII (return # None) rather than decode garbage bytes. pytest.param("é" * 13, id="non-ascii-13cp-26bytes"), pytest.param(123, id="int"), pytest.param(b"x" * 26, id="bytes-26"), pytest.param([0] * 26, id="list-26"), pytest.param(object(), id="object"), ] @pytest.mark.parametrize("value", _ULID_TO_BYTES_OR_NONE_CASES) def test_ulid_to_bytes_or_none_parity(value): _assert_parity("ulid_to_bytes_or_none", value) # --------------------------------------------------------------------------- # # bytes_to_ulid_or_none # --------------------------------------------------------------------------- # _BYTES_TO_ULID_OR_NONE_CASES = [ pytest.param(None, id="none"), pytest.param(_VALID_ULID_BYTES, id="valid-bytes"), pytest.param(b"\x00" * 16, id="zero-bytes"), pytest.param(b"short", id="short"), pytest.param(b"", id="empty"), pytest.param(b"\x00" * 17, id="too-long"), pytest.param(123, id="int"), pytest.param("x" * 16, id="str-16"), pytest.param(bytearray(16), id="bytearray-16"), pytest.param([0] * 16, id="list-16"), pytest.param(object(), id="object"), ] @pytest.mark.parametrize("value", _BYTES_TO_ULID_OR_NONE_CASES) def test_bytes_to_ulid_or_none_parity(value): _assert_parity("bytes_to_ulid_or_none", value) # --------------------------------------------------------------------------- # # ulid_at_time / ulid_at_time_bytes — non-deterministic; only shape + error # parity is asserted. # --------------------------------------------------------------------------- # _AT_TIME_VALID = [0.0, 1.0, 1_700_000_000.0] _AT_TIME_INVALID = [ pytest.param(None, id="none"), pytest.param([0], id="list"), pytest.param({}, id="dict"), pytest.param("not-a-float", id="str"), pytest.param(-1.0, id="negative"), # ts * 1000 in (-1, 0): int() truncates toward zero, so the Python impl # must reject the negative sign before the cast to match the C extension. pytest.param(-0.0005, id="negative-truncates-to-zero"), pytest.param(1e18, id="huge"), pytest.param(float("nan"), id="nan"), pytest.param(float("inf"), id="inf"), pytest.param(float("-inf"), id="neg-inf"), ] @pytest.mark.parametrize("timestamp", _AT_TIME_VALID) def test_ulid_at_time_valid_shape(timestamp): """Valid timestamps: both impls return a 26-char str (content is random).""" py_val = py_impl.ulid_at_time(timestamp) c_val = c_impl.ulid_at_time(timestamp) assert isinstance(py_val, str) assert len(py_val) == 26 assert isinstance(c_val, str) assert len(c_val) == 26 @pytest.mark.parametrize("timestamp", _AT_TIME_VALID) def test_ulid_at_time_bytes_valid_shape(timestamp): py_val = py_impl.ulid_at_time_bytes(timestamp) c_val = c_impl.ulid_at_time_bytes(timestamp) assert isinstance(py_val, bytes) assert len(py_val) == 16 assert isinstance(c_val, bytes) assert len(c_val) == 16 @pytest.mark.parametrize("timestamp", _AT_TIME_INVALID) def test_ulid_at_time_invalid_parity(timestamp): """Both impls raise the same exception type on invalid input.""" py_outcome = _run(py_impl.ulid_at_time, timestamp) c_outcome = _run(c_impl.ulid_at_time, timestamp) # Both must have errored — if either returned successfully, the case # doesn't belong in the invalid grid. Filtered separately so the failure # message is clearer than `_assert_parity` would give. assert py_outcome[0] == "exc", ( f"expected py impl to raise; got py={py_outcome!r} c={c_outcome!r}" ) assert c_outcome[0] == "exc", ( f"expected c impl to raise; got py={py_outcome!r} c={c_outcome!r}" ) assert py_outcome == c_outcome, ( f"exception type mismatch: py={py_outcome!r} c={c_outcome!r}" ) @pytest.mark.parametrize("timestamp", _AT_TIME_INVALID) def test_ulid_at_time_bytes_invalid_parity(timestamp): py_outcome = _run(py_impl.ulid_at_time_bytes, timestamp) c_outcome = _run(c_impl.ulid_at_time_bytes, timestamp) assert py_outcome[0] == "exc", ( f"expected py impl to raise; got py={py_outcome!r} c={c_outcome!r}" ) assert c_outcome[0] == "exc", ( f"expected c impl to raise; got py={py_outcome!r} c={c_outcome!r}" ) assert py_outcome == c_outcome, ( f"exception type mismatch: py={py_outcome!r} c={c_outcome!r}" ) # --------------------------------------------------------------------------- # # Generators — shape-only (output is random/time-dependent). # --------------------------------------------------------------------------- # def test_ulid_now_shape(): py_val = py_impl.ulid_now() c_val = c_impl.ulid_now() assert isinstance(py_val, str) assert len(py_val) == 26 assert isinstance(c_val, str) assert len(c_val) == 26 def test_ulid_now_bytes_shape(): py_val = py_impl.ulid_now_bytes() c_val = c_impl.ulid_now_bytes() assert isinstance(py_val, bytes) assert len(py_val) == 16 assert isinstance(c_val, bytes) assert len(c_val) == 16 def test_ulid_hex_shape(): py_val = py_impl.ulid_hex() c_val = c_impl.ulid_hex() assert isinstance(py_val, str) assert len(py_val) == 32 assert isinstance(c_val, str) assert len(c_val) == 32 # Must parse as hex int(py_val, 16) int(c_val, 16) # --------------------------------------------------------------------------- # # Cross-impl round-trip parity for ulid_to_bytes ↔ bytes_to_ulid. # --------------------------------------------------------------------------- # # Sample of ULID strings spanning the alphabet — mixed-case where allowed. _ROUND_TRIP_DECODE_SAMPLES = [ pytest.param("00000000000000000000000000", id="all-zero"), pytest.param("7ZZZZZZZZZZZZZZZZZZZZZZZZZ", id="all-z-upper"), pytest.param(_VALID_ULID_STR, id="valid-upper"), pytest.param(_VALID_ULID_STR_LOWER, id="valid-lower"), pytest.param("01GTCKZT7K26yevvw6amq3j0vt", id="valid-mixed"), ] @pytest.mark.parametrize("ulid_str", _ROUND_TRIP_DECODE_SAMPLES) def test_ulid_to_bytes_byte_equal(ulid_str): """C and Py decode to identical bytes — no silent divergence.""" assert py_impl.ulid_to_bytes(ulid_str) == c_impl.ulid_to_bytes(ulid_str) # Encode path takes canonical uppercase bytes only — no PR #209 dependency. _ROUND_TRIP_ENCODE_SAMPLES = [ "00000000000000000000000000", "7ZZZZZZZZZZZZZZZZZZZZZZZZZ", _VALID_ULID_STR, ] @pytest.mark.parametrize("ulid_str", _ROUND_TRIP_ENCODE_SAMPLES) def test_bytes_to_ulid_byte_equal(ulid_str): """Encode the canonical-uppercase bytes; both impls produce the same string.""" raw = py_impl.ulid_to_bytes(ulid_str) assert py_impl.bytes_to_ulid(raw) == c_impl.bytes_to_ulid(raw) Bluetooth-Devices-ulid-transform-f51d4f1/tests/utils.py000066400000000000000000000014231520476015000232730ustar00rootroot00000000000000from __future__ import annotations import logging import pytest log = logging.getLogger(__name__) def _get_available_implementations(): import ulid_transform._py_ulid_impl as ulid_transform_py # noqa: PLC0415 yield ("python", ulid_transform_py) try: import ulid_transform._ulid_impl as ulid_transform_c # noqa: PLC0415 yield ("c", ulid_transform_c) except ImportError: log.warning("Failed to import C extension", exc_info=True) _impls = dict(_get_available_implementations()) impl_names, impl_modules = zip(*_impls.items(), strict=False) @pytest.fixture(params=impl_modules, ids=impl_names) def impl(request): """Fixture that cycles through all available implementations of the ulid_transform module.""" return request.param Bluetooth-Devices-ulid-transform-f51d4f1/uv.lock000066400000000000000000000002071520476015000217220ustar00rootroot00000000000000version = 1 revision = 3 requires-python = ">=3.11" [[package]] name = "ulid-transform" version = "2.2.4" source = { editable = "." }