diff --git a/.cruft.json b/.cruft.json new file mode 100644 index 0000000..77ac132 --- /dev/null +++ b/.cruft.json @@ -0,0 +1,17 @@ +{ + "template": "http://github.com/allenporter/cookiecutter-python", + "commit": "09fcc9a7574cc92301d2b72ea2535fed208a2e39", + "checkout": null, + "context": { + "cookiecutter": { + "full_name": "Allen Porter", + "email": "allen.porter@gmail.com", + "github_username": "allenporter", + "project_name": "ical", + "description": "Python iCalendar implementation (rfc 2445)", + "version": "8.0.1", + "_template": "http://github.com/allenporter/cookiecutter-python" + } + }, + "directory": null +} diff --git a/renovate.json5 b/.github/renovate.json5 similarity index 75% rename from renovate.json5 rename to .github/renovate.json5 index d39a194..6a2cb3e 100644 --- a/renovate.json5 +++ b/.github/renovate.json5 @@ -12,7 +12,8 @@ "matchUpdateTypes": ["minor", "patch"] } ], - "pre-commit": { - "enabled": true - } + "pip_requirements": { + "fileMatch": ["requirements_dev.txt"] + }, + "pre-commit": {"enabled": true} } diff --git a/.github/workflows/benchmark.yaml b/.github/workflows/benchmark.yaml new file mode 100644 index 0000000..1bbffb3 --- /dev/null +++ b/.github/workflows/benchmark.yaml @@ -0,0 +1,34 @@ +--- +name: Python benchmarks + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: "pip" + cache-dependency-path: "**/requirements_dev.txt" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements_dev.txt ]; then pip install -r requirements_dev.txt; fi + - name: Run benchmarks with pytest + run: | + pytest --benchmark-only diff --git a/.github/workflows/cruft.yaml b/.github/workflows/cruft.yaml new file mode 100644 index 0000000..75bc576 --- /dev/null +++ b/.github/workflows/cruft.yaml @@ -0,0 +1,79 @@ +--- +name: Update repository with Cruft +permissions: + contents: write + pull-requests: write +on: + schedule: + - cron: "0 0 * * *" + +env: + PYTHON_VERSION: 3.12 + +jobs: + update: + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + include: + - add-paths: . + body: Use this to merge the changes to this repository. + branch: cruft/update + commit-message: "chore: accept new Cruft update" + title: New updates detected with Cruft + - add-paths: .cruft.json + body: Use this to reject the changes in this repository. + branch: cruft/reject + commit-message: "chore: reject new Cruft update" + title: Reject new updates detected with Cruft + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install Cruft + run: pip3 install cruft + + - name: Check if update is available + continue-on-error: false + id: check + run: | + CHANGES=0 + if [ -f .cruft.json ]; then + if ! cruft check; then + CHANGES=1 + fi + else + echo "No .cruft.json file" + fi + + echo "has_changes=$CHANGES" >> "$GITHUB_OUTPUT" + + - name: Run update if available + if: steps.check.outputs.has_changes == '1' + run: | + git config --global user.email "allen.porter@gmail.com" + git config --global user.name "Allen Porter" + + cruft update --skip-apply-ask --refresh-private-variables + git restore --staged . + + + - name: Create pull request + if: steps.check.outputs.has_changes == '1' + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.GITHUB_TOKEN }} + add-paths: ${{ matrix.add-paths }} + commit-message: ${{ matrix.commit-message }} + branch: ${{ matrix.branch }} + delete-branch: true + branch-suffix: timestamp + title: ${{ matrix.title }} + body: | + This is an autogenerated PR. ${{ matrix.body }} + + [Cruft](https://cruft.github.io/cruft/) has detected updates from the Cookiecutter repository. diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 62a80e7..031141d 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -1,3 +1,4 @@ +--- name: Lint on: @@ -8,27 +9,40 @@ on: branches: - main +env: + PYTHON_VERSION: 3.12 + jobs: build: - runs-on: ubuntu-latest strategy: fail-fast: false steps: - - uses: actions/checkout@v4 - - uses: chartboost/ruff-action@v1.0.0 - - uses: codespell-project/actions-codespell@v2.0 - with: - check_hidden: false - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements_dev.txt - - name: Static typing with mypy - run: | - mypy --install-types --non-interactive --no-warn-unused-ignores . + - uses: actions/checkout@v4 + - uses: chartboost/ruff-action@v1.0.0 + - uses: codespell-project/actions-codespell@v2.0 + with: + check_hidden: false + - name: Run yamllint + uses: ibiqlik/action-yamllint@v3 + with: + file_or_dir: "./" + config_file: "./.yaml-lint.yaml" + strict: true + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + cache: "pip" + cache-dependency-path: "**/requirements_dev.txt" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements_dev.txt + + - name: Static typing with mypy + run: | + mypy --install-types --non-interactive --no-warn-unused-ignores . diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yaml similarity index 61% rename from .github/workflows/pages.yml rename to .github/workflows/pages.yaml index f298f31..0c98c6d 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yaml @@ -1,29 +1,27 @@ -# Simple workflow for deploying static content to GitHub Pages +--- name: Deploy static content to Pages on: - # Runs on pushes targeting the default branch push: branches: - main - # Allows you to run this workflow manually from the Actions tab workflow_dispatch: -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: - actions: read contents: read pages: write id-token: write + actions: read -# Allow one concurrent deployment concurrency: group: "pages" cancel-in-progress: true +env: + PYTHON_VERSION: 3.12 + jobs: - # Single deploy job since we're just deploying deploy: environment: name: github-pages @@ -31,17 +29,18 @@ jobs: runs-on: ubuntu-latest strategy: fail-fast: false - matrix: - python-version: ["3.12"] steps: - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} + - name: Set up Python uses: actions/setup-python@v5 with: - python-version: ${{ matrix.python-version }} + python-version: ${{ env.PYTHON_VERSION }} + cache: "pip" + cache-dependency-path: "**/requirements_dev.txt" - name: Install dependencies run: | - if [ -f requirements_dev.txt ]; then pip install -r requirements_dev.txt; fi + python -m pip install --upgrade pip + pip install -r requirements_dev.txt - run: pdoc ./ical -o docs/ - name: Setup Pages uses: actions/configure-pages@v5 diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000..8e37333 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,51 @@ +--- +name: Upload Python Package + +on: + release: + types: [created] + +env: + PYTHON_VERSION: 3.12 + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build --user + - name: Build a binary wheel and a source tarball + run: python3 -m build + - name: Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + publish-to-pypi: + name: >- + Publish Python 🐍 distribution 📦 to PyPI + if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes + needs: + - build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/ical + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution 📦 to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/python-benchmark.yaml b/.github/workflows/python-benchmark.yaml deleted file mode 100644 index 283b888..0000000 --- a/.github/workflows/python-benchmark.yaml +++ /dev/null @@ -1,32 +0,0 @@ -name: Python benchmarks - -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - build: - - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: ["3.10", "3.11", "3.12"] - - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - if [ -f requirements_dev.txt ]; then pip install -r requirements_dev.txt; fi - - name: Run benchmarks with pytest - run: | - pytest --benchmark-only diff --git a/.github/workflows/python-package.yaml b/.github/workflows/python-package.yaml deleted file mode 100644 index febc371..0000000 --- a/.github/workflows/python-package.yaml +++ /dev/null @@ -1,44 +0,0 @@ -name: Python package - -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - build: - - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: ["3.10", "3.11", "3.12"] - - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - if [ -f requirements_dev.txt ]; then pip install -r requirements_dev.txt; fi - - name: Test with pytest - run: | - pytest --cov=ical --cov-report=term-missing - - name: Test with pytest (pydantic v1) - run: | - pip3 install pydantic==1.10.3 - pytest --cov=ical --cov-report=term-missing - if: matrix.python-version == '3.11' - - - uses: codecov/codecov-action@v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - env_vars: OS,PYTHON - fail_ci_if_error: true - verbose: true diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml deleted file mode 100644 index 6e293c3..0000000 --- a/.github/workflows/python-publish.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Upload Python Package - -on: - release: - types: - - published - -permissions: - contents: read - -jobs: - deploy: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install build - - name: Build package - run: python -m build - - name: Publish package - uses: pypa/gh-action-pypi-publish@v1.8.14 - with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..3130462 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,40 @@ +--- +name: Test + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: "pip" + cache-dependency-path: "**/requirements_dev.txt" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements_dev.txt ]; then pip install -r requirements_dev.txt; fi + - name: Test with pytest + run: | + pytest --cov=ical --cov-report=term-missing + - uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + env_vars: OS,PYTHON + fail_ci_if_error: true + verbose: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e24ccc4..2fa2140 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,37 +1,50 @@ +--- repos: -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 - hooks: - - id: trailing-whitespace - exclude: 'tests/testdata/.*yaml$' - - id: end-of-file-fixer - - id: check-yaml - - id: check-added-large-files -- repo: https://github.com/charliermarsh/ruff-pre-commit - rev: 'v0.4.4' - hooks: - - id: ruff - args: [--fix, --exit-non-zero-on-fix] -- repo: https://github.com/psf/black - rev: 24.4.2 - hooks: - - id: black -- repo: local - hooks: - - id: mypy - name: mypy - entry: "./run-mypy" - language: python - additional_dependencies: ["mypy==0.930"] - types: [python] - # use require_serial so that script - # is only called once per commit - require_serial: true -- repo: https://github.com/codespell-project/codespell - rev: v2.2.6 - hooks: - - id: codespell - exclude: | + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: trailing-whitespace + exclude: 'tests/testdata/.*yaml$' + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - repo: https://github.com/psf/black + rev: 24.4.2 + hooks: + - id: black + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.4.4 + hooks: + - id: ruff + args: + - --fix + - --exit-non-zero-on-fix + - repo: local + hooks: + - id: mypy + name: mypy + entry: script/run-mypy.sh + language: script + types: [python] + require_serial: true + - repo: https://github.com/codespell-project/codespell + rev: v2.2.6 + hooks: + - id: codespell + exclude: | + (?x)^( tests/tzif/testdata/rfc8536-v3.yaml )$ + - repo: https://github.com/adrienverge/yamllint.git + rev: v1.35.1 + hooks: + - id: yamllint + exclude: '^tests/tool/testdata/.*\.yaml$' + args: + - -c + - ".yaml-lint.yaml" + - repo: https://github.com/asottile/setup-cfg-fmt + rev: v2.5.0 + hooks: + - id: setup-cfg-fmt diff --git a/.yaml-lint.yaml b/.yaml-lint.yaml new file mode 100644 index 0000000..0577a9c --- /dev/null +++ b/.yaml-lint.yaml @@ -0,0 +1,20 @@ +--- +ignore: | + venv + tests/testdata +extends: default +rules: + truthy: + allowed-values: ['true', 'false', 'on', 'yes'] + comments: + min-spaces-from-content: 1 + line-length: disable + braces: + min-spaces-inside: 0 + max-spaces-inside: 1 + brackets: + min-spaces-inside: 0 + max-spaces-inside: 0 + indentation: + spaces: 2 + indent-sequences: consistent diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index e854ce8..0000000 --- a/mypy.ini +++ /dev/null @@ -1,24 +0,0 @@ -[mypy] -plugins = pydantic.mypy -ignore_missing_imports = True -exclude = (venv|tests) -check_untyped_defs = True -disallow_any_generics = True -disallow_incomplete_defs = True -disallow_subclassing_any = True -disallow_untyped_calls = True -disallow_untyped_decorators = True -disallow_untyped_defs = True -no_implicit_optional = True -no_implicit_reexport = True -warn_return_any = True -warn_unreachable = True -warn_redundant_casts = True -warn_unused_ignores = True -warn_no_return = True - -[pydantic-mypy] -init_forbid_extra = False -init_typed = True -warn_required_dynamic_aliases = True -warn_untyped_fields = True diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..6ccdeca --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,39 @@ +[tool.mypy] +plugins = "pydantic.mypy" +exclude = [ + "venv/", + "tests/", +] +platform = "linux" +show_error_codes = true +follow_imports = "normal" +local_partial_types = true +strict_equality = true +no_implicit_optional = true +warn_incomplete_stub = true +warn_redundant_casts = true +warn_unused_configs = true +warn_unused_ignores = true +disable_error_code = [ + "import-untyped", +] +extra_checks = false +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +warn_return_any = true +warn_unreachable = true +# Additional checks +ignore_missing_imports = true +disallow_any_generics = true +no_implicit_reexport = true +warn_no_return = true + +[tool.pydantic-mypy] +init_forbid_extra = false +init_typed = true +warn_required_dynamic_aliases = true +warn_untyped_fields = true diff --git a/requirements_dev.txt b/requirements_dev.txt index 537fa80..1121430 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,21 +1,22 @@ -e . -coverage==6.4.2 +black==24.4.2 +coverage==7.5.1 +mypy==1.10.0 +pdoc==14.4.0 +pip==24.0 +pre-commit==3.6.0 +pytest-cov==5.0.0 +pytest==8.2.0 +ruff==0.4.4 + emoji==2.2.0 python-dateutil==2.8.2 freezegun==1.2.1 -pdoc==12.1.0 -pip==22.1.2 pydantic==2.0.3 pyparsing==3.0.9 -pytest==7.1.2 -pytest-benchmark==3.4.1 -pytest-cov==3.0.0 +pytest-benchmark==4.0.0 types-python-dateutil==2.8.19 -ruff==0.0.253 -black==22.10.0 wheel==0.37.1 PyYAML==6.0.1 types-PyYAML==6.0.12.8 -pre-commit==3.6.0 -mypy==1.8.0 -syrupy==4.6.0 +syrupy==4.6.1 diff --git a/run-mypy b/run-mypy deleted file mode 100755 index fc3804b..0000000 --- a/run-mypy +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit - -# Change directory to the project root directory. -cd "$(dirname "$0")" - -pip3 install -r requirements_dev.txt --no-input --quiet - -mypy . diff --git a/script/run-mypy.sh b/script/run-mypy.sh new file mode 100755 index 0000000..eede4d7 --- /dev/null +++ b/script/run-mypy.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -o errexit + +# other common virtualenvs +my_path=$(git rev-parse --show-toplevel) + +if [ -f "${my_path}/venv/bin/activate" ]; then + . "${my_path}/venv/bin/activate" +fi + +mypy ${my_path}