diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 44ff0e1c..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,303 +0,0 @@ -version: 2.1 - -# heavily inspired by https://raw.githubusercontent.com/pinax/pinax-wiki/6bd2a99ab6f702e300d708532a6d1d9aa638b9f8/.circleci/config.yml - -common: &common - working_directory: ~/repo - steps: - - checkout - - run: - name: merge pull request base - command: ./.circleci/merge_pr.sh - - run: - name: merge pull request base (2nd try) - command: ./.circleci/merge_pr.sh - when: on_fail - - run: - name: merge pull request base (3rd try) - command: ./.circleci/merge_pr.sh - when: on_fail - - restore_cache: - keys: - - cache-v1-{{ arch }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }} - - run: - name: install dependencies - command: | - python -m pip install --upgrade pip - python -m pip install tox - - run: - name: run tox - command: python -m tox run -r - - save_cache: - paths: - - .hypothesis - - .tox - - ~/.cache/pip - - ~/.local - key: cache-v1-{{ arch }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }} - -orbs: - win: circleci/windows@5.0.0 - -windows-wheel-steps: - windows-wheel-setup: &windows-wheel-setup - executor: - name: win/default - shell: bash.exe - working_directory: C:\Users\circleci\project\hexbytes - environment: - TOXENV: windows-wheel - restore-cache-step: &restore-cache-step - restore_cache: - keys: - - cache-v1-{{ arch }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }} - install-pyenv-step: &install-pyenv-step - run: - name: install pyenv - command: | - pip install pyenv-win --target $HOME/.pyenv - echo 'export PYENV="$HOME/.pyenv/pyenv-win/"' >> $BASH_ENV - echo 'export PYENV_ROOT="$HOME/.pyenv/pyenv-win/"' >> $BASH_ENV - echo 'export PYENV_USERPROFILE="$HOME/.pyenv/pyenv-win/"' >> $BASH_ENV - echo 'export PATH="$PATH:$HOME/.pyenv/pyenv-win/bin"' >> $BASH_ENV - echo 'export PATH="$PATH:$HOME/.pyenv/pyenv-win/shims"' >> $BASH_ENV - source $BASH_ENV - pyenv update - install-latest-python-step: &install-latest-python-step - run: - name: install latest python version and tox - command: | - LATEST_VERSION=$(pyenv install --list | grep -E "${MINOR_VERSION}\.[0-9]+$" | tail -1) - echo "installing python version $LATEST_VERSION" - pyenv install $LATEST_VERSION - pyenv global $LATEST_VERSION - python3 -m pip install --upgrade pip - python3 -m pip install tox - run-tox-step: &run-tox-step - run: - name: run tox - command: | - echo 'running tox with' $(python3 --version) - python3 -m tox run -r - save-cache-step: &save-cache-step - save_cache: - paths: - - .tox - key: cache-v1-{{ arch }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }} - -docs: &docs - working_directory: ~/repo - steps: - - checkout - - restore_cache: - keys: - - cache-v1-{{ arch }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }} - - run: - name: install dependencies - command: | - python -m pip install --upgrade pip - python -m pip install tox - - run: - name: install latexpdf dependencies - command: | - sudo apt-get update - sudo apt-get install latexmk tex-gyre texlive-fonts-extra texlive-xetex xindy - - run: - name: run tox - command: python -m tox run -r - - store_artifacts: - path: /home/circleci/repo/docs/_build - - save_cache: - paths: - - .tox - - ~/.cache/pip - - ~/.local - key: cache-v1-{{ arch }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }} - resource_class: xlarge - -jobs: - docs: - <<: *docs - docker: - - image: cimg/python:3.10 - environment: - TOXENV: docs - - py310-core: - <<: *common - docker: - - image: cimg/python:3.10 - environment: - TOXENV: py310-core - py311-core: - <<: *common - docker: - - image: cimg/python:3.11 - environment: - TOXENV: py311-core - py312-core: - <<: *common - docker: - - image: cimg/python:3.12 - environment: - TOXENV: py312-core - py313-core: - <<: *common - docker: - - image: cimg/python:3.13 - environment: - TOXENV: py313-core - py314-core: - <<: *common - docker: - - image: cimg/python:3.14 - environment: - TOXENV: py314-core - - py310-lint: - <<: *common - docker: - - image: cimg/python:3.10 - environment: - TOXENV: py310-lint - py311-lint: - <<: *common - docker: - - image: cimg/python:3.11 - environment: - TOXENV: py311-lint - py312-lint: - <<: *common - docker: - - image: cimg/python:3.12 - environment: - TOXENV: py312-lint - py313-lint: - <<: *common - docker: - - image: cimg/python:3.13 - environment: - TOXENV: py313-lint - py314-lint: - <<: *common - docker: - - image: cimg/python:3.14 - environment: - TOXENV: py314-lint - - py310-wheel: - <<: *common - docker: - - image: cimg/python:3.10 - environment: - TOXENV: py310-wheel - py311-wheel: - <<: *common - docker: - - image: cimg/python:3.11 - environment: - TOXENV: py311-wheel - py312-wheel: - <<: *common - docker: - - image: cimg/python:3.12 - environment: - TOXENV: py312-wheel - py313-wheel: - <<: *common - docker: - - image: cimg/python:3.13 - environment: - TOXENV: py313-wheel - py314-wheel: - <<: *common - docker: - - image: cimg/python:3.14 - environment: - TOXENV: py314-wheel - - py311-windows-wheel: - <<: *windows-wheel-setup - steps: - - checkout - - <<: *restore-cache-step - - <<: *install-pyenv-step - - run: - name: set minor version - command: echo "export MINOR_VERSION='3.11'" >> $BASH_ENV - - <<: *install-latest-python-step - - <<: *run-tox-step - - <<: *save-cache-step - py312-windows-wheel: - <<: *windows-wheel-setup - steps: - - checkout - - <<: *restore-cache-step - - <<: *install-pyenv-step - - run: - name: set minor version - command: echo "export MINOR_VERSION='3.12'" >> $BASH_ENV - - <<: *install-latest-python-step - - <<: *run-tox-step - - <<: *save-cache-step - py313-windows-wheel: - <<: *windows-wheel-setup - steps: - - checkout - - <<: *restore-cache-step - - <<: *install-pyenv-step - - run: - name: set minor version - command: echo "export MINOR_VERSION='3.13'" >> $BASH_ENV - - <<: *install-latest-python-step - - <<: *run-tox-step - - <<: *save-cache-step - py314-windows-wheel: - <<: *windows-wheel-setup - steps: - - checkout - - <<: *restore-cache-step - - <<: *install-pyenv-step - - run: - name: set minor version - command: echo "export MINOR_VERSION='3.14'" >> $BASH_ENV - - <<: *install-latest-python-step - - <<: *run-tox-step - - <<: *save-cache-step - -define: &all_jobs - - docs - - py310-core - - py311-core - - py312-core - - py313-core - - py314-core - - py310-lint - - py311-lint - - py312-lint - - py313-lint - - py314-lint - - py310-wheel - - py311-wheel - - py312-wheel - - py313-wheel - - py314-wheel - - py311-windows-wheel - - py312-windows-wheel - - py313-windows-wheel - - py314-windows-wheel - -workflows: - version: 2 - test: - jobs: *all_jobs - nightly: - triggers: - - schedule: - # Weekdays 12:00p UTC - cron: "0 12 * * 1,2,3,4,5" - filters: - branches: - only: - - main - jobs: *all_jobs diff --git a/.circleci/merge_pr.sh b/.circleci/merge_pr.sh deleted file mode 100755 index 91eb47ca..00000000 --- a/.circleci/merge_pr.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -if [[ -n "${CIRCLE_PR_NUMBER}" ]]; then - PR_INFO_URL=https://api.github.com/repos/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/pulls/$CIRCLE_PR_NUMBER - PR_BASE_BRANCH=$(curl -L "$PR_INFO_URL" | python -c 'import json, sys; obj = json.load(sys.stdin); sys.stdout.write(obj["base"]["ref"])') - git fetch origin +"$PR_BASE_BRANCH":circleci/pr-base - # We need these config values or git complains when creating the - # merge commit - git config --global user.name "Circle CI" - git config --global user.email "circleci@example.com" - git merge --no-edit circleci/pr-base -fi diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index d5380ef8..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: Bug Report -description: File a bug report -labels: ["bug"] -body: - - type: markdown - attributes: - value: "## What was wrong" - - type: textarea - id: what-happened - attributes: - label: What happened? - description: Also tell us what you expected to happen - validations: - required: true - - type: textarea - id: code-that-caused - attributes: - label: Code that produced the error - description: Formats to Python, no backticks needed - render: python - validations: - required: false - - type: textarea - id: error-output - attributes: - label: Full error output - description: Formats to shell, no backticks needed - render: shell - validations: - required: false - - type: markdown - attributes: - value: "## Potential Solutions" - - type: textarea - id: how-to-fix - attributes: - label: Fill this section in if you know how this could or should be fixed - description: Include any relevant examples or reference material - validations: - required: false - - type: input - id: lib-version - attributes: - label: hexbytes Version - description: Which version of hexbytes are you using? - placeholder: x.x.x - validations: - required: false - - type: input - id: py-version - attributes: - label: Python Version - description: Which version of Python are you using? - placeholder: x.x.x - validations: - required: false - - type: input - id: os - attributes: - label: Operating System - description: Which operating system are you using? - placeholder: macos/linux/win - validations: - required: false - - type: textarea - id: pip-freeze - attributes: - label: Output from `pip freeze` - description: Run `python -m pip freeze` and paste the output below - render: shell - validations: - required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 871725d9..00000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,5 +0,0 @@ -blank_issues_enabled: true -contact_links: - - name: Questions about using hexbytes? - url: https://discord.gg/GHryRvPB84 - about: You can ask and answer usage questions on the Ethereum Python Community Discord diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml deleted file mode 100644 index 041c8fd1..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ /dev/null @@ -1,11 +0,0 @@ -name: Feature Request -description: Request a new feature -labels: ["feature_request"] -body: - - type: textarea - id: feature-description - attributes: - label: What feature should we add? - description: Include any relevant examples or reference material - validations: - required: true diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index 9267b40e..00000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,16 +0,0 @@ -### What was wrong? - -Related to Issue # -Closes # - -### How was it fixed? - -### Todo: - -- [ ] Clean up commit history -- [ ] Add or update documentation related to these changes -- [ ] Add entry to the [release notes](https://github.com/ethereum/hexbytes/blob/main/newsfragments/README.md) - -#### Cute Animal Picture - -![Put a link to a cute animal picture inside the parenthesis-->](<>) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml new file mode 100644 index 00000000..fb5b254a --- /dev/null +++ b/.github/workflows/docs.yaml @@ -0,0 +1,27 @@ +on: ["push", "pull_request"] + +name: Docs + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + docs: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Setup Python + uses: astral-sh/setup-uv@v7 + with: + python-version: "3.10" + + - name: Build HTML Docs + run: uv run --group docs sphinx-build -b html -W docs docs/_build/html + + - name: Run Doctests + run: uv run --group docs sphinx-build -b doctest -W docs docs/_build/doctest diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 00000000..268e66cd --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,24 @@ +name: Release + +on: + release: + types: [released] + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + id-token: write + + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - uses: astral-sh/setup-uv@v7 + + - name: Build Package + run: uv build + + - name: Publish Package + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 00000000..28ec9021 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,68 @@ +on: ["push", "pull_request"] + +name: Test + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + linting: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Setup Python + uses: astral-sh/setup-uv@v7 + with: + python-version: "3.10" + + - name: Run Ruff Lint + run: uv run --group lint ruff check . + + - name: Run Ruff Format + run: uv run --group lint ruff format --check . + + - name: Run mdformat + run: uv run --group lint mdformat --check *.md + + type-check: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Setup Python + uses: astral-sh/setup-uv@v7 + with: + python-version: "3.10" + + - name: Run MyPy + run: uv run --group lint --group test mypy hexbytes + + test: + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Setup Python + uses: astral-sh/setup-uv@v7 + with: + python-version: ${{ matrix.python-version }} + + - name: Run Tests + run: uv run --group test pytest tests diff --git a/.gitignore b/.gitignore index 2eb3d432..4f62559c 100644 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,9 @@ logs # pyenv .python-version +# uv lock file +uv.lock + # vs-code .vscode diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9b21db65..f8c871cd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,31 +12,12 @@ repos: hooks: - id: pyupgrade args: [--py310-plus] -- repo: https://github.com/psf/black - rev: 23.9.1 +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.14.6 hooks: - - id: black -- repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 - hooks: - - id: flake8 - additional_dependencies: - - flake8-bugbear==23.9.16 - exclude: setup.py -- repo: https://github.com/PyCQA/autoflake - rev: v2.2.1 - hooks: - - id: autoflake -- repo: https://github.com/pycqa/isort - rev: 5.12.0 - hooks: - - id: isort -- repo: https://github.com/pycqa/pydocstyle - rev: 6.3.0 - hooks: - - id: pydocstyle - additional_dependencies: - - tomli # required until >= python311 + - id: ruff-check + args: [--fix] + - id: ruff-format - repo: https://github.com/executablebooks/mdformat rev: 0.7.17 hooks: @@ -47,7 +28,7 @@ repos: hooks: - id: mypy-local name: run mypy with all dev dependencies present - entry: python -m mypy -p hexbytes + entry: uv run --group lint --group test mypy hexbytes language: system always_run: true pass_filenames: false @@ -55,7 +36,7 @@ repos: rev: v0.2.5 hooks: - id: blocklint - exclude: 'docs/Makefile|docs/release_notes.rst|tox.ini' + exclude: 'docs/Makefile|docs/release_notes.rst' - repo: local hooks: - id: check-rst-files diff --git a/.project-template/fill_template_vars.py b/.project-template/fill_template_vars.py deleted file mode 100644 index b964c29e..00000000 --- a/.project-template/fill_template_vars.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python3 - -import os -from pathlib import ( - Path, -) -import re -import sys - - -def _find_files(project_root): - path_exclude_pattern = r"\.git($|\/)|venv|_build" - file_exclude_pattern = r"fill_template_vars\.py|\.swp$" - filepaths = [] - for dir_path, _dir_names, file_names in os.walk(project_root): - if not re.search(path_exclude_pattern, dir_path): - for file in file_names: - if not re.search(file_exclude_pattern, file): - filepaths.append(str(Path(dir_path, file))) - - return filepaths - - -def _replace(pattern, replacement, project_root): - print(f"Replacing values: {pattern}") - for file in _find_files(project_root): - try: - with open(file) as f: - content = f.read() - content = re.sub(pattern, replacement, content) - with open(file, "w") as f: - f.write(content) - except UnicodeDecodeError: - pass - - -def main(): - project_root = Path(os.path.realpath(sys.argv[0])).parent.parent - - module_name = input("What is your python module name? ") - - pypi_input = input(f"What is your pypi package name? (default: {module_name}) ") - pypi_name = pypi_input or module_name - - repo_input = input(f"What is your github project name? (default: {pypi_name}) ") - repo_name = repo_input or pypi_name - - rtd_input = input( - f"What is your readthedocs.org project name? (default: {pypi_name}) " - ) - rtd_name = rtd_input or pypi_name - - project_input = input( - f"What is your project name (ex: at the top of the README)? (default: {repo_name}) " - ) - project_name = project_input or repo_name - - short_description = input("What is a one-liner describing the project? ") - - _replace("", module_name, project_root) - _replace("", pypi_name, project_root) - _replace("", repo_name, project_root) - _replace("", rtd_name, project_root) - _replace("", project_name, project_root) - _replace("", short_description, project_root) - - os.makedirs(project_root / module_name, exist_ok=True) - Path(project_root / module_name / "__init__.py").touch() - Path(project_root / module_name / "py.typed").touch() - - -if __name__ == "__main__": - main() diff --git a/.project-template/refill_template_vars.py b/.project-template/refill_template_vars.py deleted file mode 100644 index ce3c4440..00000000 --- a/.project-template/refill_template_vars.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python3 - -import os -from pathlib import ( - Path, -) -import subprocess -import sys - - -def main(): - template_dir = Path(os.path.dirname(sys.argv[0])) - template_vars_file = template_dir / "template_vars.txt" - fill_template_vars_script = template_dir / "fill_template_vars.py" - - with open(template_vars_file, "r") as input_file: - content_lines = input_file.readlines() - - process = subprocess.Popen( - [sys.executable, str(fill_template_vars_script)], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - ) - - for line in content_lines: - process.stdin.write(line) - process.stdin.flush() - - stdout, stderr = process.communicate() - - if process.returncode != 0: - print(f"Error occurred: {stderr}") - sys.exit(1) - - print(stdout) - - -if __name__ == "__main__": - main() diff --git a/.project-template/template_vars.txt b/.project-template/template_vars.txt deleted file mode 100644 index 1cd54102..00000000 --- a/.project-template/template_vars.txt +++ /dev/null @@ -1,6 +0,0 @@ -hexbytes -hexbytes -hexbytes -hexbytes -HexBytes -Python `bytes` subclass that decodes hex, with a readable console output diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 0584923e..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,12 +0,0 @@ -include LICENSE -include README.md - -recursive-include scripts * -recursive-include tests * - -global-include *.pyi - -recursive-exclude * __pycache__ -recursive-exclude * *.py[co] -prune .tox -prune venv* diff --git a/Makefile b/Makefile deleted file mode 100644 index d395e92f..00000000 --- a/Makefile +++ /dev/null @@ -1,114 +0,0 @@ -CURRENT_SIGN_SETTING := $(shell git config commit.gpgSign) - -.PHONY: clean-pyc clean-build docs - -help: - @echo "clean-build - remove build artifacts" - @echo "clean-pyc - remove Python file artifacts" - @echo "clean - run clean-build and clean-pyc" - @echo "dist - build package and cat contents of the dist directory" - @echo "lint - fix linting issues with pre-commit" - @echo "test - run tests quickly with the default Python" - @echo "docs - generate docs and open in browser (linux-docs for version on linux)" - @echo "autobuild-docs - live update docs when changes are saved" - @echo "package-test - build package and install it in a venv for manual testing" - @echo "notes - consume towncrier newsfragments and update release notes in docs - requires bump to be set" - @echo "release - package and upload a release (does not run notes target) - requires bump to be set" - -clean-build: - rm -fr build/ - rm -fr dist/ - rm -fr *.egg-info - -clean-pyc: - find . -name '*.pyc' -exec rm -f {} + - find . -name '*.pyo' -exec rm -f {} + - find . -name '*~' -exec rm -f {} + - find . -name '__pycache__' -exec rm -rf {} + - -clean: clean-build clean-pyc - -dist: clean - python -m build - ls -l dist - -lint: - @pre-commit run --all-files --show-diff-on-failure || ( \ - echo "\n\n\n * pre-commit should have fixed the errors above. Running again to make sure everything is good..." \ - && pre-commit run --all-files --show-diff-on-failure \ - ) - -test: - python -m pytest tests - -# docs commands - -docs: check-docs - open docs/_build/html/index.html - -linux-docs: check-docs - xdg-open docs/_build/html/index.html - -autobuild-docs: - sphinx-autobuild --open-browser docs docs/_build/html - -# docs helpers - -validate-newsfragments: - python ./newsfragments/validate_files.py - towncrier build --draft --version preview - -check-docs: build-docs validate-newsfragments - -build-docs: - sphinx-apidoc -o docs/ . setup.py "*conftest*" "tests" "tests/*" - $(MAKE) -C docs clean - $(MAKE) -C docs html - $(MAKE) -C docs doctest - -# docs helpers for CI, which requires extra dependencies -check-docs-ci: build-docs build-docs-ci validate-newsfragments - -build-docs-ci: - $(MAKE) -C docs latexpdf - $(MAKE) -C docs epub - -# release commands - -package-test: clean - python -m build - python scripts/release/test_package.py - -notes: check-bump - # Let UPCOMING_VERSION be the version that is used for the current bump - $(eval UPCOMING_VERSION=$(shell bump-my-version bump --dry-run $(bump) -v | awk -F"'" '/New version will be / {print $$2}')) - # Now generate the release notes to have them included in the release commit - towncrier build --yes --version $(UPCOMING_VERSION) - # Before we bump the version, make sure that the towncrier-generated docs will build - make build-docs - git commit -m "Compile release notes for v$(UPCOMING_VERSION)" - -release: check-bump check-git clean - # verify that notes command ran correctly - ./newsfragments/validate_files.py is-empty - CURRENT_SIGN_SETTING=$(git config commit.gpgSign) - git config commit.gpgSign true - bump-my-version bump $(bump) - python -m build - git config commit.gpgSign "$(CURRENT_SIGN_SETTING)" - git push upstream && git push upstream --tags - twine upload dist/* - -# release helpers - -check-bump: -ifndef bump - $(error bump must be set, typically: major, minor, patch, or devnum) -endif - -check-git: - # require that upstream is configured for ethereum/hexbytes - @if ! git remote -v | grep "upstream[[:space:]]git@github.com:ethereum/hexbytes.git (push)\|upstream[[:space:]]https://github.com/ethereum/hexbytes (push)"; then \ - echo "Error: You must have a remote named 'upstream' that points to 'hexbytes'"; \ - exit 1; \ - fi diff --git a/README.md b/README.md index c7e0ade3..772db107 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # HexBytes [![Join the conversation on Discord](https://img.shields.io/discord/809793915578089484?color=blue&label=chat&logo=discord&logoColor=white)](https://discord.gg/GHryRvPB84) -[![Build Status](https://circleci.com/gh/ethereum/hexbytes.svg?style=shield)](https://circleci.com/gh/ethereum/hexbytes) +[![Build Status](https://github.com/ApeWorX/hexbytes/actions/workflows/test.yaml/badge.svg)](https://github.com/ApeWorX/hexbytes/actions/workflows/test.yaml) [![PyPI version](https://badge.fury.io/py/hexbytes.svg)](https://badge.fury.io/py/hexbytes) [![Python versions](https://img.shields.io/pypi/pyversions/hexbytes.svg)](https://pypi.python.org/pypi/hexbytes) [![Docs build](https://readthedocs.org/projects/hexbytes/badge/?version=latest)](https://hexbytes.readthedocs.io/en/latest/?badge=latest) diff --git a/docs/conf.py b/docs/conf.py index dc5c575c..572ac27f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -15,14 +15,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) -import os - -DIR = os.path.dirname(__file__) -with open(os.path.join(DIR, "../setup.py"), "r") as f: - for line in f: - if "version=" in line: - setup_version = line.split('"')[1] - break +from importlib.metadata import version as package_version # -- General configuration ------------------------------------------------ @@ -55,7 +48,7 @@ project = "HexBytes" copyright = "2020-2025, The Ethereum Foundation" -__version__ = setup_version +__version__ = package_version("hexbytes") # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. diff --git a/docs/contributing.rst b/docs/contributing.rst index 3e601f9d..6dff97bb 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -14,17 +14,16 @@ development machine: .. code:: sh git clone git@github.com:your-github-username/hexbytes.git + cd hexbytes -Next, install the development dependencies. We recommend using a virtual environment, -such as `virtualenv `_. +Next, install the development dependencies with +`uv `_. The ``dev`` dependency group is installed +by default, so ``uv sync`` is enough for a local development environment. .. code:: sh - cd hexbytes - virtualenv -p python venv - . venv/bin/activate - python -m pip install -e ".[dev]" - pre-commit install + uv sync + uv run prek install Running the tests ~~~~~~~~~~~~~~~~~ @@ -35,24 +34,26 @@ We can run all tests with: .. code:: sh - pytest tests + uv run pytest Code Style ~~~~~~~~~~ -We use `pre-commit `_ to enforce a consistent code style across -the library. This tool runs automatically with every commit, but you can also run it -manually with: +We use `prek `_ to enforce a consistent code style across +the library. This tool runs automatically with every commit, but you can also run +our linting tools manually with: .. code:: sh - make lint + ruff check + ruff format + mypy . -If you need to make a commit that skips the ``pre-commit`` checks, you can do so with +If you need to make a commit that skips the ``prek`` checks, you can do so with ``git commit --no-verify``. This library uses type hints, which are enforced by the ``mypy`` tool (part of the -``pre-commit`` checks). All new code is required to land with type hints, with the +``prek`` checks). All new code is required to land with type hints, with the exception of code within the ``tests`` directory. Documentation @@ -71,23 +72,20 @@ a discussion, and doesn't necessarily need to be the final, finished submission. GitHub's documentation for working on pull requests is `available here `_. -Once you've made a pull request, take a look at the Circle CI build status in the +Once you've made a pull request, take a look at the GitHub Actions build status in the GitHub interface and make sure all tests are passing. In general pull requests that do not pass the CI build yet won't get reviewed unless explicitly requested. If the pull request introduces changes that should be reflected in the release notes, -please add a `newsfragment` file as explained -`here `_. - -If possible, the change to the release notes file should be included in the commit that -introduces the feature or bugfix. +include the user-facing summary in the pull request description. Release notes are +published through GitHub Releases. Releasing ~~~~~~~~~ -Releases are typically done from the ``main`` branch, except when releasing a beta (in -which case the beta is released from ``main``, and the previous stable branch is -released from said branch). +Releases are published from GitHub Releases. The release tag is the source of truth for +the package version, and ``setuptools-scm`` derives the version during the publishing +workflow. Final review before each release ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -97,67 +95,28 @@ Before releasing a new version, build and test the package that will be released .. code:: sh git checkout main && git pull - make package-test - -This will build the package and install it in a temporary virtual environment. Follow -the instructions to activate the venv and test whatever you think is important. + uv build + uv run pytest + uv run ruff check + uv run ruff format --check + uv run mypy . Review the documentation that will get published: .. code:: sh - make docs - -Validate and preview the release notes: - -.. code:: sh - - make validate-newsfragments - -Build the release notes -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Before bumping the version number, build the release notes. You must include the part of -the version to bump (see below), which changes how the version number will show in the -release notes. - -.. code:: sh - - make notes bump=$$VERSION_PART_TO_BUMP$$ - -If there are any errors, be sure to re-run make notes until it works. + uv run --group docs sphinx-build -b html -W docs docs/_build/html + uv run --group docs sphinx-build -b doctest -W docs docs/_build/doctest Push the release to github & pypi ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -After confirming that the release package looks okay, release a new version: - -.. code:: sh - - make release bump=$$VERSION_PART_TO_BUMP$$ - -This command will: - -- Bump the version number as specified in ``.pyproject.toml`` and ``setup.py``. -- Create a git commit and tag for the new version. -- Build the package. -- Push the commit and tag to github. -- Push the new package files to pypi. - -Which version part to bump -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -``$$VERSION_PART_TO_BUMP$$`` must be one of: ``major``, ``minor``, ``patch``, ``stage``, -or ``devnum``. - -The version format for this repo is ``{major}.{minor}.{patch}`` for stable, and -``{major}.{minor}.{patch}-{stage}.{devnum}`` for unstable (``stage`` can be alpha or -beta). - -If you are in a beta version, ``make release bump=stage`` will switch to a stable. +After confirming that the release package looks okay: -To issue an unstable version when the current version is stable, specify the new version -explicitly, like ``make release bump="--new-version 4.0.0-alpha.1"`` +1. Draft a new GitHub Release. +2. Choose the release tag, for example ``v2.0.0``. +3. Add the release notes. +4. Publish the release. -You can see what the result of bumping any particular version part would be with -``bump-my-version show-bump`` +Publishing the GitHub Release triggers the trusted publishing workflow. The workflow +builds the package with ``uv build`` and publishes it to PyPI through PyPI OIDC. diff --git a/hexbytes/_utils.py b/hexbytes/_utils.py index 8540f3f7..bc99e14d 100644 --- a/hexbytes/_utils.py +++ b/hexbytes/_utils.py @@ -43,9 +43,9 @@ def hexstr_to_bytes(hexstr: str) -> bytes: try: ascii_hex = padded_hex.encode("ascii") - except UnicodeDecodeError: + except UnicodeDecodeError as err: raise ValueError( f"hex string {padded_hex} may only contain [0-9a-fA-F] characters" - ) + ) from err else: return binascii.unhexlify(ascii_hex) diff --git a/hexbytes/main.py b/hexbytes/main.py index 48bef9ad..b0c8f37f 100644 --- a/hexbytes/main.py +++ b/hexbytes/main.py @@ -3,7 +3,6 @@ ) from typing import ( TYPE_CHECKING, - Union, overload, ) @@ -16,7 +15,7 @@ SupportsIndex, ) -BytesLike = Union[bool, bytearray, bytes, int, str, memoryview] +BytesLike = bool | bytearray | bytes | int | str | memoryview class HexBytes(bytes): @@ -43,8 +42,8 @@ def __getitem__(self, key: slice) -> "HexBytes": # noqa: F811 ... def __getitem__( # noqa: F811 - self, key: Union["SupportsIndex", slice] - ) -> Union[int, bytes, "HexBytes"]: + self, key: "SupportsIndex | slice" + ) -> "int | bytes | HexBytes": result = super().__getitem__(key) if hasattr(result, "hex"): return type(self)(result) diff --git a/newsfragments/README.md b/newsfragments/README.md index 177d6492..a532cc98 100644 --- a/newsfragments/README.md +++ b/newsfragments/README.md @@ -23,7 +23,5 @@ So for example: `123.feature.rst`, `456.bugfix.rst` If the PR fixes an issue, use that number here. If there is no issue, then open up the PR first and use the PR number for the newsfragment. -Note that the `towncrier` tool will automatically -reflow your text, so don't try to do any fancy formatting. Run -`towncrier build --draft` to get a preview of what the release notes entry -will look like in the final release notes. +Release notes are now handled in GitHub Releases. Use these fragments only if a +maintainer asks for one while a longer release-notes migration is in progress. diff --git a/pyproject.toml b/pyproject.toml index 41c0d636..50794514 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,19 +1,77 @@ -[tool.autoflake] -exclude = "__init__.py" -remove_all_unused_imports = true +[build-system] +requires = ["setuptools>=75", "wheel", "setuptools_scm[toml]>=8"] +build-backend = "setuptools.build_meta" + +[project] +name = "hexbytes" +dynamic = ["version"] +description = "hexbytes: Python `bytes` subclass that decodes hex, with a readable console output" +readme = "README.md" +requires-python = ">=3.10,<4" +license = "MIT" +authors = [ + { name = "The Ethereum Foundation", email = "snakecharmers@ethereum.org" }, + { name = "ApeWorX Collective", email = "admin@apeworx.io" }, +] +maintainers = [ + { name = "ApeWorX Collective", email = "admin@apeworx.io" }, +] +keywords = ["ethereum"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Natural Language :: English", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", +] +dependencies = [] + +[project.urls] +Homepage = "https://github.com/ApeWorX/hexbytes" +Repository = "https://github.com/ApeWorX/hexbytes.git" +Issues = "https://github.com/ApeWorX/hexbytes/issues" +Documentation = "https://hexbytes.readthedocs.io/" + +[dependency-groups] +test = [ + "eth_utils>=2.0.0", + "hypothesis>=3.44.24", + "pytest>=7.0.0", + "pytest-xdist>=2.4.0", +] +lint = [ + "ruff>=0.12.0", + "mypy==1.18.2", + "mdformat>=0.7.22", + "mdformat-gfm>=0.3.5", + "mdformat-frontmatter>=0.4.1", + "mdformat-pyproject>=0.0.2", +] +docs = [ + "sphinx>=6.0.0", + "sphinx-autobuild>=2021.3.14", + "sphinx_rtd_theme>=1.0.0", +] +dev = [ + { include-group = "test" }, + { include-group = "lint" }, + { include-group = "docs" }, + "ipython", + "prek", +] -[tool.isort] -combine_as_imports = true -extra_standard_library = "pytest" -force_grid_wrap = 1 -force_sort_within_sections = true -force_to_top = "pytest" -honor_noqa = true -known_first_party = "hexbytes" -known_third_party = "hypothesis,pytest" -multi_line_output = 3 -profile = "black" -use_parentheses = true +[tool.setuptools.packages.find] +exclude = ["scripts", "scripts.*", "tests", "tests.*"] + +[tool.setuptools.package-data] +hexbytes = ["py.typed"] + +[tool.setuptools_scm] +fallback_version = "2.0.0b1" [tool.mypy] check_untyped_defs = true @@ -23,6 +81,13 @@ disallow_subclassing_any = true disallow_untyped_calls = true disallow_untyped_decorators = true disallow_untyped_defs = true +exclude = [ + "build/", + "dist/", + "docs/", + "venv/", + ".venv/", +] ignore_missing_imports = true strict_equality = true strict_optional = true @@ -31,138 +96,70 @@ warn_return_any = true warn_unused_configs = true warn_unused_ignores = true - -[tool.pydocstyle] -# All error codes found here: -# http://www.pydocstyle.org/en/3.0.0/error_codes.html -# -# Ignored: -# D1 - Missing docstring error codes -# -# Selected: -# D2 - Whitespace error codes -# D3 - Quote error codes -# D4 - Content related error codes -select = "D2,D3,D4" - -# Extra ignores: -# D200 - One-line docstring should fit on one line with quotes -# D203 - 1 blank line required before class docstring -# D204 - 1 blank line required after class docstring -# D205 - 1 blank line required between summary line and description -# D212 - Multi-line docstring summary should start at the first line -# D302 - Use u""" for Unicode docstrings -# D400 - First line should end with a period -# D401 - First line should be in imperative mood -# D412 - No blank lines allowed between a section header and its content -# D415 - First line should end with a period, question mark, or exclamation point -add-ignore = "D200,D203,D204,D205,D212,D302,D400,D401,D412,D415" - -# Explanation: -# D400 - Enabling this error code seems to make it a requirement that the first -# sentence in a docstring is not split across two lines. It also makes it a -# requirement that no docstring can have a multi-sentence description without a -# summary line. Neither one of those requirements seem appropriate. - [tool.pytest.ini_options] addopts = "-v --showlocals --durations 10" log_date_format = "%m-%d %H:%M:%S" log_format = "%(levelname)8s %(asctime)s %(filename)20s %(message)s" +testpaths = ["tests"] xfail_strict = true -[tool.towncrier] -# Read https://github.com/ethereum/hexbytes/blob/main/newsfragments/README.md for instructions -directory = "newsfragments" -filename = "docs/release_notes.rst" -issue_format = "`#{issue} `__" -package = "hexbytes" -title_format = "hexbytes v{version} ({project_date})" -underlines = ["-", "~", "^"] - -[[tool.towncrier.type]] -directory = "breaking" -name = "Breaking Changes" -showcontent = true - -[[tool.towncrier.type]] -directory = "bugfix" -name = "Bugfixes" -showcontent = true - -[[tool.towncrier.type]] -directory = "deprecation" -name = "Deprecations" -showcontent = true - -[[tool.towncrier.type]] -directory = "docs" -name = "Improved Documentation" -showcontent = true - -[[tool.towncrier.type]] -directory = "feature" -name = "Features" -showcontent = true - -[[tool.towncrier.type]] -directory = "internal" -name = "Internal Changes - for hexbytes Contributors" -showcontent = true - -[[tool.towncrier.type]] -directory = "misc" -name = "Miscellaneous Changes" -showcontent = false +[tool.ruff] +target-version = "py310" +line-length = 88 +exclude = [ + ".eggs", + ".git", + ".mypy_cache", + ".project-template", + ".pytest_cache", + ".ruff_cache", + ".tox", + ".venv", + "build", + "dist", + "docs", + "venv*", +] -[[tool.towncrier.type]] -directory = "performance" -name = "Performance Improvements" -showcontent = true +[tool.ruff.lint] +select = [ + "B", + "D2", + "D3", + "D4", + "E", + "F", + "I", + "UP", + "W", +] +ignore = [ + "D200", + "D203", + "D204", + "D205", + "D212", + "D400", + "D401", + "D412", + "D415", +] -[[tool.towncrier.type]] -directory = "removal" -name = "Removals" -showcontent = true +[tool.ruff.lint.per-file-ignores] +"__init__.py" = ["F401"] -[tool.bumpversion] -current_version = "2.0.0-beta.1" -parse = """ - (?P\\d+) - \\.(?P\\d+) - \\.(?P\\d+) - (- - (?P[^.]*) - \\.(?P\\d+) - )? -""" -serialize = [ - "{major}.{minor}.{patch}-{stage}.{devnum}", - "{major}.{minor}.{patch}", -] -search = "{current_version}" -replace = "{new_version}" -regex = false -ignore_missing_version = false -tag = true -sign_tags = true -tag_name = "v{new_version}" -tag_message = "Bump version: {current_version} → {new_version}" -allow_dirty = false -commit = true -message = "Bump version: {current_version} → {new_version}" +[tool.ruff.lint.pydocstyle] +convention = "pep257" -[tool.bumpversion.parts.stage] -optional_value = "stable" -first_value = "stable" -values = [ - "alpha", - "beta", - "stable" -] +[tool.ruff.lint.isort] +known-first-party = ["hexbytes"] +known-third-party = ["hypothesis", "pytest"] -[tool.bumpversion.part.devnum] +[tool.ruff.format] +quote-style = "double" +line-ending = "auto" +indent-style = "space" +docstring-code-format = true -[[tool.bumpversion.files]] -filename = "setup.py" -search = "version=\"{current_version}\"" -replace = "version=\"{new_version}\"" +[tool.mdformat] +number = true diff --git a/scripts/release/test_package.py b/scripts/release/test_package.py deleted file mode 100644 index 2f23898e..00000000 --- a/scripts/release/test_package.py +++ /dev/null @@ -1,50 +0,0 @@ -from pathlib import ( - Path, -) -import subprocess -from tempfile import ( - TemporaryDirectory, -) -import venv - - -def create_venv(parent_path: Path) -> Path: - venv_path = parent_path / "package-smoke-test" - venv.create(venv_path, with_pip=True) - subprocess.run( - [venv_path / "bin" / "pip", "install", "-U", "pip", "setuptools"], check=True - ) - return venv_path - - -def find_wheel(project_path: Path) -> Path: - wheels = list(project_path.glob("dist/*.whl")) - - if len(wheels) != 1: - raise Exception( - f"Expected one wheel. Instead found: {wheels} " - f"in project {project_path.absolute()}" - ) - - return wheels[0] - - -def install_wheel(venv_path: Path, wheel_path: Path) -> None: - subprocess.run( - [venv_path / "bin" / "pip", "install", f"{wheel_path}"], - check=True, - ) - - -def test_install_local_wheel() -> None: - with TemporaryDirectory() as tmpdir: - venv_path = create_venv(Path(tmpdir)) - wheel_path = find_wheel(Path(".")) - install_wheel(venv_path, wheel_path) - print("Installed", wheel_path.absolute(), "to", venv_path) - print(f"Activate with `source {venv_path}/bin/activate`") - input("Press enter when the test has completed. The directory will be deleted.") - - -if __name__ == "__main__": - test_install_local_wheel() diff --git a/setup.py b/setup.py deleted file mode 100644 index b4d35188..00000000 --- a/setup.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python -from setuptools import ( - find_packages, - setup, -) - -extras_require = { - "dev": [ - "build>=0.9.0", - "bump_my_version>=0.19.0", - "ipython", - "mypy==1.18.2", - "pre-commit>=3.4.0", - "tox>=4.0.0", - "twine", - "wheel>=0.38.1", - ], - "docs": [ - "sphinx>=6.0.0", - "sphinx-autobuild>=2021.3.14", - "sphinx_rtd_theme>=1.0.0", - "towncrier>=24,<25", - ], - "test": [ - "eth_utils>=2.0.0", - "hypothesis>=3.44.24", - "pytest>=7.0.0", - "pytest-xdist>=2.4.0", - ], -} - -extras_require["dev"] = ( - extras_require["dev"] + extras_require["docs"] + extras_require["test"] -) - - -with open("./README.md") as readme: - long_description = readme.read() - - -setup( - name="hexbytes", - # *IMPORTANT*: Don't manually change the version here. See Contributing docs for the release process. - version="2.0.0-beta.1", - description="""hexbytes: Python `bytes` subclass that decodes hex, with a readable console output""", - long_description=long_description, - long_description_content_type="text/markdown", - author="The Ethereum Foundation", - author_email="snakecharmers@ethereum.org", - url="https://github.com/ethereum/hexbytes", - include_package_data=True, - install_requires=[], - python_requires=">=3.10, <4", - extras_require=extras_require, - py_modules=["hexbytes"], - zip_safe=False, - keywords="ethereum", - packages=find_packages(exclude=["scripts", "scripts.*", "tests", "tests.*"]), - package_data={"hexbytes": ["py.typed"]}, - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Natural Language :: English", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3.14", - ], -) diff --git a/tests/core/test_hexbytes.py b/tests/core/test_hexbytes.py index d9b43394..36cf1f6a 100644 --- a/tests/core/test_hexbytes.py +++ b/tests/core/test_hexbytes.py @@ -4,10 +4,14 @@ from eth_utils import ( decode_hex, remove_0x_prefix, +) +from eth_utils import ( to_bytes as standard_to_bytes, ) from hypothesis import ( given, +) +from hypothesis import ( strategies as st, ) @@ -21,7 +25,7 @@ def assert_equal(hexbytes, bytes_expected): assert hexbytes == bytes_expected assert len(hexbytes) == len(bytes_expected) - for byte_actual, byte_expected in zip(hexbytes, bytes_expected): + for byte_actual, byte_expected in zip(hexbytes, bytes_expected, strict=True): assert byte_actual == byte_expected assert bytes(hexbytes) == bytes_expected @@ -88,17 +92,17 @@ def test_hex_inputs(hex_input): def test_pretty_output(): - hb = HexBytes(b"\x0F\x1a") + hb = HexBytes(b"\x0f\x1a") assert repr(hb) == "HexBytes('0x0f1a')" def test_does_not_break_bytes_hex(): - hb = HexBytes(b"\x0F\x1a") + hb = HexBytes(b"\x0f\x1a") assert hb.hex() == "0f1a" def test_to_0x_hex(): - hb = HexBytes(b"\x0F\x1a") + hb = HexBytes(b"\x0f\x1a") assert hb.to_0x_hex() == "0x0f1a" diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 0cd0ef96..00000000 --- a/tox.ini +++ /dev/null @@ -1,71 +0,0 @@ -[tox] -envlist= - py{310,311,312,313,314}-core - py{310,311,312,313,314}-lint - py{310,311,312,313,314}-wheel - windows-wheel - docs - -[flake8] -exclude=venv*,.tox,docs,build -extend-ignore=E203 -max-line-length=88 -per-file-ignores=__init__.py:F401 - -[blocklint] -max_issue_threshold=1 - -[testenv] -usedevelop=True -commands= - core: pytest {posargs:tests/core} - docs: make check-docs-ci -basepython= - docs: python - windows-wheel: python - py310: python3.10 - py311: python3.11 - py312: python3.12 - py313: python3.13 - py314: python3.14 -extras= - test - docs -allowlist_externals=make,pre-commit - -[testenv:py{310,311,312,313,314}-lint] -deps=pre-commit -extras=dev -commands= - pre-commit install - pre-commit run --all-files --show-diff-on-failure - -[testenv:py{310,311,312,313,314}-wheel] -deps= - wheel>=0.38.1 - build[virtualenv] -allowlist_externals= - /bin/rm - /bin/bash -commands= - python -m pip install --upgrade pip - /bin/rm -rf build dist - python -m build - /bin/bash -c 'python -m pip install --upgrade "$(ls dist/hexbytes-*-py3-none-any.whl)" --progress-bar off' - python -c "import hexbytes" -skip_install=true - -[testenv:windows-wheel] -deps= - wheel>=0.38.1 - build[virtualenv] -allowlist_externals= - bash.exe -commands= - python --version - python -m pip install --upgrade pip - bash.exe -c "rm -rf build dist" - python -m build - bash.exe -c 'python -m pip install --upgrade "$(ls dist/hexbytes-*-py3-none-any.whl)" --progress-bar off' - python -c "import hexbytes" -skip_install=true