diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..47ebd5e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,27 @@ +# 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: + # Enable version updates for GitHub Actions + - package-ecosystem: "github-actions" + groups: + actions: + patterns: + - "*" + directory: "/" + schedule: + # Check for updates to GitHub Actions every week + interval: "weekly" + + # Enable version updates for pre-commit hooks + - package-ecosystem: "pre-commit" + directory: "/" + schedule: + interval: "weekly" + groups: + pre-commit: + patterns: + - "*" diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 3d48c6f..014fea8 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -6,6 +6,10 @@ on: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: check: runs-on: ubuntu-latest @@ -16,19 +20,16 @@ jobs: steps: - uses: actions/checkout@v5 - - name: Set up Python 3.12 - uses: actions/setup-python@v6 - with: - python-version: "3.12" - name: Install uv uses: astral-sh/setup-uv@v6 with: + python-version: "3.12" enable-cache: true cache-dependency-glob: "pyproject.toml" - name: Install package with check dependencies - run: uv sync --extra check + run: uv sync --group check # check with ruff - name: Run ruff diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 6e42013..2a008cf 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -11,6 +11,8 @@ name: Upload Python Package on: release: types: [published] + # allow manually running on main + workflow_dispatch: permissions: contents: read diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index fc93c1b..806b68f 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -4,6 +4,10 @@ permissions: contents: read id-token: write +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + on: push: branches: @@ -34,23 +38,23 @@ jobs: # use github python action instead of uv to take advantage of caching - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v6 + uses: astral-sh/setup-uv@v8.1.0 with: python-version: ${{ matrix.python }} cache: 'pip' cache-dependency-path: '**/pyproject.toml' - - name: Install package with dependencies - run: pip install -e ".[test]" + - name: Install package with check dependencies + run: uv sync --group test # for all versions but the one we use for code coverage, run normally - name: Run unit tests without code coverage - run: pytest + run: uv run pytest if: ${{ matrix.python != env.COV_PYTHON_VERSION }} # run code coverage in one version only - name: Run unit tests with code coverage reporting - run: pytest --cov=. + run: uv run pytest --cov=. if: ${{ matrix.python == env.COV_PYTHON_VERSION }} - name: Upload coverage to Codecov diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e49e09d..2aaf155 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,22 +1,46 @@ files: \.py repos: + # ruff for linting and formatting python - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.4 + rev: v0.12.7 hooks: - - id: ruff - args: [ --fix, --exit-non-zero-on-fix ] + - id: ruff-check + args: [ --fix, --show-fixes, --exit-non-zero-on-fix ] - id: ruff-format - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.3.0 hooks: - id: check-case-conflict + - id: check-merge-conflict - id: check-executables-have-shebangs - id: debug-statements - id: end-of-file-fixer - id: mixed-line-ending - id: trailing-whitespace + - id: check-yaml + - id: name-tests-test + args: [--pytest-test-first] + - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.13.0 hooks: - id: mypy additional_dependencies: [numpy] + # yamlfmt for formatting YAML files + - repo: https://github.com/google/yamlfmt + rev: v0.17.2 + hooks: + - id: yamlfmt + # Codespell for spell checking + - repo: https://github.com/codespell-project/codespell + rev: v2.4.1 + hooks: + - id: codespell + additional_dependencies: + - tomli + exclude_types: ["css", "html", "javascript", "json"] + # Validate GitHub Actions workflow files + - repo: https://github.com/mpalmer/action-validator + rev: v0.8.0 + hooks: + - id: action-validator diff --git a/.readthedocs.yaml b/.readthedocs.yaml index a2bd875..250396f 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -11,8 +11,8 @@ sphinx: configuration: docs/conf.py python: - install: - - method: pip - path: . - extra_requirements: - - docs + install: + - method: uv + command: sync + groups: + - docs diff --git a/DEVELOPER_NOTES.md b/DEVELOPER_NOTES.md index c7db002..6ea99d6 100644 --- a/DEVELOPER_NOTES.md +++ b/DEVELOPER_NOTES.md @@ -44,9 +44,15 @@ source .venv/bin/activate Install an editable version of the local package along with python dependencies needed for testing and development. ```sh -pip install -e ".[dev]" +pip install -e . --group=dev ``` +If using `uv`, use + +```sh + uv sync --group test + ``` + ### Install pre-commit hooks We use [pre-commit](https://pre-commit.com/) for automated checks and consistent formatting. If you're planning to contribute, please install these when you set up your local development. diff --git a/pyproject.toml b/pyproject.toml index ac39aac..08e9b70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ dynamic = ["version"] dependencies = [ "lark[interegular]", "numpy", - "convertdate", + "convertdate>=2.4,<2.4.1", # changes syntax, deprecation warning "strenum; python_version < '3.11'", ] authors = [ @@ -42,7 +42,6 @@ classifiers = [ "Programming Language :: Python :: 3.13", "Intended Audience :: Developers", "Intended Audience :: Science/Research", - "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Scientific/Engineering", @@ -51,21 +50,20 @@ classifiers = [ ] -[project.optional-dependencies] +[dependency-groups] docs = ["sphinx>=7.0.0", "alabaster", "myst-parser", "myst-parser[linkify]"] -test = ["pytest>=7.2", "pytest-ordering", "pytest-cov"] +test = ["pytest>=9", "pytest-ordering", "pytest-cov"] notebooks = ["jupyterlab", "pandas", "treon", "altair"] -check = ["undate[docs]", "undate[notebooks]", "mypy", "ruff"] +check = [ { include-group = "docs" }, {include-group = "notebooks"}, "mypy", "ruff"] dev = [ "pre-commit>=2.20.0", "twine", "wheel", "build", - "undate[check]", - "undate[docs]", - "undate[test]", + { include-group = "test" }, + { include-group = "check" }, + { include-group = "docs" } ] -all = ["undate", "undate[dev]"] [project.urls] Homepage = "https://github.com/dh-tech/undate-python" @@ -87,9 +85,16 @@ dependencies = ["babel"] [tool.hatch.envs.codegen.scripts] generate = "python scripts/generate_gregorian_grammar.py" -[tool.pytest.ini_options] -pythonpath = "src/" -testpaths = ["tests/"] +[tool.pytest] +minversion = "9" +log_level = "INFO" +strict = true +addopts = ["-ra"] +filterwarnings = ["error"] +pythonpath = [ "src/" ] +testpaths = [ + "tests", +] markers = [ "last : run marked tests after all others", "first : run marked tests before all others", diff --git a/src/undate/converters/base.py b/src/undate/converters/base.py index 93a63a7..88f64a4 100644 --- a/src/undate/converters/base.py +++ b/src/undate/converters/base.py @@ -102,7 +102,7 @@ def import_converters(cls) -> int: logger.debug("Loading converters under undate.converters") import undate.converters - # load packages under this path with curent package prefix + # load packages under this path with current package prefix converter_path = undate.converters.__path__ converter_prefix = f"{undate.converters.__name__}." diff --git a/src/undate/converters/calendars/__init__.py b/src/undate/converters/calendars/__init__.py index 5836b2f..f0aa6ff 100644 --- a/src/undate/converters/calendars/__init__.py +++ b/src/undate/converters/calendars/__init__.py @@ -6,6 +6,6 @@ __all__ = [ "GregorianDateConverter", "HebrewDateConverter", - "IslamicDateConverter", + "IslamicDateConverter", "SeleucidDateConverter", ] diff --git a/src/undate/converters/calendars/hebrew/converter.py b/src/undate/converters/calendars/hebrew/converter.py index dc8ad19..0c4a67d 100644 --- a/src/undate/converters/calendars/hebrew/converter.py +++ b/src/undate/converters/calendars/hebrew/converter.py @@ -115,4 +115,4 @@ def parse(self, value: str) -> Union[Undate, UndateInterval]: raise ValueError(f"Could not parse '{value}' as a Hebrew date") from err # do we need to support conversion the other direction? - # i.e., generate a Hebrew date from an abitrary undate or undate interval? + # i.e., generate a Hebrew date from an arbitrary undate or undate interval? diff --git a/src/undate/converters/iso8601.py b/src/undate/converters/iso8601.py index 4f05b69..fbcafd5 100644 --- a/src/undate/converters/iso8601.py +++ b/src/undate/converters/iso8601.py @@ -103,7 +103,7 @@ def _undate_to_string(self, undate: Undate) -> str: elif date_portion == "year": # if year is not known, add '-' for year portion, - # to genereate --MM-DD unknown year format + # to generate --MM-DD unknown year format date_parts.append("-") # TODO: fix type error: "list[str | None]" is incompatible with "Iterable[str]" return "-".join(date_parts) # type: ignore diff --git a/src/undate/date.py b/src/undate/date.py index ee87d30..e72f545 100644 --- a/src/undate/date.py +++ b/src/undate/date.py @@ -297,7 +297,7 @@ class DatePrecision(IntEnum): of the date is known.""" # NOTE: values MUST be ordered based on the relative size or - # precison of the time unit. That is, the smaller the unit, the more precise + # precision of the time unit. That is, the smaller the unit, the more precise # it is: a day is more precise than a month, a month is more precise than a year, # (DatePrecision.year < DatePrecision.month) diff --git a/src/undate/undate.py b/src/undate/undate.py index 5ca407f..5999ab1 100644 --- a/src/undate/undate.py +++ b/src/undate/undate.py @@ -177,7 +177,7 @@ def calculate_earliest_latest(self, year, month, day): # if we have no day or partial day, calculate min / max min_day = 1 # is min day ever anything other than 1 ? rel_year = year if year and isinstance(year, int) else max_year - # use month if it is an integer; otherwise use previusly determined + # use month if it is an integer; otherwise use previously determined # max month (which may not be 12 depending if partially unknown) rel_month = month if month and isinstance(month, int) else latest_month diff --git a/tests/test_converters/test_calendars/test_islamic/test_islamic_transformer.py b/tests/test_converters/test_calendars/test_islamic/test_islamic_transformer.py index 04ff53b..880988e 100644 --- a/tests/test_converters/test_calendars/test_islamic/test_islamic_transformer.py +++ b/tests/test_converters/test_calendars/test_islamic/test_islamic_transformer.py @@ -28,7 +28,7 @@ def test_islamic_undate(): # examples from ISMI data (reformatted to day month year) # Rabi 1 = month 3 ("14 Rabīʿ I 901", IslamicUndate(901, 3, 14), DatePrecision.DAY), - ("Rabīʿ I 490", IslamicUndate(490, 3), DatePrecision.MONTH), + ("Rabīʿ I 490", IslamicUndate(490, 3), DatePrecision.MONTH), ("884", IslamicUndate(884), DatePrecision.YEAR), # Gregorian: UndateInterval(Undate(1479, 4, 3), Undate(1480, 3, 21)), # add when we support parsing ranges: diff --git a/tests/test_converters/test_combined_parser.py b/tests/test_converters/test_combined_parser.py index 02a79e6..a61297b 100644 --- a/tests/test_converters/test_combined_parser.py +++ b/tests/test_converters/test_combined_parser.py @@ -29,7 +29,7 @@ ("Epiphany 1921", Undate(1921, 1, 6)), ("Pentecost 2016", Undate(2016, 5, 15)), ("Ash Wednesday 2000", Undate(2000, 3, 8)), - ("Whit Monday 2023", Undate(2023, 5, 29)), + ("Whit Monday 2023", Undate(2023, 5, 29)), # codespell:ignore whit ] diff --git a/tests/test_converters/test_holidays.py b/tests/test_converters/test_holidays.py index cf30bbe..708cc19 100644 --- a/tests/test_converters/test_holidays.py +++ b/tests/test_converters/test_holidays.py @@ -36,7 +36,11 @@ def test_fixed_holidays(self, input_string, expected): ("Ascension 1988", Undate(1988, 5, 12), Weekday.THURSDAY), ("Ascension Day 1999", Undate(1999, 5, 13), Weekday.THURSDAY), ("Pentecost 2016", Undate(2016, 5, 15), Weekday.SUNDAY), - ("whit monday 2005", Undate(2005, 5, 16), Weekday.MONDAY), + ( + "whit monday 2005", # codespell:ignore whit + Undate(2005, 5, 16), + Weekday.MONDAY, + ), ("whitsun monday 2023", Undate(2023, 5, 29), Weekday.MONDAY), ("trinity 1978", Undate(1978, 5, 21), Weekday.SUNDAY), ("Trinity Sunday 1967", Undate(1967, 5, 21), Weekday.SUNDAY),